mirror of
https://github.com/stenzek/duckstation.git
synced 2026-01-31 02:05:18 +01:00
5499 lines
239 KiB
C++
5499 lines
239 KiB
C++
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
|
|
|
#include "achievements.h"
|
|
#include "bios.h"
|
|
#include "cheats.h"
|
|
#include "controller.h"
|
|
#include "core.h"
|
|
#include "fullscreenui_private.h"
|
|
#include "game_database.h"
|
|
#include "game_list.h"
|
|
#include "gpu.h"
|
|
#include "gpu_presenter.h"
|
|
#include "gpu_thread.h"
|
|
#include "gte.h"
|
|
#include "host.h"
|
|
#include "input_types.h"
|
|
#include "settings.h"
|
|
#include "system.h"
|
|
|
|
#include "util/imgui_manager.h"
|
|
#include "util/ini_settings_interface.h"
|
|
#include "util/input_manager.h"
|
|
#include "util/postprocessing.h"
|
|
|
|
#include "common/assert.h"
|
|
#include "common/error.h"
|
|
#include "common/file_system.h"
|
|
#include "common/log.h"
|
|
#include "common/path.h"
|
|
#include "common/string_util.h"
|
|
|
|
#include "IconsEmoji.h"
|
|
#include "IconsFontAwesome.h"
|
|
#include "IconsPromptFont.h"
|
|
|
|
#include <limits>
|
|
#include <mutex>
|
|
|
|
LOG_CHANNEL(FullscreenUI);
|
|
|
|
#ifndef __ANDROID__
|
|
|
|
namespace FullscreenUI {
|
|
|
|
namespace {
|
|
|
|
class InputBindingDialog : public PopupDialog
|
|
{
|
|
public:
|
|
InputBindingDialog();
|
|
~InputBindingDialog();
|
|
|
|
void Draw();
|
|
void ClearState();
|
|
|
|
void Start(SettingsInterface* bsi, InputBindingInfo::Type type, std::string_view section, std::string_view key,
|
|
std::string_view display_name);
|
|
|
|
private:
|
|
static constexpr float INPUT_BINDING_TIMEOUT_SECONDS = 5.0f;
|
|
|
|
std::string m_binding_section;
|
|
std::string m_binding_key;
|
|
std::string m_display_name;
|
|
std::vector<InputBindingKey> m_new_bindings;
|
|
std::vector<std::pair<InputBindingKey, std::pair<float, float>>> m_value_ranges;
|
|
float m_time_remaining = 0.0f;
|
|
InputBindingInfo::Type m_binding_type = InputBindingInfo::Type::Unknown;
|
|
};
|
|
|
|
struct PostProcessingStageInfo
|
|
{
|
|
std::string name;
|
|
std::vector<PostProcessing::ShaderOption> options;
|
|
bool enabled;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
static void PopulateHotkeyList();
|
|
|
|
static void DrawSummarySettingsPage(bool show_localized_titles);
|
|
static void DrawInterfaceSettingsPage();
|
|
static void DrawGameListSettingsPage();
|
|
static void DrawBIOSSettingsPage();
|
|
static void DrawConsoleSettingsPage();
|
|
static void DrawEmulationSettingsPage();
|
|
static void DrawGraphicsSettingsPage();
|
|
static void DrawPostProcessingSettingsPage();
|
|
static void DrawAudioSettingsPage();
|
|
static void DrawMemoryCardSettingsPage();
|
|
static void DrawControllerSettingsPage();
|
|
static void DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& settings_lock);
|
|
static void DrawAchievementsSettingsHeader(SettingsInterface* bsi, std::unique_lock<std::mutex>& settings_lock);
|
|
static void DrawAdvancedSettingsPage();
|
|
static void DrawPatchesOrCheatsSettingsPage(bool cheats);
|
|
|
|
static void DrawCoverDownloaderWindow();
|
|
static void DrawAchievementsLoginWindow();
|
|
static void StartAchievementsProgressRefresh();
|
|
static void StartAchievementsGameIconDownload();
|
|
|
|
static bool ShouldShowAdvancedSettings();
|
|
static bool IsEditingGameSettings(SettingsInterface* bsi);
|
|
static SettingsInterface* GetEditingSettingsInterface();
|
|
static SettingsInterface* GetEditingSettingsInterface(bool game_settings);
|
|
static void SetSettingsChanged(SettingsInterface* bsi);
|
|
static bool GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value);
|
|
static s32 GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, s32 default_value);
|
|
static u32 GetEffectiveUIntSetting(SettingsInterface* bsi, const char* section, const char* key, u32 default_value);
|
|
static float GetEffectiveFloatSetting(SettingsInterface* bsi, const char* section, const char* key,
|
|
float default_value);
|
|
static TinyString GetEffectiveTinyStringSetting(SettingsInterface* bsi, const char* section, const char* key,
|
|
const char* default_value = "");
|
|
static void BeginResetSettings();
|
|
static void DoCopyGameSettings();
|
|
static void DoClearGameSettings();
|
|
static void CopyGlobalControllerSettingsToGame();
|
|
static void BeginResetControllerSettings();
|
|
static void DoLoadInputProfile();
|
|
static void DoSaveInputProfile();
|
|
static void DoSaveNewInputProfile();
|
|
static void DoSaveInputProfile(const std::string& name);
|
|
|
|
static bool DrawToggleSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, bool default_value, bool enabled = true,
|
|
bool allow_tristate = true);
|
|
static void DrawIntListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value,
|
|
std::span<const char* const> options, bool translate_options = true,
|
|
int option_offset = 0, bool enabled = true,
|
|
std::string_view tr_context = FSUI_TR_CONTEXT);
|
|
static void DrawIntListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value,
|
|
std::span<const char* const> options, bool translate_options,
|
|
std::span<const int> values, bool enabled = true,
|
|
std::string_view tr_context = FSUI_TR_CONTEXT);
|
|
static void DrawIntRangeSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value, int min_value, int max_value,
|
|
const char* format = "%d", bool enabled = true);
|
|
static void DrawIntSpinBoxSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value, int min_value, int max_value,
|
|
int step_value, const char* format = "%d", bool enabled = true);
|
|
static void DrawFloatRangeSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, float default_value, float min_value,
|
|
float max_value, const char* format = "%f", float multiplier = 1.0f,
|
|
bool enabled = true);
|
|
static void DrawFloatSpinBoxSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, float default_value, float min_value,
|
|
float max_value, float step_value, float multiplier, const char* format = "%f",
|
|
bool enabled = true);
|
|
static bool DrawIntRectSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* left_key, int default_left, const char* top_key,
|
|
int default_top, const char* right_key, int default_right, const char* bottom_key,
|
|
int default_bottom, int min_value, int max_value, const char* format = "%d",
|
|
bool enabled = true);
|
|
static void DrawStringListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, const char* default_value,
|
|
std::span<const char* const> options, std::span<const char* const> option_values,
|
|
bool enabled = true, void (*changed_callback)(std::string_view) = nullptr,
|
|
std::string_view tr_context = FSUI_TR_CONTEXT);
|
|
template<typename DataType, typename SizeType>
|
|
static void DrawEnumSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, DataType default_value,
|
|
std::optional<DataType> (*from_string_function)(const char* str),
|
|
const char* (*to_string_function)(DataType value),
|
|
const char* (*to_display_string_function)(DataType value), SizeType option_count,
|
|
bool enabled = true);
|
|
static void DrawFloatListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, float default_value, const char* const* options,
|
|
const float* option_values, size_t option_count, bool translate_options,
|
|
bool enabled = true);
|
|
static void DrawFolderSetting(SettingsInterface* bsi, std::string_view title, const char* section, const char* key,
|
|
const std::string& runtime_var);
|
|
|
|
static void PopulateGraphicsAdapterList();
|
|
static void PopulateGameListDirectoryCache(const SettingsInterface& si);
|
|
static void PopulatePatchesAndCheatsList();
|
|
static void PopulatePostProcessingChain(const SettingsInterface& si, const char* section);
|
|
static void BeginEffectBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
|
|
const char* key, std::string_view display_name);
|
|
static void DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
|
|
const char* name, std::string_view display_name, std::string_view icon_name,
|
|
bool show_type = true);
|
|
static void StartAutomaticBindingForPort(u32 port);
|
|
static void StartClearBindingsForPort(u32 port);
|
|
|
|
static constexpr std::string_view ACHIEVEMENTS_LOGIN_DIALOG_NAME = "##achievements_login";
|
|
static constexpr std::string_view COVER_DOWNLOADER_DIALOG_NAME = "##cover_downloader";
|
|
|
|
namespace {
|
|
struct SettingsLocals
|
|
{
|
|
float settings_last_bg_alpha = 1.0f;
|
|
SettingsPage settings_page = SettingsPage::Interface;
|
|
std::unique_ptr<INISettingsInterface> game_settings_interface;
|
|
std::unique_ptr<GameList::Entry> game_settings_entry;
|
|
std::vector<std::pair<std::string, bool>> game_list_directories_cache;
|
|
GPUDevice::AdapterInfoList graphics_adapter_list_cache;
|
|
std::vector<std::string> fullscreen_mode_list_cache;
|
|
Cheats::CodeInfoList game_patch_list;
|
|
std::vector<std::string> enabled_game_patch_cache;
|
|
Cheats::CodeInfoList game_cheats_list;
|
|
std::vector<std::string> enabled_game_cheat_cache;
|
|
std::vector<std::string_view> game_cheat_groups;
|
|
std::vector<PostProcessingStageInfo> postprocessing_stages;
|
|
std::vector<const HotkeyInfo*> hotkey_list_cache;
|
|
s8 selected_controller_port = -1;
|
|
bool settings_changed = false;
|
|
bool game_settings_changed = false;
|
|
InputBindingDialog input_binding_dialog;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
ALIGN_TO_CACHE_LINE static SettingsLocals s_settings_locals;
|
|
|
|
} // namespace FullscreenUI
|
|
|
|
bool FullscreenUI::ShouldShowAdvancedSettings()
|
|
{
|
|
return Core::GetBaseBoolSettingValue("Main", "ShowDebugMenu", false);
|
|
}
|
|
|
|
bool FullscreenUI::IsEditingGameSettings(SettingsInterface* bsi)
|
|
{
|
|
return (bsi == s_settings_locals.game_settings_interface.get());
|
|
}
|
|
|
|
SettingsInterface* FullscreenUI::GetEditingSettingsInterface()
|
|
{
|
|
return s_settings_locals.game_settings_interface ? s_settings_locals.game_settings_interface.get() :
|
|
Core::GetBaseSettingsLayer();
|
|
}
|
|
|
|
SettingsInterface* FullscreenUI::GetEditingSettingsInterface(bool game_settings)
|
|
{
|
|
return (game_settings && s_settings_locals.game_settings_interface) ?
|
|
s_settings_locals.game_settings_interface.get() :
|
|
Core::GetBaseSettingsLayer();
|
|
}
|
|
|
|
void FullscreenUI::SetSettingsChanged(SettingsInterface* bsi)
|
|
{
|
|
if (bsi && bsi == s_settings_locals.game_settings_interface.get())
|
|
s_settings_locals.game_settings_changed = true;
|
|
else
|
|
s_settings_locals.settings_changed = true;
|
|
}
|
|
|
|
bool FullscreenUI::GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key,
|
|
bool default_value)
|
|
{
|
|
if (IsEditingGameSettings(bsi))
|
|
{
|
|
std::optional<bool> value = bsi->GetOptionalBoolValue(section, key, std::nullopt);
|
|
if (value.has_value())
|
|
return value.value();
|
|
}
|
|
|
|
return Core::GetBaseSettingsLayer()->GetBoolValue(section, key, default_value);
|
|
}
|
|
|
|
s32 FullscreenUI::GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key,
|
|
s32 default_value)
|
|
{
|
|
if (IsEditingGameSettings(bsi))
|
|
{
|
|
std::optional<s32> value = bsi->GetOptionalIntValue(section, key, std::nullopt);
|
|
if (value.has_value())
|
|
return value.value();
|
|
}
|
|
|
|
return Core::GetBaseSettingsLayer()->GetIntValue(section, key, default_value);
|
|
}
|
|
|
|
u32 FullscreenUI::GetEffectiveUIntSetting(SettingsInterface* bsi, const char* section, const char* key,
|
|
u32 default_value)
|
|
{
|
|
if (IsEditingGameSettings(bsi))
|
|
{
|
|
std::optional<u32> value = bsi->GetOptionalUIntValue(section, key, std::nullopt);
|
|
if (value.has_value())
|
|
return value.value();
|
|
}
|
|
|
|
return Core::GetBaseSettingsLayer()->GetUIntValue(section, key, default_value);
|
|
}
|
|
|
|
float FullscreenUI::GetEffectiveFloatSetting(SettingsInterface* bsi, const char* section, const char* key,
|
|
float default_value)
|
|
{
|
|
if (IsEditingGameSettings(bsi))
|
|
{
|
|
std::optional<float> value = bsi->GetOptionalFloatValue(section, key, std::nullopt);
|
|
if (value.has_value())
|
|
return value.value();
|
|
}
|
|
|
|
return Core::GetBaseSettingsLayer()->GetFloatValue(section, key, default_value);
|
|
}
|
|
|
|
TinyString FullscreenUI::GetEffectiveTinyStringSetting(SettingsInterface* bsi, const char* section, const char* key,
|
|
const char* default_value)
|
|
{
|
|
TinyString ret;
|
|
std::optional<TinyString> value;
|
|
|
|
if (IsEditingGameSettings(bsi))
|
|
value = bsi->GetOptionalTinyStringValue(section, key, std::nullopt);
|
|
|
|
if (value.has_value())
|
|
ret = std::move(value.value());
|
|
else
|
|
ret = Core::GetBaseSettingsLayer()->GetTinyStringValue(section, key, default_value);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
|
|
const char* name, std::string_view display_name, std::string_view icon_name,
|
|
bool show_type)
|
|
{
|
|
if (type == InputBindingInfo::Type::Pointer || type == InputBindingInfo::Type::RelativePointer)
|
|
return;
|
|
|
|
SmallString title;
|
|
SmallString value = bsi->GetSmallStringValue(section, name);
|
|
const bool oneline = value.count('&') <= 1;
|
|
if (oneline && type != InputBindingInfo::Type::Pointer && type != InputBindingInfo::Type::Device)
|
|
InputManager::PrettifyInputBinding(value, &GetControllerIconMapping);
|
|
|
|
if (show_type)
|
|
{
|
|
if (!icon_name.empty())
|
|
{
|
|
title.format("{} {}", icon_name, display_name);
|
|
}
|
|
else
|
|
{
|
|
switch (type)
|
|
{
|
|
case InputBindingInfo::Type::Button:
|
|
title.format(ICON_FA_CIRCLE_DOT " {}", display_name);
|
|
break;
|
|
case InputBindingInfo::Type::Axis:
|
|
case InputBindingInfo::Type::HalfAxis:
|
|
title.format(ICON_FA_BULLSEYE " {}", display_name);
|
|
break;
|
|
case InputBindingInfo::Type::Motor:
|
|
title.format(ICON_FA_BELL " {}", display_name);
|
|
break;
|
|
case InputBindingInfo::Type::LED:
|
|
title.format(ICON_FA_LIGHTBULB " {}", display_name);
|
|
break;
|
|
case InputBindingInfo::Type::Macro:
|
|
title.format(ICON_FA_PIZZA_SLICE " {}", display_name);
|
|
break;
|
|
case InputBindingInfo::Type::Device:
|
|
title.format(ICON_FA_GAMEPAD " {}", display_name);
|
|
break;
|
|
default:
|
|
title = display_name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
title = display_name;
|
|
}
|
|
|
|
title.append_format("##{}/{}", section, name);
|
|
|
|
bool clicked;
|
|
if (oneline)
|
|
{
|
|
if (value.empty())
|
|
value.assign(FSUI_VSTR("-"));
|
|
|
|
clicked = MenuButtonWithValue(title, {}, value);
|
|
}
|
|
else
|
|
{
|
|
clicked = MenuButton(title, value);
|
|
}
|
|
|
|
if (clicked)
|
|
{
|
|
if (type == InputBindingInfo::Type::Motor || type == InputBindingInfo::Type::LED)
|
|
BeginEffectBinding(bsi, type, section, name, display_name);
|
|
else
|
|
s_settings_locals.input_binding_dialog.Start(bsi, type, section, name, display_name);
|
|
}
|
|
else if (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false))
|
|
{
|
|
CancelPendingMenuClose();
|
|
bsi->DeleteValue(section, name);
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
}
|
|
|
|
FullscreenUI::InputBindingDialog::InputBindingDialog() = default;
|
|
|
|
FullscreenUI::InputBindingDialog::~InputBindingDialog() = default;
|
|
|
|
void FullscreenUI::InputBindingDialog::Start(SettingsInterface* bsi, InputBindingInfo::Type type,
|
|
std::string_view section, std::string_view key,
|
|
std::string_view display_name)
|
|
{
|
|
if (m_binding_type != InputBindingInfo::Type::Unknown)
|
|
InputManager::RemoveHook();
|
|
|
|
m_binding_type = type;
|
|
m_binding_section = section;
|
|
m_binding_key = key;
|
|
m_display_name = display_name;
|
|
m_new_bindings = {};
|
|
m_value_ranges = {};
|
|
m_time_remaining = INPUT_BINDING_TIMEOUT_SECONDS;
|
|
SetTitleAndOpen(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Set Input Binding"));
|
|
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
|
|
InputManager::SetHook([this, game_settings](InputBindingKey key, float value) -> InputInterceptHook::CallbackResult {
|
|
// holding the settings lock here will protect the input binding list
|
|
const auto lock = Core::GetSettingsLock();
|
|
|
|
// shouldn't happen, just in case
|
|
if (m_binding_type == InputBindingInfo::Type::Unknown)
|
|
return InputInterceptHook::CallbackResult::RemoveHookAndContinueProcessingEvent;
|
|
|
|
float initial_value = value;
|
|
float min_value = value;
|
|
InputInterceptHook::CallbackResult default_action = InputInterceptHook::CallbackResult::StopProcessingEvent;
|
|
const auto it = std::find_if(m_value_ranges.begin(), m_value_ranges.end(),
|
|
[key](const auto& it) { return it.first.bits == key.bits; });
|
|
|
|
if (it != m_value_ranges.end())
|
|
{
|
|
initial_value = it->second.first;
|
|
min_value = it->second.second = std::min(it->second.second, value);
|
|
}
|
|
else
|
|
{
|
|
m_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value));
|
|
|
|
// forward the event to imgui if it's a new key and a release, because this is what triggered the binding to
|
|
// start if we don't do this, imgui thinks the activate button is held down
|
|
default_action = (value == 0.0f) ? InputInterceptHook::CallbackResult::ContinueProcessingEvent :
|
|
InputInterceptHook::CallbackResult::StopProcessingEvent;
|
|
}
|
|
|
|
const float abs_value = std::abs(value);
|
|
const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis &&
|
|
std::abs(initial_value) > 0.5f && std::abs(initial_value - min_value) > 0.1f);
|
|
|
|
for (InputBindingKey& other_key : m_new_bindings)
|
|
{
|
|
// if this key is in our new binding list, it's a "release", and we're done
|
|
if (other_key.MaskDirection() == key.MaskDirection())
|
|
{
|
|
// for pedals, we wait for it to go back to near its starting point to commit the binding
|
|
if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f)))
|
|
{
|
|
// did we go the full range?
|
|
if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f)
|
|
other_key.modifier = InputModifier::FullAxis;
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
const SmallString new_binding =
|
|
InputManager::ConvertInputBindingKeysToString(m_binding_type, m_new_bindings.data(), m_new_bindings.size());
|
|
bsi->SetStringValue(m_binding_section.c_str(), m_binding_key.c_str(), new_binding.c_str());
|
|
SetSettingsChanged(bsi);
|
|
|
|
// don't try to process any more
|
|
m_binding_type = InputBindingInfo::Type::Unknown;
|
|
GPUThread::RunOnThread([this]() { StartClose(); });
|
|
|
|
return InputInterceptHook::CallbackResult::RemoveHookAndStopProcessingEvent;
|
|
}
|
|
|
|
// otherwise, keep waiting
|
|
return default_action;
|
|
}
|
|
}
|
|
|
|
// new binding, add it to the list, but wait for a decent distance first, and then wait for release
|
|
if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f)))
|
|
{
|
|
InputBindingKey key_to_add = key;
|
|
key_to_add.modifier = (value < 0.0f) ? InputModifier::Negate : InputModifier::None;
|
|
key_to_add.invert = reverse_threshold;
|
|
m_new_bindings.push_back(key_to_add);
|
|
}
|
|
|
|
return default_action;
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::InputBindingDialog::ClearState()
|
|
{
|
|
PopupDialog::ClearState();
|
|
|
|
if (m_binding_type != InputBindingInfo::Type::Unknown)
|
|
InputManager::RemoveHook();
|
|
|
|
m_binding_type = InputBindingInfo::Type::Unknown;
|
|
m_binding_section = {};
|
|
m_binding_key = {};
|
|
m_display_name = {};
|
|
m_new_bindings = {};
|
|
m_value_ranges = {};
|
|
}
|
|
|
|
void FullscreenUI::InputBindingDialog::Draw()
|
|
{
|
|
if (!IsOpen())
|
|
return;
|
|
|
|
if (m_time_remaining > 0.0f)
|
|
{
|
|
m_time_remaining -= ImGui::GetIO().DeltaTime;
|
|
if (m_time_remaining <= 0.0f)
|
|
{
|
|
// allow the dialog to fade out, but stop receiving any more events
|
|
m_time_remaining = 0.0f;
|
|
m_binding_type = InputBindingInfo::Type::Unknown;
|
|
InputManager::RemoveHook();
|
|
StartClose();
|
|
}
|
|
}
|
|
|
|
if (!BeginRender(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(500.0f, 0.0f)))
|
|
{
|
|
ClearState();
|
|
return;
|
|
}
|
|
|
|
ImGui::TextWrapped(
|
|
"%s", SmallString::from_format(FSUI_FSTR("Setting {} binding {}."), m_binding_section, m_display_name).c_str());
|
|
ImGui::TextUnformatted(FSUI_CSTR("Push a controller button or axis now."));
|
|
ImGui::NewLine();
|
|
ImGui::TextUnformatted(SmallString::from_format(FSUI_FSTR("Timing out in {:.0f} seconds..."), m_time_remaining));
|
|
|
|
EndRender();
|
|
}
|
|
|
|
void FullscreenUI::BeginEffectBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section,
|
|
const char* key, std::string_view display_name)
|
|
{
|
|
// vibration motors use a list to select
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
InputManager::DeviceEffectList effects = InputManager::EnumerateDeviceEffects(type);
|
|
if (effects.empty())
|
|
{
|
|
ShowToast(OSDMessageType::Info, {}, FSUI_STR("No devices with vibration motors were detected."));
|
|
return;
|
|
}
|
|
|
|
const TinyString current_binding = bsi->GetTinyStringValue(section, key);
|
|
size_t current_index = effects.size();
|
|
ChoiceDialogOptions options;
|
|
options.reserve(effects.size() + 1);
|
|
for (size_t i = 0; i < effects.size(); i++)
|
|
{
|
|
const TinyString text = InputManager::ConvertInputBindingKeyToString(effects[i].first, effects[i].second);
|
|
const bool this_index = (current_binding.view() == text);
|
|
current_index = this_index ? i : current_index;
|
|
options.emplace_back(text, this_index);
|
|
}
|
|
|
|
// empty/no mapping value
|
|
if (type == InputBindingInfo::Type::Motor)
|
|
options.emplace_back(FSUI_STR("No Vibration"), current_binding.empty());
|
|
else if (type == InputBindingInfo::Type::LED)
|
|
options.emplace_back(FSUI_STR("No LED"), current_binding.empty());
|
|
|
|
// add current value to list if it's not currently available
|
|
if (!current_binding.empty() && current_index == effects.size())
|
|
options.emplace_back(std::make_pair(std::string(current_binding.view()), true));
|
|
|
|
OpenChoiceDialog(display_name, false, std::move(options),
|
|
[game_settings, section = std::string(section), key = std::string(key),
|
|
effects = std::move(effects)](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (static_cast<size_t>(index) == effects.size())
|
|
bsi->DeleteValue(section.c_str(), key.c_str());
|
|
else
|
|
bsi->SetStringValue(section.c_str(), key.c_str(), title.c_str());
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
|
|
bool FullscreenUI::DrawToggleSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, bool default_value,
|
|
bool enabled /* = true */, bool allow_tristate /* = true */)
|
|
{
|
|
if (!allow_tristate || !IsEditingGameSettings(bsi))
|
|
{
|
|
bool value = bsi->GetBoolValue(section, key, default_value);
|
|
if (!ToggleButton(title, summary, &value, enabled))
|
|
return false;
|
|
|
|
bsi->SetBoolValue(section, key, value);
|
|
}
|
|
else
|
|
{
|
|
std::optional<bool> value(false);
|
|
if (!bsi->GetBoolValue(section, key, &value.value()))
|
|
value.reset();
|
|
if (!ThreeWayToggleButton(title, summary, &value, enabled))
|
|
return false;
|
|
|
|
if (value.has_value())
|
|
bsi->SetBoolValue(section, key, value.value());
|
|
else
|
|
bsi->DeleteValue(section, key);
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
return true;
|
|
}
|
|
|
|
void FullscreenUI::DrawIntListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value,
|
|
std::span<const char* const> options, bool translate_options /* = true */,
|
|
int option_offset /* = 0 */, bool enabled /* = true */,
|
|
std::string_view tr_context /* = TR_CONTEXT */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
|
|
const std::optional<int> value =
|
|
bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(default_value));
|
|
const int index = value.has_value() ? (value.value() - option_offset) : std::numeric_limits<int>::min();
|
|
const std::string_view value_text =
|
|
(value.has_value()) ?
|
|
((index < 0 || static_cast<size_t>(index) >= options.size()) ?
|
|
FSUI_VSTR("Unknown") :
|
|
(translate_options ? Host::TranslateToStringView(tr_context, options[index]) : options[index])) :
|
|
FSUI_VSTR("Use Global Setting");
|
|
|
|
if (MenuButtonWithValue(title, summary, value_text, enabled))
|
|
{
|
|
ChoiceDialogOptions cd_options;
|
|
cd_options.reserve(options.size() + 1);
|
|
if (game_settings)
|
|
cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value());
|
|
for (size_t i = 0; i < options.size(); i++)
|
|
{
|
|
cd_options.emplace_back(translate_options ? Host::TranslateToString(tr_context, options[i]) :
|
|
std::string(options[i]),
|
|
(i == static_cast<size_t>(index)));
|
|
}
|
|
OpenChoiceDialog(title, false, std::move(cd_options),
|
|
[game_settings, section = TinyString(section), key = TinyString(key),
|
|
option_offset](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings)
|
|
{
|
|
if (index == 0)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetIntValue(section, key, index - 1 + option_offset);
|
|
}
|
|
else
|
|
{
|
|
bsi->SetIntValue(section, key, index + option_offset);
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
}
|
|
|
|
void FullscreenUI::DrawIntListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value,
|
|
std::span<const char* const> options, bool translate_options,
|
|
std::span<const int> values, bool enabled /* = true */,
|
|
std::string_view tr_context /* = TR_CONTEXT */)
|
|
{
|
|
static constexpr auto value_to_index = [](s32 value, const std::span<const int> values) {
|
|
for (size_t i = 0; i < values.size(); i++)
|
|
{
|
|
if (values[i] == value)
|
|
return static_cast<int>(i);
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
DebugAssert(options.size() == values.size());
|
|
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
|
|
const std::optional<int> value =
|
|
bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(default_value));
|
|
const int index = value.has_value() ? value_to_index(value.value(), values) : -1;
|
|
const std::string_view value_text =
|
|
(value.has_value()) ?
|
|
((index < 0 || static_cast<size_t>(index) >= options.size()) ?
|
|
FSUI_VSTR("Unknown") :
|
|
(translate_options ? Host::TranslateToStringView(tr_context, options[index]) : options[index])) :
|
|
FSUI_VSTR("Use Global Setting");
|
|
|
|
if (MenuButtonWithValue(title, summary, value_text, enabled))
|
|
{
|
|
ChoiceDialogOptions cd_options;
|
|
cd_options.reserve(options.size() + 1);
|
|
if (game_settings)
|
|
cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value());
|
|
for (size_t i = 0; i < options.size(); i++)
|
|
{
|
|
cd_options.emplace_back(translate_options ? Host::TranslateToString(tr_context, options[i]) :
|
|
std::string(options[i]),
|
|
(i == static_cast<size_t>(index)));
|
|
}
|
|
OpenChoiceDialog(title, false, std::move(cd_options),
|
|
[game_settings, section = TinyString(section), key = TinyString(key),
|
|
values](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings)
|
|
{
|
|
if (index == 0)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetIntValue(section, key, values[index - 1]);
|
|
}
|
|
else
|
|
{
|
|
bsi->SetIntValue(section, key, values[index]);
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
}
|
|
|
|
void FullscreenUI::DrawIntRangeSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value, int min_value,
|
|
int max_value, const char* format /* = "%d" */, bool enabled /* = true */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<int> value =
|
|
bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(default_value));
|
|
SmallString sstr =
|
|
value.has_value() ? SmallString::from_sprintf(format, value.value()) : SmallString(FSUI_VSTR("Use Global Setting"));
|
|
|
|
const bool pressed = MenuButtonWithValue(title, summary, sstr, enabled);
|
|
|
|
// use setting key to avoid id conflicts
|
|
sstr.format("{}##{}-intrange", title, key);
|
|
if (pressed)
|
|
OpenFixedPopupDialog(sstr);
|
|
|
|
if (!IsFixedPopupDialogOpen(sstr) ||
|
|
!BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(600.0f, 0.0f)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight);
|
|
TextAlignedMultiLine(0.0f, IMSTR_START_END(summary));
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Value Range"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, min_value);
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted(" - ");
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, max_value);
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Default Value"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, default_value);
|
|
ImGui::PopFont();
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, LayoutScale(15.0f));
|
|
|
|
ImGui::SetNextItemWidth(ImGui::GetCurrentWindow()->WorkRect.GetWidth());
|
|
s32 dlg_value = static_cast<s32>(value.value_or(default_value));
|
|
if (ImGui::SliderInt("##value", &dlg_value, min_value, max_value, format, ImGuiSliderFlags_NoInput))
|
|
{
|
|
if (IsEditingGameSettings(bsi) && dlg_value == default_value)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetIntValue(section, key, dlg_value);
|
|
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
|
|
ImGui::PopStyleVar(4);
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
|
|
void FullscreenUI::DrawFloatRangeSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, float default_value, float min_value,
|
|
float max_value, const char* format /* = "%f" */,
|
|
float multiplier /* = 1.0f */, bool enabled /* = true */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<float> value =
|
|
bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional<float>(default_value));
|
|
SmallString sstr = value.has_value() ? SmallString::from_sprintf(format, value.value() * multiplier) :
|
|
SmallString(FSUI_VSTR("Use Global Setting"));
|
|
|
|
const bool pressed = MenuButtonWithValue(title, summary, sstr, enabled);
|
|
|
|
// use setting key to avoid id conflicts
|
|
sstr.format("{}##{}-floatrange", title, key);
|
|
if (pressed)
|
|
OpenFixedPopupDialog(sstr);
|
|
|
|
if (!IsFixedPopupDialogOpen(sstr) ||
|
|
!BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(600.0f, 0.0f)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight);
|
|
TextAlignedMultiLine(0.0f, IMSTR_START_END(summary));
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Value Range"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, min_value * multiplier);
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted(" - ");
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, max_value * multiplier);
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Default Value"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, default_value * multiplier);
|
|
ImGui::PopFont();
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, LayoutScale(15.0f));
|
|
|
|
const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth();
|
|
ImGui::SetNextItemWidth(end);
|
|
float dlg_value = value.value_or(default_value) * multiplier;
|
|
if (ImGui::SliderFloat("##value", &dlg_value, min_value * multiplier, max_value * multiplier, format,
|
|
ImGuiSliderFlags_NoInput))
|
|
{
|
|
dlg_value /= multiplier;
|
|
|
|
if (IsEditingGameSettings(bsi) && dlg_value == default_value)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetFloatValue(section, key, dlg_value);
|
|
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
|
|
ImGui::PopStyleVar(4);
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
|
|
void FullscreenUI::DrawFloatSpinBoxSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, float default_value, float min_value,
|
|
float max_value, float step_value, float multiplier,
|
|
const char* format /* = "%f" */, bool enabled /* = true */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<float> value =
|
|
bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional<float>(default_value));
|
|
SmallString sstr = value.has_value() ? SmallString::from_sprintf(format, value.value() * multiplier) :
|
|
SmallString(FSUI_VSTR("Use Global Setting"));
|
|
|
|
static bool manual_input = false;
|
|
|
|
const bool pressed = MenuButtonWithValue(title, summary, sstr, enabled);
|
|
|
|
// use setting key to avoid id conflicts
|
|
sstr.format("{}##{}-floatspin", title, key);
|
|
if (pressed)
|
|
{
|
|
OpenFixedPopupDialog(sstr);
|
|
manual_input = false;
|
|
}
|
|
|
|
if (!IsFixedPopupDialogOpen(sstr) ||
|
|
!BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(650.0f, 0.0f)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight);
|
|
TextAlignedMultiLine(0.0f, IMSTR_START_END(summary));
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Value Range"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, min_value * multiplier);
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted(" - ");
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, max_value * multiplier);
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Default Value"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, default_value * multiplier);
|
|
ImGui::PopFont();
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
float dlg_value = value.value_or(default_value) * multiplier;
|
|
bool dlg_value_changed = false;
|
|
|
|
char str_value[32];
|
|
std::snprintf(str_value, std::size(str_value), format, dlg_value);
|
|
|
|
if (manual_input)
|
|
{
|
|
const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth();
|
|
ImGui::SetNextItemWidth(end);
|
|
|
|
// round trip to drop any suffixes (e.g. percent)
|
|
if (auto tmp_value = StringUtil::FromChars<float>(str_value); tmp_value.has_value())
|
|
{
|
|
std::snprintf(str_value, std::size(str_value),
|
|
((tmp_value.value() - std::floor(tmp_value.value())) < 0.01f) ? "%.0f" : "%f", tmp_value.value());
|
|
}
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
|
|
if (ImGui::InputText("##value", str_value, std::size(str_value), ImGuiInputTextFlags_CharsDecimal))
|
|
{
|
|
dlg_value = StringUtil::FromChars<float>(str_value).value_or(dlg_value);
|
|
dlg_value_changed = true;
|
|
}
|
|
|
|
ImGui::PopStyleVar(2);
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
}
|
|
else
|
|
{
|
|
BeginHorizontalMenuButtons(5);
|
|
HorizontalMenuButton(str_value, false);
|
|
|
|
float step = 0;
|
|
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
|
|
if (HorizontalMenuButton(ICON_FA_CHEVRON_UP))
|
|
step = step_value;
|
|
if (HorizontalMenuButton(ICON_FA_CHEVRON_DOWN))
|
|
step = -step_value;
|
|
ImGui::PopItemFlag();
|
|
if (HorizontalMenuButton(ICON_FA_KEYBOARD))
|
|
manual_input = true;
|
|
if (HorizontalMenuButton(ICON_FA_ARROW_ROTATE_LEFT))
|
|
{
|
|
dlg_value = default_value * multiplier;
|
|
dlg_value_changed = true;
|
|
}
|
|
|
|
EndHorizontalMenuButtons(10.0f);
|
|
|
|
if (step != 0)
|
|
{
|
|
dlg_value += step * multiplier;
|
|
dlg_value_changed = true;
|
|
}
|
|
}
|
|
|
|
if (dlg_value_changed)
|
|
{
|
|
dlg_value = std::clamp(dlg_value / multiplier, min_value, max_value);
|
|
if (IsEditingGameSettings(bsi) && dlg_value == default_value)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetFloatValue(section, key, dlg_value);
|
|
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
|
|
bool FullscreenUI::DrawIntRectSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* left_key, int default_left, const char* top_key,
|
|
int default_top, const char* right_key, int default_right, const char* bottom_key,
|
|
int default_bottom, int min_value, int max_value, const char* format /* = "%d" */,
|
|
bool enabled /* = true */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<int> left_value =
|
|
bsi->GetOptionalIntValue(section, left_key, game_settings ? std::nullopt : std::optional<int>(default_left));
|
|
const std::optional<int> top_value =
|
|
bsi->GetOptionalIntValue(section, top_key, game_settings ? std::nullopt : std::optional<int>(default_top));
|
|
const std::optional<int> right_value =
|
|
bsi->GetOptionalIntValue(section, right_key, game_settings ? std::nullopt : std::optional<int>(default_right));
|
|
const std::optional<int> bottom_value =
|
|
bsi->GetOptionalIntValue(section, bottom_key, game_settings ? std::nullopt : std::optional<int>(default_bottom));
|
|
SmallString sstr = SmallString::from_format(
|
|
"{}/{}/{}/{}",
|
|
left_value.has_value() ? TinyString::from_sprintf(format, left_value.value()) : TinyString(FSUI_VSTR("Default")),
|
|
top_value.has_value() ? TinyString::from_sprintf(format, top_value.value()) : TinyString(FSUI_VSTR("Default")),
|
|
right_value.has_value() ? TinyString::from_sprintf(format, right_value.value()) : TinyString(FSUI_VSTR("Default")),
|
|
bottom_value.has_value() ? TinyString::from_sprintf(format, bottom_value.value()) :
|
|
TinyString(FSUI_VSTR("Default")));
|
|
|
|
const bool pressed = MenuButtonWithValue(title, summary, sstr, enabled);
|
|
|
|
// use setting key to avoid id conflicts
|
|
sstr.format("{}##{}-intrect", title, left_key);
|
|
if (pressed)
|
|
OpenFixedPopupDialog(sstr);
|
|
|
|
if (!IsFixedPopupDialogOpen(sstr) ||
|
|
!BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
ImVec2(LayoutScale(500.0f), 0.0f)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
s32 dlg_left_value = static_cast<s32>(left_value.value_or(default_left));
|
|
s32 dlg_top_value = static_cast<s32>(top_value.value_or(default_top));
|
|
s32 dlg_right_value = static_cast<s32>(right_value.value_or(default_right));
|
|
s32 dlg_bottom_value = static_cast<s32>(bottom_value.value_or(default_bottom));
|
|
bool changed = false;
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, LayoutScale(15.0f));
|
|
|
|
const float midpoint = LayoutScale(150.0f);
|
|
const float end = (ImGui::GetCurrentWindow()->WorkRect.GetWidth() - midpoint) + ImGui::GetStyle().WindowPadding.x;
|
|
ImGui::PushFont(nullptr, 0.0f, UIStyle.BoldFontWeight);
|
|
ImGui::TextUnformatted(IMSTR_START_END(FSUI_VSTR("Left: ")));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine(midpoint);
|
|
ImGui::SetNextItemWidth(end);
|
|
const bool left_modified =
|
|
ImGui::SliderInt("##left", &dlg_left_value, min_value, max_value, format, ImGuiSliderFlags_NoInput);
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
ImGui::PushFont(nullptr, 0.0f, UIStyle.BoldFontWeight);
|
|
ImGui::TextUnformatted(IMSTR_START_END(FSUI_VSTR("Top: ")));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine(midpoint);
|
|
ImGui::SetNextItemWidth(end);
|
|
const bool top_modified =
|
|
ImGui::SliderInt("##top", &dlg_top_value, min_value, max_value, format, ImGuiSliderFlags_NoInput);
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
ImGui::PushFont(nullptr, 0.0f, UIStyle.BoldFontWeight);
|
|
ImGui::TextUnformatted(IMSTR_START_END(FSUI_VSTR("Right: ")));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine(midpoint);
|
|
ImGui::SetNextItemWidth(end);
|
|
const bool right_modified =
|
|
ImGui::SliderInt("##right", &dlg_right_value, min_value, max_value, format, ImGuiSliderFlags_NoInput);
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
ImGui::PushFont(nullptr, 0.0f, UIStyle.BoldFontWeight);
|
|
ImGui::TextUnformatted(IMSTR_START_END(FSUI_VSTR("Bottom: ")));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine(midpoint);
|
|
ImGui::SetNextItemWidth(end);
|
|
const bool bottom_modified =
|
|
ImGui::SliderInt("##bottom", &dlg_bottom_value, min_value, max_value, format, ImGuiSliderFlags_NoInput);
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
if (left_modified)
|
|
{
|
|
if (IsEditingGameSettings(bsi) && dlg_left_value == default_left)
|
|
bsi->DeleteValue(section, left_key);
|
|
else
|
|
bsi->SetIntValue(section, left_key, dlg_left_value);
|
|
}
|
|
if (top_modified)
|
|
{
|
|
if (IsEditingGameSettings(bsi) && dlg_top_value == default_top)
|
|
bsi->DeleteValue(section, top_key);
|
|
else
|
|
bsi->SetIntValue(section, top_key, dlg_top_value);
|
|
}
|
|
if (right_modified)
|
|
{
|
|
if (IsEditingGameSettings(bsi) && dlg_right_value == default_right)
|
|
bsi->DeleteValue(section, right_key);
|
|
else
|
|
bsi->SetIntValue(section, right_key, dlg_right_value);
|
|
}
|
|
if (bottom_modified)
|
|
{
|
|
if (IsEditingGameSettings(bsi) && dlg_bottom_value == default_bottom)
|
|
bsi->DeleteValue(section, bottom_key);
|
|
else
|
|
bsi->SetIntValue(section, bottom_key, dlg_bottom_value);
|
|
}
|
|
|
|
changed = (left_modified || top_modified || right_modified || bottom_modified);
|
|
if (changed)
|
|
SetSettingsChanged(bsi);
|
|
|
|
ImGui::PopStyleVar(4);
|
|
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
|
|
return changed;
|
|
}
|
|
|
|
void FullscreenUI::DrawIntSpinBoxSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, int default_value, int min_value,
|
|
int max_value, int step_value, const char* format /* = "%d" */,
|
|
bool enabled /* = true */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<int> value =
|
|
bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(default_value));
|
|
TinyString sstr;
|
|
if (value.has_value())
|
|
sstr.sprintf(format, value.value());
|
|
else
|
|
sstr = FSUI_VSTR("Use Global Setting");
|
|
|
|
static bool manual_input = false;
|
|
|
|
const bool pressed = MenuButtonWithValue(title, summary, sstr, enabled);
|
|
|
|
// use setting key to avoid id conflicts
|
|
sstr.format("{}##{}-intspin", title, key);
|
|
if (pressed)
|
|
{
|
|
OpenFixedPopupDialog(sstr);
|
|
manual_input = false;
|
|
}
|
|
|
|
if (!IsFixedPopupDialogOpen(sstr) ||
|
|
!BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(650.0f, 0.0f)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight);
|
|
TextAlignedMultiLine(0.0f, IMSTR_START_END(summary));
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Value Range"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, min_value);
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted(" - ");
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, max_value);
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.BoldFontWeight);
|
|
ImGui::Text("%s: ", FSUI_CSTR("Default Value"));
|
|
ImGui::PopFont();
|
|
ImGui::SameLine();
|
|
ImGui::Text(format, default_value);
|
|
ImGui::PopFont();
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
s32 dlg_value = static_cast<s32>(value.value_or(default_value));
|
|
bool dlg_value_changed = false;
|
|
|
|
char str_value[32];
|
|
std::snprintf(str_value, std::size(str_value), format, dlg_value);
|
|
|
|
if (manual_input)
|
|
{
|
|
const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth();
|
|
ImGui::SetNextItemWidth(end);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
|
|
std::snprintf(str_value, std::size(str_value), "%d", dlg_value);
|
|
if (ImGui::InputText("##value", str_value, std::size(str_value), ImGuiInputTextFlags_CharsDecimal))
|
|
{
|
|
dlg_value = StringUtil::FromChars<s32>(str_value).value_or(dlg_value);
|
|
dlg_value_changed = true;
|
|
}
|
|
|
|
ImGui::PopStyleVar(2);
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
}
|
|
else
|
|
{
|
|
BeginHorizontalMenuButtons(5);
|
|
HorizontalMenuButton(str_value, false);
|
|
|
|
s32 step = 0;
|
|
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
|
|
if (HorizontalMenuButton(ICON_FA_CHEVRON_UP))
|
|
step = step_value;
|
|
if (HorizontalMenuButton(ICON_FA_CHEVRON_DOWN))
|
|
step = -step_value;
|
|
ImGui::PopItemFlag();
|
|
if (HorizontalMenuButton(ICON_FA_KEYBOARD))
|
|
manual_input = true;
|
|
if (HorizontalMenuButton(ICON_FA_ARROW_ROTATE_LEFT))
|
|
{
|
|
dlg_value = default_value;
|
|
dlg_value_changed = true;
|
|
}
|
|
|
|
EndHorizontalMenuButtons(10.0f);
|
|
|
|
if (step != 0)
|
|
{
|
|
dlg_value += step;
|
|
dlg_value_changed = true;
|
|
}
|
|
}
|
|
|
|
if (dlg_value_changed)
|
|
{
|
|
dlg_value = std::clamp(dlg_value, min_value, max_value);
|
|
if (IsEditingGameSettings(bsi) && dlg_value == default_value)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetIntValue(section, key, dlg_value);
|
|
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
|
|
void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, const char* default_value,
|
|
std::span<const char* const> options,
|
|
std::span<const char* const> option_values, bool enabled /* = true */,
|
|
void (*changed_callback)(std::string_view) /* = nullptr */,
|
|
std::string_view tr_context /* = TR_CONTEXT */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<SmallString> value(bsi->GetOptionalSmallStringValue(
|
|
section, key, game_settings ? std::nullopt : std::optional<const char*>(default_value)));
|
|
|
|
DebugAssert(options.size() == option_values.size());
|
|
|
|
size_t index = options.size();
|
|
if (value.has_value())
|
|
{
|
|
for (size_t i = 0; i < options.size(); i++)
|
|
{
|
|
if (value == option_values[i])
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MenuButtonWithValue(title, summary,
|
|
value.has_value() ? ((index < options.size()) ? TRANSLATE_SV(tr_context, options[index]) :
|
|
FSUI_VSTR("Unknown")) :
|
|
FSUI_VSTR("Use Global Setting"),
|
|
enabled))
|
|
{
|
|
ChoiceDialogOptions cd_options;
|
|
cd_options.reserve(options.size() + 1);
|
|
if (game_settings)
|
|
cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value());
|
|
for (size_t i = 0; i < options.size(); i++)
|
|
{
|
|
cd_options.emplace_back(TRANSLATE_STR(tr_context, options[i]),
|
|
(value.has_value() && i == static_cast<size_t>(index)));
|
|
}
|
|
OpenChoiceDialog(title, false, std::move(cd_options),
|
|
[game_settings, section, key, default_value, option_values,
|
|
changed_callback](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings)
|
|
{
|
|
if (index == 0)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetStringValue(section, key, option_values[index - 1]);
|
|
|
|
if (changed_callback)
|
|
changed_callback(Core::GetStringSettingValue(section, key, default_value));
|
|
}
|
|
else
|
|
{
|
|
bsi->SetStringValue(section, key, option_values[index]);
|
|
|
|
if (changed_callback)
|
|
changed_callback(option_values[index]);
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
}
|
|
|
|
template<typename DataType, typename SizeType>
|
|
void FullscreenUI::DrawEnumSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, DataType default_value,
|
|
std::optional<DataType> (*from_string_function)(const char* str),
|
|
const char* (*to_string_function)(DataType value),
|
|
const char* (*to_display_string_function)(DataType value), SizeType option_count,
|
|
bool enabled /* = true */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<SmallString> value(bsi->GetOptionalSmallStringValue(
|
|
section, key, game_settings ? std::nullopt : std::optional<const char*>(to_string_function(default_value))));
|
|
|
|
const std::optional<DataType> typed_value(value.has_value() ? from_string_function(value->c_str()) : std::nullopt);
|
|
|
|
if (MenuButtonWithValue(title, summary,
|
|
typed_value.has_value() ? to_display_string_function(typed_value.value()) :
|
|
FSUI_CSTR("Use Global Setting"),
|
|
enabled))
|
|
{
|
|
ChoiceDialogOptions cd_options;
|
|
cd_options.reserve(static_cast<u32>(option_count) + 1);
|
|
if (game_settings)
|
|
cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value());
|
|
for (u32 i = 0; i < static_cast<u32>(option_count); i++)
|
|
cd_options.emplace_back(to_display_string_function(static_cast<DataType>(i)),
|
|
(typed_value.has_value() && i == static_cast<u32>(typed_value.value())));
|
|
OpenChoiceDialog(title, false, std::move(cd_options),
|
|
[section = TinyString(section), key = TinyString(key), to_string_function,
|
|
game_settings](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings)
|
|
{
|
|
if (index == 0)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetStringValue(section, key, to_string_function(static_cast<DataType>(index - 1)));
|
|
}
|
|
else
|
|
{
|
|
bsi->SetStringValue(section, key, to_string_function(static_cast<DataType>(index)));
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
}
|
|
void FullscreenUI::DrawFloatListSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary,
|
|
const char* section, const char* key, float default_value,
|
|
const char* const* options, const float* option_values, size_t option_count,
|
|
bool translate_options, bool enabled /* = true */)
|
|
{
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const std::optional<float> value(
|
|
bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional<float>(default_value)));
|
|
|
|
if (option_count == 0)
|
|
{
|
|
// select from null entry
|
|
while (options && options[option_count] != nullptr)
|
|
option_count++;
|
|
}
|
|
|
|
size_t index = option_count;
|
|
if (value.has_value())
|
|
{
|
|
for (size_t i = 0; i < option_count; i++)
|
|
{
|
|
if (value == option_values[i])
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MenuButtonWithValue(
|
|
title, summary,
|
|
value.has_value() ?
|
|
((index < option_count) ?
|
|
(translate_options ? Host::TranslateToStringView(FSUI_TR_CONTEXT, options[index]) : options[index]) :
|
|
FSUI_VSTR("Unknown")) :
|
|
FSUI_VSTR("Use Global Setting"),
|
|
enabled))
|
|
{
|
|
ChoiceDialogOptions cd_options;
|
|
cd_options.reserve(option_count + 1);
|
|
if (game_settings)
|
|
cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value());
|
|
for (size_t i = 0; i < option_count; i++)
|
|
{
|
|
cd_options.emplace_back(translate_options ? Host::TranslateToString(FSUI_TR_CONTEXT, options[i]) :
|
|
std::string(options[i]),
|
|
(value.has_value() && i == static_cast<size_t>(index)));
|
|
}
|
|
OpenChoiceDialog(title, false, std::move(cd_options),
|
|
[game_settings, section = TinyString(section), key = TinyString(key),
|
|
option_values](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings)
|
|
{
|
|
if (index == 0)
|
|
bsi->DeleteValue(section, key);
|
|
else
|
|
bsi->SetFloatValue(section, key, option_values[index - 1]);
|
|
}
|
|
else
|
|
{
|
|
bsi->SetFloatValue(section, key, option_values[index]);
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
}
|
|
|
|
void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, std::string_view title, const char* section,
|
|
const char* key, const std::string& runtime_var)
|
|
{
|
|
if (MenuButton(title, runtime_var))
|
|
{
|
|
OpenFileSelector(title, true,
|
|
[game_settings = IsEditingGameSettings(bsi), section = TinyString(section),
|
|
key = TinyString(key)](const std::string& dir) {
|
|
if (dir.empty())
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
std::string relative_path(Path::MakeRelative(dir, EmuFolders::DataRoot));
|
|
bsi->SetStringValue(section.c_str(), key.c_str(), relative_path.c_str());
|
|
SetSettingsChanged(bsi);
|
|
|
|
Host::RunOnCoreThread(&EmuFolders::Update);
|
|
if (key == "Covers")
|
|
RemoveCoverCacheEntry({});
|
|
});
|
|
}
|
|
}
|
|
|
|
void FullscreenUI::StartAutomaticBindingForPort(u32 port)
|
|
{
|
|
InputManager::DeviceList devices = InputManager::EnumerateDevices();
|
|
if (devices.empty())
|
|
{
|
|
ShowToast(OSDMessageType::Info, {}, FSUI_STR("Automatic mapping failed, no devices are available."));
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> names;
|
|
ChoiceDialogOptions options;
|
|
options.reserve(devices.size());
|
|
names.reserve(devices.size());
|
|
for (auto& [key, name, display_name] : devices)
|
|
{
|
|
names.push_back(std::move(name));
|
|
options.emplace_back(std::move(display_name), false);
|
|
}
|
|
OpenChoiceDialog(FSUI_STR("Select Device"), false, std::move(options),
|
|
[port, names = std::move(names)](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const std::string& name = names[index];
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
const bool result =
|
|
InputManager::MapController(*bsi, port, InputManager::GetGenericBindingMapping(name), true);
|
|
SetSettingsChanged(bsi);
|
|
|
|
// and the toast needs to happen on the UI thread.
|
|
ShowToast(result ? OSDMessageType::Quick : OSDMessageType::Info, {},
|
|
result ? fmt::format(FSUI_FSTR("Automatic mapping completed for {}."), name) :
|
|
fmt::format(FSUI_FSTR("Automatic mapping failed for {}."), name));
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::StartClearBindingsForPort(u32 port)
|
|
{
|
|
OpenConfirmMessageDialog(
|
|
FSUI_STR("Clear Mappings"),
|
|
FSUI_STR("Are you sure you want to clear all mappings for this controller?\n\nYou cannot undo this action."),
|
|
[port](bool result) {
|
|
if (!result)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
InputManager::ClearPortBindings(*bsi, port);
|
|
ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Controller mapping cleared."));
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::PopulateHotkeyList()
|
|
{
|
|
if (!s_settings_locals.hotkey_list_cache.empty())
|
|
return;
|
|
|
|
// sort hotkeys by category so we don't duplicate the groups
|
|
const auto hotkeys = Core::GetHotkeyList();
|
|
s_settings_locals.hotkey_list_cache.reserve(hotkeys.size());
|
|
|
|
// this mess is needed to preserve the category order
|
|
for (const HotkeyInfo& hk : hotkeys)
|
|
{
|
|
size_t j;
|
|
for (j = 0; j < s_settings_locals.hotkey_list_cache.size(); j++)
|
|
{
|
|
if (std::strcmp(hk.category, s_settings_locals.hotkey_list_cache[j]->category) == 0)
|
|
break;
|
|
}
|
|
if (j != s_settings_locals.hotkey_list_cache.size())
|
|
{
|
|
// already done
|
|
continue;
|
|
}
|
|
|
|
// add all hotkeys with this category
|
|
for (const HotkeyInfo& other_hk : hotkeys)
|
|
{
|
|
if (std::strcmp(hk.category, other_hk.category) != 0)
|
|
continue;
|
|
|
|
s_settings_locals.hotkey_list_cache.push_back(&other_hk);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FullscreenUI::ClearSettingsState()
|
|
{
|
|
s_settings_locals.input_binding_dialog.ClearState();
|
|
s_settings_locals.game_list_directories_cache = {};
|
|
s_settings_locals.game_settings_entry.reset();
|
|
s_settings_locals.game_settings_interface.reset();
|
|
s_settings_locals.game_settings_changed = false;
|
|
s_settings_locals.game_patch_list = {};
|
|
s_settings_locals.enabled_game_patch_cache = {};
|
|
s_settings_locals.game_cheats_list = {};
|
|
s_settings_locals.enabled_game_cheat_cache = {};
|
|
s_settings_locals.game_cheat_groups = {};
|
|
s_settings_locals.postprocessing_stages = {};
|
|
s_settings_locals.fullscreen_mode_list_cache = {};
|
|
s_settings_locals.graphics_adapter_list_cache = {};
|
|
s_settings_locals.hotkey_list_cache = {};
|
|
}
|
|
|
|
void FullscreenUI::SwitchToSettings()
|
|
{
|
|
s_settings_locals.game_settings_entry.reset();
|
|
s_settings_locals.game_settings_interface.reset();
|
|
s_settings_locals.settings_changed = false;
|
|
s_settings_locals.game_patch_list = {};
|
|
s_settings_locals.enabled_game_patch_cache = {};
|
|
s_settings_locals.game_cheats_list = {};
|
|
s_settings_locals.enabled_game_cheat_cache = {};
|
|
s_settings_locals.game_cheat_groups = {};
|
|
s_settings_locals.selected_controller_port = -1;
|
|
|
|
PopulateGraphicsAdapterList();
|
|
PopulateHotkeyList();
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
const SettingsInterface* const sif = GetEditingSettingsInterface();
|
|
PopulateGameListDirectoryCache(*sif);
|
|
PopulatePostProcessingChain(*sif, PostProcessing::Config::DISPLAY_CHAIN_SECTION);
|
|
|
|
SwitchToMainWindow(MainWindowType::Settings);
|
|
s_settings_locals.settings_page = SettingsPage::Interface;
|
|
s_settings_locals.settings_last_bg_alpha = GetBackgroundAlpha();
|
|
}
|
|
|
|
bool FullscreenUI::SwitchToGameSettings(SettingsPage page)
|
|
{
|
|
return SwitchToGameSettingsForPath(GPUThread::GetGamePath());
|
|
}
|
|
|
|
bool FullscreenUI::SwitchToGameSettingsForPath(const std::string& path, SettingsPage page)
|
|
{
|
|
auto lock = GameList::GetLock();
|
|
const GameList::Entry* entry = !path.empty() ? GameList::GetEntryForPath(path) : nullptr;
|
|
|
|
// playlists will always contain the first disc's serial, so use the current game instead
|
|
if (!entry || entry->serial.empty() || entry->type == GameList::EntryType::Playlist)
|
|
{
|
|
Host::RunOnCoreThread([page]() {
|
|
Error error;
|
|
GameList::Entry dynamic_entry;
|
|
if (System::PopulateGameListEntryFromCurrentGame(&dynamic_entry, &error))
|
|
{
|
|
GPUThread::RunOnThread([dynamic_entry = std::move(dynamic_entry), page]() {
|
|
if (IsInitialized())
|
|
SwitchToGameSettings(&dynamic_entry, page);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
ShowToast(OSDMessageType::Info, {}, error.TakeDescription());
|
|
}
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
SwitchToGameSettings(entry, page);
|
|
return true;
|
|
}
|
|
|
|
void FullscreenUI::SwitchToGameSettings(const GameList::Entry* entry, SettingsPage page)
|
|
{
|
|
s_settings_locals.game_settings_entry = std::make_unique<GameList::Entry>(*entry);
|
|
s_settings_locals.game_settings_interface = System::GetGameSettingsInterface(
|
|
s_settings_locals.game_settings_entry->dbentry, s_settings_locals.game_settings_entry->serial, true, false);
|
|
PopulatePatchesAndCheatsList();
|
|
s_settings_locals.settings_page = page;
|
|
s_settings_locals.selected_controller_port = -1;
|
|
SwitchToMainWindow(MainWindowType::Settings);
|
|
}
|
|
|
|
void FullscreenUI::PopulateGraphicsAdapterList()
|
|
{
|
|
GPURenderer renderer;
|
|
const auto lock = Core::GetSettingsLock();
|
|
{
|
|
renderer = Settings::ParseRendererName(
|
|
GetEffectiveTinyStringSetting(GetEditingSettingsInterface(false), "GPU", "Renderer").c_str())
|
|
.value_or(Settings::DEFAULT_GPU_RENDERER);
|
|
}
|
|
|
|
Error error;
|
|
std::optional<GPUDevice::AdapterInfoList> adapter_list = GPUDevice::GetAdapterListForAPI(
|
|
Settings::GetRenderAPIForRenderer(renderer),
|
|
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWindowInfo().type :
|
|
WindowInfoType::Surfaceless,
|
|
&error);
|
|
if (adapter_list.has_value())
|
|
{
|
|
s_settings_locals.graphics_adapter_list_cache = std::move(adapter_list.value());
|
|
}
|
|
else
|
|
{
|
|
ERROR_LOG("Failed to enumerate graphics adapters: {}", error.GetDescription());
|
|
s_settings_locals.graphics_adapter_list_cache = {};
|
|
}
|
|
}
|
|
|
|
void FullscreenUI::PopulateGameListDirectoryCache(const SettingsInterface& si)
|
|
{
|
|
s_settings_locals.game_list_directories_cache.clear();
|
|
for (std::string& dir : si.GetStringList("GameList", "Paths"))
|
|
s_settings_locals.game_list_directories_cache.emplace_back(std::move(dir), false);
|
|
for (std::string& dir : si.GetStringList("GameList", "RecursivePaths"))
|
|
s_settings_locals.game_list_directories_cache.emplace_back(std::move(dir), true);
|
|
}
|
|
|
|
void FullscreenUI::PopulatePatchesAndCheatsList()
|
|
{
|
|
s_settings_locals.game_patch_list = Cheats::GetCodeInfoList(
|
|
s_settings_locals.game_settings_entry->serial, s_settings_locals.game_settings_entry->hash, false, true, true);
|
|
s_settings_locals.game_cheats_list = Cheats::GetCodeInfoList(
|
|
s_settings_locals.game_settings_entry->serial, s_settings_locals.game_settings_entry->hash, true,
|
|
s_settings_locals.game_settings_interface->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true),
|
|
s_settings_locals.game_settings_interface->GetBoolValue("Cheats", "SortList", false));
|
|
s_settings_locals.game_cheat_groups = Cheats::GetCodeListUniquePrefixes(s_settings_locals.game_cheats_list, true);
|
|
s_settings_locals.enabled_game_patch_cache = s_settings_locals.game_settings_interface->GetStringList(
|
|
Cheats::PATCHES_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY);
|
|
s_settings_locals.enabled_game_cheat_cache = s_settings_locals.game_settings_interface->GetStringList(
|
|
Cheats::CHEATS_CONFIG_SECTION, Cheats::PATCH_ENABLE_CONFIG_KEY);
|
|
}
|
|
|
|
void FullscreenUI::BeginResetSettings()
|
|
{
|
|
OpenConfirmMessageDialog(FSUI_STR("Restore Defaults"),
|
|
FSUI_STR("Are you sure you want to restore the default settings? Any preferences will be "
|
|
"lost.\n\nYou cannot undo this action."),
|
|
[](bool result) {
|
|
if (!result)
|
|
return;
|
|
|
|
Host::RequestResetSettings(true, false);
|
|
ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Settings reset to default."));
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::DoCopyGameSettings()
|
|
{
|
|
if (!s_settings_locals.game_settings_interface)
|
|
return;
|
|
|
|
Settings temp_settings;
|
|
temp_settings.Load(*GetEditingSettingsInterface(false), *GetEditingSettingsInterface(false));
|
|
temp_settings.Save(*s_settings_locals.game_settings_interface, true);
|
|
SetSettingsChanged(s_settings_locals.game_settings_interface.get());
|
|
|
|
ShowToast(OSDMessageType::Info, {},
|
|
fmt::format(FSUI_FSTR("Game settings initialized with global settings for '{}'."),
|
|
Path::GetFileTitle(s_settings_locals.game_settings_interface->GetPath())));
|
|
}
|
|
|
|
void FullscreenUI::DoClearGameSettings()
|
|
{
|
|
if (!s_settings_locals.game_settings_interface)
|
|
return;
|
|
|
|
s_settings_locals.game_settings_interface->Clear();
|
|
if (!s_settings_locals.game_settings_interface->GetPath().empty())
|
|
FileSystem::DeleteFile(s_settings_locals.game_settings_interface->GetPath().c_str());
|
|
|
|
SetSettingsChanged(s_settings_locals.game_settings_interface.get());
|
|
|
|
ShowToast(OSDMessageType::Info, {},
|
|
fmt::format(FSUI_FSTR("Game settings have been cleared for '{}'."),
|
|
Path::GetFileTitle(s_settings_locals.game_settings_interface->GetPath())));
|
|
}
|
|
|
|
FullscreenUI::SettingsPage FullscreenUI::GetCurrentSettingsPage()
|
|
{
|
|
return s_settings_locals.settings_page;
|
|
}
|
|
|
|
bool FullscreenUI::IsInputBindingDialogOpen()
|
|
{
|
|
return s_settings_locals.input_binding_dialog.IsOpen();
|
|
}
|
|
|
|
void FullscreenUI::DrawSettingsWindow()
|
|
{
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
const ImVec2 heading_size = ImVec2(
|
|
io.DisplaySize.x, UIStyle.LargeFontSize + (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
|
|
|
|
const float target_bg_alpha = GetBackgroundAlpha();
|
|
s_settings_locals.settings_last_bg_alpha =
|
|
(target_bg_alpha < s_settings_locals.settings_last_bg_alpha) ?
|
|
std::max(s_settings_locals.settings_last_bg_alpha - io.DeltaTime * 2.0f, target_bg_alpha) :
|
|
std::min(s_settings_locals.settings_last_bg_alpha + io.DeltaTime * 2.0f, target_bg_alpha);
|
|
|
|
const bool show_localized_titles = GameList::ShouldShowLocalizedTitles();
|
|
|
|
static constexpr const SettingsPage global_pages[] = {
|
|
SettingsPage::Interface, SettingsPage::GameList, SettingsPage::Console, SettingsPage::Emulation,
|
|
SettingsPage::BIOS, SettingsPage::Graphics, SettingsPage::PostProcessing, SettingsPage::Audio,
|
|
SettingsPage::Controller, SettingsPage::MemoryCards, SettingsPage::Achievements, SettingsPage::Advanced};
|
|
static constexpr const SettingsPage per_game_pages[] = {
|
|
SettingsPage::Summary, SettingsPage::Console, SettingsPage::Emulation, SettingsPage::Patches,
|
|
SettingsPage::Cheats, SettingsPage::Graphics, SettingsPage::Audio, SettingsPage::Controller,
|
|
SettingsPage::MemoryCards, SettingsPage::Achievements, SettingsPage::Advanced};
|
|
static constexpr std::array<std::pair<const char*, const char*>, static_cast<u32>(SettingsPage::Count)> titles = {
|
|
{{FSUI_NSTR("Summary"), ICON_FA_FILE},
|
|
{FSUI_NSTR("Interface Settings"), ICON_FA_TV},
|
|
{FSUI_NSTR("Game List Settings"), ICON_FA_LIST_UL},
|
|
{FSUI_NSTR("Console Settings"), ICON_PF_CONSOLE},
|
|
{FSUI_NSTR("Emulation Settings"), ICON_FA_GEAR},
|
|
{FSUI_NSTR("BIOS Settings"), ICON_PF_MICROCHIP},
|
|
{FSUI_NSTR("Controller Settings"), ICON_PF_GAMEPAD_ALT},
|
|
{FSUI_NSTR("Memory Card Settings"), ICON_PF_MEMORY_CARD},
|
|
{FSUI_NSTR("Graphics Settings"), ICON_PF_PICTURE},
|
|
{FSUI_NSTR("Post-Processing Settings"), ICON_FA_WAND_MAGIC_SPARKLES},
|
|
{FSUI_NSTR("Audio Settings"), ICON_PF_SOUND},
|
|
{FSUI_NSTR("Achievements Settings"), ICON_FA_TROPHY},
|
|
{FSUI_NSTR("Advanced Settings"), ICON_FA_TRIANGLE_EXCLAMATION},
|
|
{FSUI_NSTR("Patches"), ICON_PF_SPARKLING},
|
|
{FSUI_NSTR("Cheats"), ICON_PF_CHEATS}}};
|
|
|
|
const bool game_settings = IsEditingGameSettings(GetEditingSettingsInterface());
|
|
const u32 count =
|
|
(game_settings ? static_cast<u32>(std::size(per_game_pages)) : static_cast<u32>(std::size(global_pages))) -
|
|
BoolToUInt32(!ShouldShowAdvancedSettings());
|
|
const SettingsPage* pages = game_settings ? per_game_pages : global_pages;
|
|
u32 index = 0;
|
|
for (u32 i = 0; i < count; i++)
|
|
{
|
|
if (pages[i] == s_settings_locals.settings_page)
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category",
|
|
ImVec4(UIStyle.PrimaryColor.x, UIStyle.PrimaryColor.y, UIStyle.PrimaryColor.z,
|
|
s_settings_locals.settings_last_bg_alpha)))
|
|
{
|
|
BeginNavBar();
|
|
|
|
if (NavButton(ICON_PF_NAVIGATION_BACK, true, true))
|
|
ReturnToPreviousWindow();
|
|
|
|
NavTitle(s_settings_locals.game_settings_entry ?
|
|
s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles) :
|
|
Host::TranslateToStringView(FSUI_TR_CONTEXT, titles[static_cast<u32>(pages[index])].first));
|
|
|
|
RightAlignNavButtons(count);
|
|
|
|
for (u32 i = 0; i < count; i++)
|
|
{
|
|
if (NavButton(titles[static_cast<u32>(pages[i])].second, i == index, true))
|
|
{
|
|
BeginTransition([page = pages[i]]() {
|
|
s_settings_locals.settings_page = page;
|
|
QueueResetFocus(FocusResetType::Other);
|
|
});
|
|
}
|
|
}
|
|
|
|
EndNavBar();
|
|
}
|
|
|
|
EndFullscreenWindow();
|
|
|
|
const bool is_split_window = (s_settings_locals.settings_page == SettingsPage::Controller);
|
|
bool window_visible;
|
|
if (!is_split_window)
|
|
{
|
|
// we have to do this here, because otherwise it uses target, and jumps a frame later.
|
|
// don't do it for popups opening/closing, otherwise we lose our position
|
|
if (IsFocusResetFromWindowChange())
|
|
ImGui::SetNextWindowScroll(ImVec2(0.0f, 0.0f));
|
|
|
|
window_visible = BeginFullscreenWindow(
|
|
ImVec2(0.0f, heading_size.y),
|
|
ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)),
|
|
TinyString::from_format("settings_page_{}", static_cast<u32>(s_settings_locals.settings_page)).c_str(),
|
|
ImVec4(UIStyle.BackgroundColor.x, UIStyle.BackgroundColor.y, UIStyle.BackgroundColor.z,
|
|
s_settings_locals.settings_last_bg_alpha),
|
|
0.0f, ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, 0.0f));
|
|
|
|
if (ImGui::IsWindowFocused() && WantsToCloseMenu())
|
|
ReturnToPreviousWindow();
|
|
}
|
|
else
|
|
{
|
|
// split windows should have Y padding as well
|
|
window_visible = BeginFullscreenWindow(
|
|
ImVec2(0.0f, heading_size.y),
|
|
ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)),
|
|
TinyString::from_format("settings_page_{}", static_cast<u32>(s_settings_locals.settings_page)).c_str(),
|
|
ImVec4(UIStyle.BackgroundColor.x, UIStyle.BackgroundColor.y, UIStyle.BackgroundColor.z,
|
|
s_settings_locals.settings_last_bg_alpha));
|
|
|
|
if (SplitWindowIsNavWindow() && WantsToCloseMenu())
|
|
ReturnToPreviousWindow();
|
|
}
|
|
|
|
if (window_visible)
|
|
{
|
|
auto lock = Core::GetSettingsLock();
|
|
|
|
switch (s_settings_locals.settings_page)
|
|
{
|
|
case SettingsPage::Summary:
|
|
DrawSummarySettingsPage(show_localized_titles);
|
|
break;
|
|
|
|
case SettingsPage::Interface:
|
|
DrawInterfaceSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::GameList:
|
|
DrawGameListSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::BIOS:
|
|
DrawBIOSSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::Emulation:
|
|
DrawEmulationSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::Console:
|
|
DrawConsoleSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::Graphics:
|
|
DrawGraphicsSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::PostProcessing:
|
|
DrawPostProcessingSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::Audio:
|
|
DrawAudioSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::MemoryCards:
|
|
DrawMemoryCardSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::Controller:
|
|
DrawControllerSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::Achievements:
|
|
DrawAchievementsSettingsPage(lock);
|
|
break;
|
|
|
|
case SettingsPage::Advanced:
|
|
DrawAdvancedSettingsPage();
|
|
break;
|
|
|
|
case SettingsPage::Patches:
|
|
DrawPatchesOrCheatsSettingsPage(false);
|
|
break;
|
|
|
|
case SettingsPage::Cheats:
|
|
DrawPatchesOrCheatsSettingsPage(true);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
EndFullscreenWindow();
|
|
|
|
if (!ImGui::IsPopupOpen(0u, ImGuiPopupFlags_AnyPopup) && !WasSplitWindowChanged())
|
|
{
|
|
if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, true) ||
|
|
ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakSlow, true) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow, true))
|
|
{
|
|
EnqueueSoundEffect(SFX_NAV_MOVE);
|
|
BeginTransition([page = pages[(index == 0) ? (count - 1) : (index - 1)]]() {
|
|
s_settings_locals.settings_page = page;
|
|
QueueResetFocus(FocusResetType::Other);
|
|
});
|
|
}
|
|
else if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight, true) ||
|
|
ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, true) || ImGui::IsKeyPressed(ImGuiKey_RightArrow, true))
|
|
{
|
|
EnqueueSoundEffect(SFX_NAV_MOVE);
|
|
BeginTransition([page = pages[(index + 1) % count]]() {
|
|
s_settings_locals.settings_page = page;
|
|
QueueResetFocus(FocusResetType::Other);
|
|
});
|
|
}
|
|
}
|
|
|
|
if (IsGamepadInputSource())
|
|
{
|
|
SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_LEFT_RIGHT, FSUI_VSTR("Change Page")),
|
|
std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Navigate")),
|
|
std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")),
|
|
std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Back"))});
|
|
}
|
|
else
|
|
{
|
|
SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Change Page")),
|
|
std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Navigate")),
|
|
std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")),
|
|
std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back"))});
|
|
}
|
|
|
|
if (IsFixedPopupDialogOpen(COVER_DOWNLOADER_DIALOG_NAME))
|
|
DrawCoverDownloaderWindow();
|
|
else if (IsFixedPopupDialogOpen(ACHIEVEMENTS_LOGIN_DIALOG_NAME))
|
|
DrawAchievementsLoginWindow();
|
|
else
|
|
s_settings_locals.input_binding_dialog.Draw();
|
|
|
|
if (s_settings_locals.settings_changed)
|
|
{
|
|
s_settings_locals.settings_changed = false;
|
|
Host::CommitBaseSettingChanges();
|
|
Host::RunOnCoreThread([]() { System::ApplySettings(false); });
|
|
}
|
|
if (s_settings_locals.game_settings_changed)
|
|
{
|
|
s_settings_locals.game_settings_changed = false;
|
|
if (s_settings_locals.game_settings_interface)
|
|
{
|
|
Error error;
|
|
s_settings_locals.game_settings_interface->RemoveEmptySections();
|
|
|
|
if (s_settings_locals.game_settings_interface->IsEmpty())
|
|
{
|
|
if (FileSystem::FileExists(s_settings_locals.game_settings_interface->GetPath().c_str()))
|
|
{
|
|
INFO_LOG("Removing empty game settings {}", s_settings_locals.game_settings_interface->GetPath());
|
|
if (!FileSystem::DeleteFile(s_settings_locals.game_settings_interface->GetPath().c_str(), &error))
|
|
{
|
|
OpenInfoMessageDialog(FSUI_STR("Error"),
|
|
fmt::format(FSUI_FSTR("An error occurred while deleting empty game settings:\n{}"),
|
|
error.GetDescription()));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!s_settings_locals.game_settings_interface->Save(&error))
|
|
{
|
|
OpenInfoMessageDialog(
|
|
FSUI_STR("Error"),
|
|
fmt::format(FSUI_FSTR("An error occurred while saving game settings:\n{}"), error.GetDescription()));
|
|
}
|
|
}
|
|
|
|
if (GPUThread::HasGPUBackend())
|
|
Host::RunOnCoreThread([]() { System::ReloadGameSettings(false); });
|
|
}
|
|
}
|
|
}
|
|
|
|
void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles)
|
|
{
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
if (!s_settings_locals.game_settings_entry || s_settings_locals.game_settings_entry->is_runtime_populated)
|
|
{
|
|
MenuButton(FSUI_ICONVSTR(ICON_EMOJI_WARNING,
|
|
"This game was not scanned by DuckStation. Some functionality is not available."),
|
|
{}, false);
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Details"));
|
|
|
|
if (s_settings_locals.game_settings_entry)
|
|
{
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_WINDOW_MAXIMIZE, "Title"),
|
|
s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles), true))
|
|
{
|
|
CopyTextToClipboard(FSUI_STR("Game title copied to clipboard."),
|
|
s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles));
|
|
}
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_PAGER, "Serial"), s_settings_locals.game_settings_entry->serial, true))
|
|
CopyTextToClipboard(FSUI_STR("Game serial copied to clipboard."), s_settings_locals.game_settings_entry->serial);
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_COMPACT_DISC, "Type"),
|
|
GameList::GetEntryTypeDisplayName(s_settings_locals.game_settings_entry->type), true))
|
|
{
|
|
CopyTextToClipboard(FSUI_STR("Game type copied to clipboard."),
|
|
GameList::GetEntryTypeDisplayName(s_settings_locals.game_settings_entry->type));
|
|
}
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_GLOBE, "Region"),
|
|
Settings::GetDiscRegionDisplayName(s_settings_locals.game_settings_entry->region), true))
|
|
{
|
|
CopyTextToClipboard(FSUI_STR("Game region copied to clipboard."),
|
|
Settings::GetDiscRegionDisplayName(s_settings_locals.game_settings_entry->region));
|
|
}
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_STAR, "Compatibility Rating"),
|
|
GameDatabase::GetCompatibilityRatingDisplayName(
|
|
s_settings_locals.game_settings_entry->dbentry ?
|
|
s_settings_locals.game_settings_entry->dbentry->compatibility :
|
|
GameDatabase::CompatibilityRating::Unknown),
|
|
true))
|
|
{
|
|
CopyTextToClipboard(FSUI_STR("Game compatibility rating copied to clipboard."),
|
|
GameDatabase::GetCompatibilityRatingDisplayName(
|
|
s_settings_locals.game_settings_entry->dbentry ?
|
|
s_settings_locals.game_settings_entry->dbentry->compatibility :
|
|
GameDatabase::CompatibilityRating::Unknown));
|
|
}
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_FILE, "Path"), s_settings_locals.game_settings_entry->path, true))
|
|
{
|
|
CopyTextToClipboard(FSUI_STR("Game path copied to clipboard."), s_settings_locals.game_settings_entry->path);
|
|
}
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Options"));
|
|
|
|
DebugAssert(s_settings_locals.game_settings_entry);
|
|
if (s_settings_locals.game_settings_entry->dbentry && s_settings_locals.game_settings_entry->dbentry->disc_set)
|
|
{
|
|
// only enable for first disc
|
|
const bool is_first_disc = (s_settings_locals.game_settings_entry->dbentry->serial ==
|
|
s_settings_locals.game_settings_entry->dbentry->disc_set->serials.front());
|
|
DrawToggleSetting(
|
|
GetEditingSettingsInterface(), FSUI_ICONVSTR(ICON_FA_COMPACT_DISC, "Use Separate Disc Settings"),
|
|
FSUI_VSTR(
|
|
"Uses separate game settings for each disc of multi-disc games. Can only be set on the first/main disc."),
|
|
"Main", "UseSeparateConfigForDiscSet", !is_first_disc, is_first_disc, false);
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_COPY, "Copy Settings"),
|
|
FSUI_VSTR("Copies the current global settings to this game.")))
|
|
{
|
|
DoCopyGameSettings();
|
|
}
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_TRASH, "Clear Settings"), FSUI_VSTR("Clears all settings set for this game.")))
|
|
{
|
|
DoClearGameSettings();
|
|
}
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawInterfaceSettingsPage()
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
MenuHeading(FSUI_VSTR("Appearance"));
|
|
|
|
{
|
|
// Have to do this the annoying way, because it's host-derived.
|
|
const auto language_list = Host::GetAvailableLanguageList();
|
|
TinyString current_language = bsi->GetTinyStringValue("Main", "Language", "");
|
|
if (MenuButtonWithValue(FSUI_ICONVSTR(ICON_FA_LANGUAGE, "Language"),
|
|
FSUI_VSTR("Chooses the language used for UI elements."),
|
|
Host::GetLanguageName(current_language)))
|
|
{
|
|
ChoiceDialogOptions options;
|
|
for (const auto& [language, code] : language_list)
|
|
options.emplace_back(Host::GetLanguageName(code), (current_language == code));
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_LANGUAGE, "UI Language"), false, std::move(options),
|
|
[language_list](s32 index, const std::string& title, bool checked) {
|
|
if (static_cast<u32>(index) >= language_list.size())
|
|
return;
|
|
|
|
Host::RunOnCoreThread(
|
|
[language = language_list[index].second]() { Host::ChangeLanguage(language); });
|
|
});
|
|
}
|
|
}
|
|
|
|
DrawStringListSetting(bsi, FSUI_ICONVSTR(ICON_FA_PALETTE, "Theme"),
|
|
FSUI_VSTR("Selects the color style to be used for Big Picture UI."), "UI", "FullscreenUITheme",
|
|
"", FullscreenUI::GetThemeDisplayNames(), FullscreenUI::GetThemeNames(), true,
|
|
[](std::string_view) { BeginTransition(LONG_TRANSITION_TIME, &FullscreenUI::UpdateTheme); });
|
|
|
|
if (const TinyString current_value =
|
|
bsi->GetTinyStringValue("Main", "FullscreenUIBackground", DEFAULT_BACKGROUND_NAME);
|
|
MenuButtonWithValue(FSUI_ICONVSTR(ICON_FA_IMAGE, "Menu Background"),
|
|
FSUI_VSTR("Shows a background image or shader when a game isn't running. Backgrounds are "
|
|
"located in resources/fullscreenui/backgrounds in the data directory."),
|
|
(current_value == NONE_BACKGROUND_NAME) ? FSUI_VSTR("None") : current_value.view()))
|
|
{
|
|
ChoiceDialogOptions options = GetBackgroundOptions(current_value);
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_IMAGE, "Menu Background"), false, std::move(options),
|
|
[](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
bsi->SetStringValue("Main", "FullscreenUIBackground",
|
|
(index == 0) ? NONE_BACKGROUND_NAME : title.c_str());
|
|
SetSettingsChanged(bsi);
|
|
|
|
// Have to defer the reload, because we've already drawn the bg for this frame.
|
|
BeginTransition(LONG_TRANSITION_TIME, {});
|
|
Host::RunOnCoreThread([]() { GPUThread::RunOnThread(&FullscreenUI::UpdateBackground); });
|
|
});
|
|
}
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_LIST, "Open To Game List"),
|
|
FSUI_VSTR("When Big Picture mode is started, the game list will be displayed instead of the main menu."), "Main",
|
|
"FullscreenUIOpenToGameList", false);
|
|
|
|
bool widgets_settings_changed = DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_PF_GAMEPAD, "Use DualShock/DualSense Button Icons"),
|
|
FSUI_VSTR("Displays DualShock/DualSense button icons in the footer and input binding, instead of Xbox buttons."),
|
|
"Main", "FullscreenUIDisplayPSIcons", false);
|
|
|
|
widgets_settings_changed |=
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_WINDOW_RESTORE, "Window Animations"),
|
|
FSUI_VSTR("Animates windows opening/closing and changes between views in the Big Picture UI."),
|
|
"Main", "FullscreenUIAnimations", true);
|
|
|
|
widgets_settings_changed |= DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_UP_DOWN, "Smooth Scrolling"),
|
|
FSUI_VSTR("Enables smooth scrolling of menus in the Big Picture UI."),
|
|
"Main", "FullscreenUISmoothScrolling", true);
|
|
widgets_settings_changed |=
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BORDER_NONE, "Menu Borders"),
|
|
FSUI_VSTR("Draws a border around the currently-selected item for readability."), "Main",
|
|
"FullscreenUIMenuBorders", false);
|
|
|
|
widgets_settings_changed |= DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_VOLUME_HIGH, "Sound Effects"),
|
|
FSUI_VSTR("Plays sound effects when navigating and activating menus."),
|
|
"Main", "FullscreenUISoundEffects", true);
|
|
|
|
// have to queue because we're holding the settings lock, and UpdateWidgetsSettings() reads it
|
|
if (widgets_settings_changed)
|
|
{
|
|
Host::RunOnCoreThread([]() { GPUThread::RunOnThread(&FullscreenUI::UpdateWidgetsSettings); });
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Behavior"));
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_POWER_OFF, "Confirm Game Close"),
|
|
FSUI_VSTR("Determines whether a prompt will be displayed to confirm closing the game."), "Main",
|
|
"ConfirmPowerOff", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_FLOPPY_DISK, "Save State On Game Close"),
|
|
FSUI_VSTR("Automatically saves the system state when closing the game or exiting. You can then "
|
|
"resume directly from where you left off next time."),
|
|
"Main", "SaveStateOnExit", true);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_WAND_MAGIC_SPARKLES, "Inhibit Screensaver"),
|
|
FSUI_VSTR("Prevents the screen saver from activating and the host from sleeping while emulation is running."),
|
|
"Main", "InhibitScreensaver", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_PAUSE, "Pause On Start"),
|
|
FSUI_VSTR("Pauses the emulator when a game is started."), "Main", "StartPaused", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_EYE_LOW_VISION, "Pause On Focus Loss"),
|
|
FSUI_VSTR("Pauses the emulator when you minimize the window or switch to another "
|
|
"application, and unpauses when you switch back."),
|
|
"Main", "PauseOnFocusLoss", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_GAMEPAD, "Pause On Controller Disconnection"),
|
|
FSUI_VSTR("Pauses the emulator when a controller with bindings is disconnected."), "Main",
|
|
"PauseOnControllerDisconnection", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_FILE_EXPORT, "Create Save State Backups"),
|
|
FSUI_VSTR("Renames existing save states when saving to a backup file."), "Main",
|
|
"CreateSaveStateBackups", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_CIRCLE_USER, "Enable Discord Presence"),
|
|
FSUI_VSTR("Shows the game you are currently playing as part of your profile in Discord."), "Main",
|
|
"EnableDiscordPresence", false);
|
|
|
|
MenuHeading(FSUI_VSTR("Game Display"));
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_PF_FULLSCREEN, "Start Fullscreen"),
|
|
FSUI_VSTR("Automatically switches to fullscreen mode when the program is started."), "Main",
|
|
"StartFullscreen", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_WINDOW_MAXIMIZE, "Double-Click Toggles Fullscreen"),
|
|
FSUI_VSTR("Switches between full screen and windowed when the window is double-clicked."), "Main",
|
|
"DoubleClickTogglesFullscreen", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_ARROW_POINTER, "Hide Cursor In Fullscreen"),
|
|
FSUI_VSTR("Hides the mouse pointer/cursor when the emulator is in fullscreen mode."), "Main",
|
|
"HideCursorInFullscreen", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_MINIMIZE, "Disable Window Resizing"),
|
|
FSUI_VSTR("Prevents resizing of the window while a game is running."), "Main",
|
|
"DisableWindowResize", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_MAXIMIZE, "Automatically Resize Window"),
|
|
FSUI_VSTR("Automatically resizes the window to match the internal resolution."), "Display",
|
|
"AutoResizeWindow", false);
|
|
|
|
MenuHeading(FSUI_VSTR("On-Screen Display"));
|
|
DrawIntSpinBoxSetting(bsi, FSUI_ICONVSTR(ICON_FA_MAGNIFYING_GLASS, "OSD Scale"),
|
|
FSUI_VSTR("Determines how large the on-screen messages and monitor are."), "Display",
|
|
"OSDScale", 100, 25, 500, 1, "%d%%");
|
|
DrawFloatSpinBoxSetting(bsi, FSUI_ICONVSTR(ICON_FA_RULER, "Screen Margins"),
|
|
FSUI_VSTR("Determines the margin between the edge of the screen and on-screen messages."),
|
|
"Display", "OSDMargin", ImGuiManager::DEFAULT_SCREEN_MARGIN, 0.0f, 100.0f, 1.0f, 1.0f,
|
|
"%.0fpx");
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_LOCATION_DOT, "Message Location"),
|
|
FSUI_VSTR("Selects which location on the screen messages are displayed."), "Display",
|
|
"OSDMessageLocation", Settings::DEFAULT_OSD_MESSAGE_LOCATION, &Settings::ParseNotificationLocation,
|
|
&Settings::GetNotificationLocationName, &Settings::GetNotificationLocationDisplayName,
|
|
NotificationLocation::MaxCount);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_CIRCLE_EXCLAMATION, "Show Messages"),
|
|
FSUI_VSTR("Shows on-screen-display messages when events occur. Errors and warnings are still "
|
|
"displayed regardless of this setting."),
|
|
"Display", "ShowOSDMessages", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_PLAY, "Show Status Indicators"),
|
|
FSUI_VSTR("Shows persistent icons when turbo is active or when paused."), "Display",
|
|
"ShowStatusIndicators", true);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_GAUGE, "Show Speed"),
|
|
FSUI_VSTR(
|
|
"Shows the current emulation speed of the system in the top-right corner of the display as a percentage."),
|
|
"Display", "ShowSpeed", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_STOPWATCH, "Show FPS"),
|
|
FSUI_VSTR("Shows the number of frames (or v-syncs) displayed per second by the system in the top-right "
|
|
"corner of the display."),
|
|
"Display", "ShowFPS", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_CHART_BAR, "Show GPU Statistics"),
|
|
FSUI_VSTR("Shows information about the emulated GPU in the top-right corner of the display."),
|
|
"Display", "ShowGPUStatistics", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_USER_CLOCK, "Show Latency Statistics"),
|
|
FSUI_VSTR("Shows information about input and audio latency in the top-right corner of the display."), "Display",
|
|
"ShowLatencyStatistics", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_PF_CPU_PROCESSOR, "Show CPU Usage"),
|
|
FSUI_VSTR("Shows the host's CPU usage of each system thread in the top-right corner of the display."), "Display",
|
|
"ShowCPU", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_PF_GPU_GRAPHICS_CARD, "Show GPU Usage"),
|
|
FSUI_VSTR("Shows the host's GPU usage in the top-right corner of the display."), "Display",
|
|
"ShowGPU", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_WAVE_SQUARE, "Show Frame Times"),
|
|
FSUI_VSTR("Shows a visual history of frame times in the upper-left corner of the display."),
|
|
"Display", "ShowFrameTimes", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_EXPAND, "Show Resolution"),
|
|
FSUI_VSTR("Shows the current rendering resolution of the system in the top-right corner of the display."),
|
|
"Display", "ShowResolution", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_GAMEPAD, "Show Controller Input"),
|
|
FSUI_VSTR("Shows the current controller state of the system in the bottom-left corner of the display."), "Display",
|
|
"ShowInputs", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_CHART_LINE, "Show Enhancement Settings"),
|
|
FSUI_VSTR("Shows enhancement settings in the bottom-right corner of the screen."), "Display",
|
|
"ShowEnhancements", false);
|
|
|
|
MenuHeading(FSUI_VSTR("On-Screen Display Message Durations"));
|
|
|
|
static constexpr float MIN_OSD_MESSAGE_DURATION = 0.5f;
|
|
static constexpr float MAX_OSD_MESSAGE_DURATION = 60.0f;
|
|
static constexpr float OSD_MESSAGE_DURATION_STEP = 0.5f;
|
|
DrawFloatSpinBoxSetting(
|
|
bsi, FSUI_ICONSTR(ICON_FA_TRIANGLE_EXCLAMATION, "Error Message Duration"),
|
|
FSUI_VSTR("Determines how long error messages are displayed on screen."), "Display", "OSDErrorDuration",
|
|
Settings::DEFAULT_DISPLAY_OSD_MESSAGE_DURATIONS[static_cast<size_t>(OSDMessageType::Error)],
|
|
MIN_OSD_MESSAGE_DURATION, MAX_OSD_MESSAGE_DURATION, OSD_MESSAGE_DURATION_STEP, 1.0f, FSUI_CSTR("%g seconds"));
|
|
DrawFloatSpinBoxSetting(
|
|
bsi, FSUI_ICONSTR(ICON_FA_CIRCLE_EXCLAMATION, "Warning Message Duration"),
|
|
FSUI_VSTR("Determines how long warning messages are displayed on screen."), "Display", "OSDWarningDuration",
|
|
Settings::DEFAULT_DISPLAY_OSD_MESSAGE_DURATIONS[static_cast<size_t>(OSDMessageType::Warning)],
|
|
MIN_OSD_MESSAGE_DURATION, MAX_OSD_MESSAGE_DURATION, OSD_MESSAGE_DURATION_STEP, 1.0f, FSUI_CSTR("%g seconds"));
|
|
DrawFloatSpinBoxSetting(
|
|
bsi, FSUI_ICONSTR(ICON_FA_CIRCLE_INFO, "Informational Message Duration"),
|
|
FSUI_VSTR("Determines how long informational messages are displayed on screen."), "Display", "OSDInfoDuration",
|
|
Settings::DEFAULT_DISPLAY_OSD_MESSAGE_DURATIONS[static_cast<size_t>(OSDMessageType::Info)],
|
|
MIN_OSD_MESSAGE_DURATION, MAX_OSD_MESSAGE_DURATION, OSD_MESSAGE_DURATION_STEP, 1.0f, FSUI_CSTR("%g seconds"));
|
|
DrawFloatSpinBoxSetting(
|
|
bsi, FSUI_ICONSTR(ICON_FA_CIRCLE_CHECK, "Quick Message Duration"),
|
|
FSUI_VSTR("Determines how long action confirmation messages are displayed on screen."), "Display",
|
|
"OSDQuickDuration", Settings::DEFAULT_DISPLAY_OSD_MESSAGE_DURATIONS[static_cast<size_t>(OSDMessageType::Quick)],
|
|
MIN_OSD_MESSAGE_DURATION, MAX_OSD_MESSAGE_DURATION, OSD_MESSAGE_DURATION_STEP, 1.0f, FSUI_CSTR("%g seconds"));
|
|
|
|
MenuHeading(FSUI_VSTR("Operations"));
|
|
{
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_DUMPSTER_FIRE, "Restore Defaults"),
|
|
FSUI_VSTR("Resets all settings to the defaults.")))
|
|
{
|
|
BeginResetSettings();
|
|
}
|
|
}
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawGameListSettingsPage()
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(false);
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
MenuHeading(FSUI_VSTR("List Settings"));
|
|
{
|
|
static constexpr const char* view_types[] = {FSUI_NSTR("Game Grid"), FSUI_NSTR("Game List")};
|
|
static constexpr const char* sort_types[] = {
|
|
FSUI_NSTR("Type"),
|
|
FSUI_NSTR("Serial"),
|
|
FSUI_NSTR("Title"),
|
|
FSUI_NSTR("File Title"),
|
|
FSUI_NSTR("Time Played"),
|
|
FSUI_NSTR("Last Played"),
|
|
FSUI_NSTR("File Size"),
|
|
FSUI_NSTR("Uncompressed Size"),
|
|
FSUI_NSTR("Achievement Unlock/Count"),
|
|
};
|
|
|
|
DrawIntListSetting(bsi, FSUI_ICONVSTR(ICON_FA_TABLE_CELLS_LARGE, "Default View"),
|
|
FSUI_VSTR("Selects the view that the game list will open to."), "Main",
|
|
"DefaultFullscreenUIGameView", 0, view_types);
|
|
DrawIntListSetting(bsi, FSUI_ICONVSTR(ICON_FA_SORT, "Sort By"),
|
|
FSUI_VSTR("Determines that field that the game list will be sorted by."), "Main",
|
|
"FullscreenUIGameSort", 0, sort_types);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_ARROW_DOWN_Z_A, "Sort Reversed"),
|
|
FSUI_VSTR("Reverses the game list sort order from the default (usually ascending to descending)."), "Main",
|
|
"FullscreenUIGameSortReverse", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_RECTANGLE_LIST, "Merge Multi-Disc Games"),
|
|
FSUI_VSTR("Merges multi-disc games into one item in the game list."), "Main",
|
|
"FullscreenUIMergeDiscSets", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LANGUAGE, "Show Localized Titles"),
|
|
FSUI_VSTR("Uses localized (native language) titles in the game list."), "UI",
|
|
"GameListShowLocalizedTitles", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TROPHY, "Show Achievement Trophy Icons"),
|
|
FSUI_VSTR("Shows trophy icons in game grid when games have achievements or have been mastered."),
|
|
"Main", "FullscreenUIShowTrophyIcons", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TAGS, "Show Grid View Titles"),
|
|
FSUI_VSTR("Shows titles underneath the images in the game grid view."), "Main",
|
|
"FullscreenUIShowGridTitles", true);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TEXT_SLASH, "List Compact Mode"),
|
|
FSUI_VSTR("Displays only the game title in the list, instead of the title and serial/file name."),
|
|
"Main", "FullscreenUIGameListCompactMode", true);
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Search Directories"));
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_FOLDER_PLUS, "Add Search Directory"),
|
|
FSUI_VSTR("Adds a new directory to the game search list.")))
|
|
{
|
|
OpenFileSelector(FSUI_ICONVSTR(ICON_FA_FOLDER_PLUS, "Add Search Directory"), true, [](const std::string& dir) {
|
|
if (!dir.empty())
|
|
{
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = Core::GetBaseSettingsLayer();
|
|
|
|
bsi->AddToStringList("GameList", "RecursivePaths", dir.c_str());
|
|
bsi->RemoveFromStringList("GameList", "Paths", dir.c_str());
|
|
SetSettingsChanged(bsi);
|
|
PopulateGameListDirectoryCache(*bsi);
|
|
Host::RefreshGameListAsync(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const auto& it : s_settings_locals.game_list_directories_cache)
|
|
{
|
|
if (MenuButton(SmallString::from_format(ICON_FA_FOLDER " {}", it.first),
|
|
it.second ? FSUI_VSTR("Scanning Subdirectories") : FSUI_VSTR("Not Scanning Subdirectories")))
|
|
{
|
|
ChoiceDialogOptions options = {
|
|
{FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Open in File Browser"), false},
|
|
{it.second ? (FSUI_ICONSTR(ICON_FA_FOLDER_MINUS, "Disable Subdirectory Scanning")) :
|
|
(FSUI_ICONSTR(ICON_FA_FOLDER_PLUS, "Enable Subdirectory Scanning")),
|
|
false},
|
|
{FSUI_ICONSTR(ICON_FA_DELETE_LEFT, "Remove From List"), false},
|
|
{FSUI_ICONSTR(ICON_FA_SQUARE_XMARK, "Close Menu"), false},
|
|
};
|
|
|
|
OpenChoiceDialog(it.first.c_str(), false, std::move(options),
|
|
[dir = it.first, recursive = it.second](s32 index, const std::string& title, bool checked) {
|
|
if (index == 0)
|
|
{
|
|
// Open in file browser
|
|
ExitFullscreenAndOpenURL(Path::CreateFileURL(dir));
|
|
}
|
|
else if (index == 1)
|
|
{
|
|
// toggle subdirectory scanning
|
|
{
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = Core::GetBaseSettingsLayer();
|
|
if (!recursive)
|
|
{
|
|
bsi->RemoveFromStringList("GameList", "Paths", dir.c_str());
|
|
bsi->AddToStringList("GameList", "RecursivePaths", dir.c_str());
|
|
}
|
|
else
|
|
{
|
|
bsi->RemoveFromStringList("GameList", "RecursivePaths", dir.c_str());
|
|
bsi->AddToStringList("GameList", "Paths", dir.c_str());
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
PopulateGameListDirectoryCache(*bsi);
|
|
}
|
|
|
|
Host::RefreshGameListAsync(false);
|
|
}
|
|
else if (index == 2)
|
|
{
|
|
// remove from list
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = Core::GetBaseSettingsLayer();
|
|
bsi->RemoveFromStringList("GameList", "Paths", dir.c_str());
|
|
bsi->RemoveFromStringList("GameList", "RecursivePaths", dir.c_str());
|
|
SetSettingsChanged(bsi);
|
|
PopulateGameListDirectoryCache(*bsi);
|
|
Host::RefreshGameListAsync(false);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Cover Settings"));
|
|
{
|
|
DrawFolderSetting(bsi, FSUI_ICONVSTR(ICON_FA_FOLDER, "Covers Directory"), "Folders", "Covers", EmuFolders::Covers);
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_DOWNLOAD, "Download Covers"),
|
|
FSUI_VSTR("Downloads covers from a user-specified URL template.")))
|
|
{
|
|
OpenFixedPopupDialog(COVER_DOWNLOADER_DIALOG_NAME);
|
|
}
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Operations"));
|
|
{
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_MAGNIFYING_GLASS, "Scan For New Games"),
|
|
FSUI_VSTR("Identifies any new files added to the game directories.")))
|
|
{
|
|
Host::RefreshGameListAsync(false);
|
|
}
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_ARROWS_ROTATE, "Rescan All Games"),
|
|
FSUI_VSTR("Forces a full rescan of all games previously identified.")))
|
|
{
|
|
Host::RefreshGameListAsync(true);
|
|
}
|
|
}
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawCoverDownloaderWindow()
|
|
{
|
|
static char template_urls[512];
|
|
static bool use_serial_names;
|
|
|
|
if (!BeginFixedPopupDialog(LayoutScale(LAYOUT_LARGE_POPUP_PADDING), LayoutScale(LAYOUT_LARGE_POPUP_ROUNDING),
|
|
LayoutScale(1000.0f, 0.0f)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f));
|
|
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight);
|
|
|
|
ImGui::TextWrapped(
|
|
"%s",
|
|
FSUI_CSTR("DuckStation can automatically download covers for games which do not currently have a cover set. We "
|
|
"do not host any cover images, the user must provide their own source for images."));
|
|
ImGui::NewLine();
|
|
ImGui::TextWrapped("%s",
|
|
FSUI_CSTR("In the form below, specify the URLs to download covers from, with one template URL "
|
|
"per line. The following variables are available:"));
|
|
ImGui::NewLine();
|
|
ImGui::TextWrapped("%s", FSUI_CSTR("${title}: Title of the game.\n${filetitle}: Name component of the game's "
|
|
"filename.\n${serial}: Serial of the game."));
|
|
ImGui::NewLine();
|
|
ImGui::TextWrapped("%s", FSUI_CSTR("Example: https://www.example-not-a-real-domain.com/covers/${serial}.jpg"));
|
|
ImGui::NewLine();
|
|
|
|
ImGui::InputTextMultiline("##templates", template_urls, sizeof(template_urls),
|
|
ImVec2(ImGui::GetCurrentWindow()->WorkRect.GetWidth(), LayoutScale(175.0f)));
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(5.0f));
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(2.0f, 2.0f));
|
|
ImGui::Checkbox(FSUI_CSTR("Save as Serial File Names"), &use_serial_names);
|
|
ImGui::PopStyleVar(1);
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
|
|
const bool download_enabled = (std::strlen(template_urls) > 0);
|
|
|
|
BeginHorizontalMenuButtons(2, 200.0f);
|
|
ResetFocusHere();
|
|
|
|
if (HorizontalMenuButton(FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Start Download"), download_enabled))
|
|
{
|
|
// TODO: Remove release once using move_only_function
|
|
std::unique_ptr<ProgressCallback> progress = OpenModalProgressDialog(FSUI_STR("Cover Downloader"), 1000.0f);
|
|
Host::QueueAsyncTask([progress = progress.release(), urls = StringUtil::SplitNewString(template_urls, '\n'),
|
|
use_serial_names = use_serial_names]() {
|
|
Error error;
|
|
if (!GameList::DownloadCovers(
|
|
urls, use_serial_names, progress, &error, [](const GameList::Entry* entry, std::string save_path) {
|
|
// cache the cover path on our side once it's saved
|
|
Host::RunOnCoreThread([path = entry->path, save_path = std::move(save_path)]() mutable {
|
|
GPUThread::RunOnThread([path = std::move(path), save_path = std::move(save_path)]() mutable {
|
|
FullscreenUI::SetCoverCacheEntry(std::move(path), std::move(save_path));
|
|
});
|
|
});
|
|
}))
|
|
{
|
|
OpenInfoMessageDialog(FSUI_STR("Cover Download Error"), error.TakeDescription());
|
|
}
|
|
|
|
// close the parent window if we weren't cancelled
|
|
if (!progress->IsCancelled())
|
|
{
|
|
Host::RunOnCoreThread([]() {
|
|
GPUThread::RunOnThread([]() {
|
|
if (IsFixedPopupDialogOpen(COVER_DOWNLOADER_DIALOG_NAME))
|
|
CloseFixedPopupDialog();
|
|
});
|
|
});
|
|
}
|
|
|
|
delete progress;
|
|
});
|
|
}
|
|
|
|
if (HorizontalMenuButton(FSUI_ICONSTR(ICON_FA_XMARK, "Close")))
|
|
CloseFixedPopupDialog();
|
|
|
|
EndHorizontalMenuButtons();
|
|
|
|
ImGui::PopFont();
|
|
ImGui::PopStyleVar(2);
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
|
|
void FullscreenUI::DrawBIOSSettingsPage()
|
|
{
|
|
static constexpr const std::array config_keys = {"", "PathNTSCJ", "PathNTSCU", "PathPAL"};
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
MenuHeading(FSUI_VSTR("BIOS Selection"));
|
|
|
|
for (u32 i = 0; i < static_cast<u32>(ConsoleRegion::Count); i++)
|
|
{
|
|
const ConsoleRegion region = static_cast<ConsoleRegion>(i);
|
|
if (region == ConsoleRegion::Auto)
|
|
continue;
|
|
|
|
TinyString title;
|
|
title.assign(ICON_FA_MICROCHIP " ");
|
|
title.append_format(FSUI_FSTR("BIOS for {}"), Settings::GetConsoleRegionDisplayName(region));
|
|
|
|
const std::optional<SmallString> filename(bsi->GetOptionalSmallStringValue(
|
|
"BIOS", config_keys[i], game_settings ? std::nullopt : std::optional<const char*>("")));
|
|
|
|
if (MenuButtonWithValue(title,
|
|
TinyString::from_format(FSUI_FSTR("BIOS to use when emulating {} consoles."),
|
|
Settings::GetConsoleRegionDisplayName(region)),
|
|
filename.has_value() ? (filename->empty() ? FSUI_VSTR("Auto-Detect") : filename->view()) :
|
|
FSUI_VSTR("Use Global Setting")))
|
|
{
|
|
ChoiceDialogOptions options;
|
|
auto images = BIOS::FindBIOSImagesInDirectory(EmuFolders::Bios.c_str());
|
|
options.reserve(images.size() + 2);
|
|
if (IsEditingGameSettings(bsi))
|
|
options.emplace_back(FSUI_STR("Use Global Setting"), !filename.has_value());
|
|
options.emplace_back(FSUI_STR("Auto-Detect"), filename.has_value() && filename->empty());
|
|
for (auto& [path, info] : images)
|
|
{
|
|
const bool selected = (filename.has_value() && filename.value() == path);
|
|
options.emplace_back(std::move(path), selected);
|
|
}
|
|
|
|
OpenChoiceDialog(
|
|
title, false, std::move(options), [game_settings, i](s32 index, const std::string& path, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings && index == 0)
|
|
{
|
|
bsi->DeleteValue("BIOS", config_keys[i]);
|
|
}
|
|
else
|
|
{
|
|
bsi->SetStringValue("BIOS", config_keys[i],
|
|
(index == static_cast<s32>(BoolToUInt32(game_settings))) ? "" : path.c_str());
|
|
}
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Options"));
|
|
|
|
DrawFolderSetting(bsi, FSUI_ICONVSTR(ICON_FA_FOLDER, "BIOS Directory"), "BIOS", "SearchDirectory", EmuFolders::Bios);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_SCROLL, "Enable TTY Logging"),
|
|
FSUI_VSTR("Logs BIOS calls to printf(). Not all games contain debugging messages."), "BIOS",
|
|
"TTYLogging", false);
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawConsoleSettingsPage()
|
|
{
|
|
static constexpr const std::array cdrom_read_speeds = {
|
|
FSUI_NSTR("None (Double Speed)"), FSUI_NSTR("2x (Quad Speed)"), FSUI_NSTR("3x (6x Speed)"),
|
|
FSUI_NSTR("4x (8x Speed)"), FSUI_NSTR("5x (10x Speed)"), FSUI_NSTR("6x (12x Speed)"),
|
|
FSUI_NSTR("Maximum (Safer)"),
|
|
};
|
|
|
|
static constexpr const std::array cdrom_seek_speeds = {
|
|
FSUI_NSTR("None (Normal Speed)"),
|
|
FSUI_NSTR("2x"),
|
|
FSUI_NSTR("3x"),
|
|
FSUI_NSTR("4x"),
|
|
FSUI_NSTR("5x"),
|
|
FSUI_NSTR("6x"),
|
|
FSUI_NSTR("Maximum (Safer)"),
|
|
};
|
|
|
|
static constexpr std::array cdrom_read_seek_speed_values = {1, 2, 3, 4, 5, 6, 0};
|
|
|
|
SettingsInterface* const bsi = GetEditingSettingsInterface();
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
MenuHeading(FSUI_VSTR("Console Settings"));
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_GLOBE, "Region"), FSUI_VSTR("Determines the emulated hardware type."),
|
|
"Console", "Region", Settings::DEFAULT_CONSOLE_REGION, &Settings::ParseConsoleRegionName,
|
|
&Settings::GetConsoleRegionName, &Settings::GetConsoleRegionDisplayName, ConsoleRegion::Count);
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_STOPWATCH, "Frame Rate"),
|
|
FSUI_VSTR("Utilizes the chosen frame rate regardless of the game's setting."), "GPU",
|
|
"ForceVideoTiming", Settings::DEFAULT_FORCE_VIDEO_TIMING_MODE, &Settings::ParseForceVideoTimingName,
|
|
&Settings::GetForceVideoTimingName, &Settings::GetForceVideoTimingDisplayName,
|
|
ForceVideoTimingMode::Count);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_SHIELD_HALVED, "Safe Mode"),
|
|
FSUI_VSTR("Temporarily disables all enhancements, useful when testing."), "Main",
|
|
"DisableAllEnhancements", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BOLT, "Enable Fast Boot"),
|
|
FSUI_VSTR("Patches the BIOS to skip the boot animation. Safe to enable."), "BIOS", "PatchFastBoot",
|
|
Settings::DEFAULT_FAST_BOOT_VALUE);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_FORWARD, "Fast Forward Boot"),
|
|
FSUI_VSTR("Fast forwards through the early loading process when fast booting, saving time. Results "
|
|
"may vary between games."),
|
|
"BIOS", "FastForwardBoot", false,
|
|
GetEffectiveBoolSetting(bsi, "BIOS", "PatchFastBoot", Settings::DEFAULT_FAST_BOOT_VALUE));
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_PF_MEMORY_CARD, "Fast Forward Memory Card Access"),
|
|
FSUI_VSTR("Fast forwards through memory card access, both loading and saving. Can reduce waiting "
|
|
"times in games that frequently access memory cards."),
|
|
"MemoryCards", "FastForwardAccess", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_MEMORY, "Enable 8MB RAM"),
|
|
FSUI_VSTR("Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles."),
|
|
"Console", "Enable8MBRAM", false);
|
|
|
|
MenuHeading(FSUI_VSTR("CPU Emulation"));
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_BOLT, "Execution Mode"),
|
|
FSUI_VSTR("Determines how the emulated CPU executes instructions."), "CPU", "ExecutionMode",
|
|
Settings::DEFAULT_CPU_EXECUTION_MODE, &Settings::ParseCPUExecutionMode,
|
|
&Settings::GetCPUExecutionModeName, &Settings::GetCPUExecutionModeDisplayName,
|
|
CPUExecutionMode::Count);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_GAUGE_SIMPLE_HIGH, "Enable Overclocking"),
|
|
FSUI_VSTR("When this option is chosen, the clock speed set below will be used."), "CPU",
|
|
"OverclockEnable", false);
|
|
|
|
const bool oc_enable = GetEffectiveBoolSetting(bsi, "CPU", "OverclockEnable", false);
|
|
if (oc_enable)
|
|
{
|
|
u32 oc_numerator = GetEffectiveUIntSetting(bsi, "CPU", "OverclockNumerator", 1);
|
|
u32 oc_denominator = GetEffectiveUIntSetting(bsi, "CPU", "OverclockDenominator", 1);
|
|
s32 oc_percent = static_cast<s32>(Settings::CPUOverclockFractionToPercent(oc_numerator, oc_denominator));
|
|
if (RangeButton(FSUI_ICONVSTR(ICON_FA_GAUGE_SIMPLE_HIGH, "Overclocking Percentage"),
|
|
FSUI_VSTR("Selects the percentage of the normal clock speed the emulated hardware will run at."),
|
|
&oc_percent, 10, 1000, 10, "%d%%"))
|
|
{
|
|
Settings::CPUOverclockPercentToFraction(oc_percent, &oc_numerator, &oc_denominator);
|
|
bsi->SetUIntValue("CPU", "OverclockNumerator", oc_numerator);
|
|
bsi->SetUIntValue("CPU", "OverclockDenominator", oc_denominator);
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
}
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_MICROCHIP, "Enable Recompiler ICache"),
|
|
FSUI_VSTR("Makes games run closer to their console framerate, at a small cost to performance."),
|
|
"CPU", "RecompilerICache", false);
|
|
|
|
MenuHeading(FSUI_VSTR("CD-ROM Emulation"));
|
|
|
|
DrawIntListSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_COMPACT_DISC, "Read Speedup"),
|
|
FSUI_VSTR(
|
|
"Speeds up CD-ROM reads by the specified factor. May improve loading speeds in some games, and break others."),
|
|
"CDROM", "ReadSpeedup", 1, cdrom_read_speeds, true, cdrom_read_seek_speed_values);
|
|
DrawIntListSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_MAGNIFYING_GLASS, "Seek Speedup"),
|
|
FSUI_VSTR(
|
|
"Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, and break others."),
|
|
"CDROM", "SeekSpeedup", 1, cdrom_seek_speeds, true, cdrom_read_seek_speed_values);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_DOWNLOAD, "Preload Images to RAM"),
|
|
FSUI_VSTR("Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay."),
|
|
"CDROM", "LoadImageToRAM", false);
|
|
if (!game_settings)
|
|
{
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_VEST_PATCHES, "Apply Image Patches"),
|
|
FSUI_VSTR("Automatically applies patches to disc images when they are present, currently only PPF is supported."),
|
|
"CDROM", "LoadImagePatches", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BAN, "Ignore Drive Subcode"),
|
|
FSUI_VSTR("Ignores the subchannel provided by the drive when using physical discs, instead "
|
|
"always generating subchannel data. Can improve read reliability on some drives."),
|
|
"CDROM", "IgnoreHostSubcode", false);
|
|
}
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIST_OL, "Switch to Next Disc on Stop"),
|
|
FSUI_VSTR("Automatically switches to the next disc in the game when the game stops the CD-ROM "
|
|
"motor. Does not work for all games."),
|
|
"CDROM", "AutoDiscChange", false);
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawEmulationSettingsPage()
|
|
{
|
|
static constexpr const std::array emulation_speed_values = {
|
|
0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.25f, 1.5f,
|
|
1.75f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f,
|
|
};
|
|
static constexpr const std::array emulation_speed_titles = {
|
|
FSUI_NSTR("Unlimited"),
|
|
FSUI_NSTR("10% [6 FPS (NTSC) / 5 FPS (PAL)]"),
|
|
FSUI_NSTR("20% [12 FPS (NTSC) / 10 FPS (PAL)]"),
|
|
FSUI_NSTR("30% [18 FPS (NTSC) / 15 FPS (PAL)]"),
|
|
FSUI_NSTR("40% [24 FPS (NTSC) / 20 FPS (PAL)]"),
|
|
FSUI_NSTR("50% [30 FPS (NTSC) / 25 FPS (PAL)]"),
|
|
FSUI_NSTR("60% [36 FPS (NTSC) / 30 FPS (PAL)]"),
|
|
FSUI_NSTR("70% [42 FPS (NTSC) / 35 FPS (PAL)]"),
|
|
FSUI_NSTR("80% [48 FPS (NTSC) / 40 FPS (PAL)]"),
|
|
FSUI_NSTR("90% [54 FPS (NTSC) / 45 FPS (PAL)]"),
|
|
FSUI_NSTR("100% [60 FPS (NTSC) / 50 FPS (PAL)]"),
|
|
FSUI_NSTR("125% [75 FPS (NTSC) / 62 FPS (PAL)]"),
|
|
FSUI_NSTR("150% [90 FPS (NTSC) / 75 FPS (PAL)]"),
|
|
FSUI_NSTR("175% [105 FPS (NTSC) / 87 FPS (PAL)]"),
|
|
FSUI_NSTR("200% [120 FPS (NTSC) / 100 FPS (PAL)]"),
|
|
FSUI_NSTR("250% [150 FPS (NTSC) / 125 FPS (PAL)]"),
|
|
FSUI_NSTR("300% [180 FPS (NTSC) / 150 FPS (PAL)]"),
|
|
FSUI_NSTR("350% [210 FPS (NTSC) / 175 FPS (PAL)]"),
|
|
FSUI_NSTR("400% [240 FPS (NTSC) / 200 FPS (PAL)]"),
|
|
FSUI_NSTR("450% [270 FPS (NTSC) / 225 FPS (PAL)]"),
|
|
FSUI_NSTR("500% [300 FPS (NTSC) / 250 FPS (PAL)]"),
|
|
FSUI_NSTR("600% [360 FPS (NTSC) / 300 FPS (PAL)]"),
|
|
FSUI_NSTR("700% [420 FPS (NTSC) / 350 FPS (PAL)]"),
|
|
FSUI_NSTR("800% [480 FPS (NTSC) / 400 FPS (PAL)]"),
|
|
FSUI_NSTR("900% [540 FPS (NTSC) / 450 FPS (PAL)]"),
|
|
FSUI_NSTR("1000% [600 FPS (NTSC) / 500 FPS (PAL)]"),
|
|
};
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
MenuHeading(FSUI_VSTR("Speed Control"));
|
|
DrawFloatListSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_GAUGE, "Emulation Speed"),
|
|
FSUI_VSTR("Sets the target emulation speed. It is not guaranteed that this speed will be reached on all systems."),
|
|
"Main", "EmulationSpeed", 1.0f, emulation_speed_titles.data(), emulation_speed_values.data(),
|
|
emulation_speed_titles.size(), true);
|
|
DrawFloatListSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_FORWARD, "Fast Forward Speed"),
|
|
FSUI_VSTR("Sets the fast forward speed. It is not guaranteed that this speed will be reached on all systems."),
|
|
"Main", "FastForwardSpeed", 0.0f, emulation_speed_titles.data(), emulation_speed_values.data(),
|
|
emulation_speed_titles.size(), true);
|
|
DrawFloatListSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_BOLT, "Turbo Speed"),
|
|
FSUI_VSTR("Sets the turbo speed. It is not guaranteed that this speed will be reached on all systems."), "Main",
|
|
"TurboSpeed", 2.0f, emulation_speed_titles.data(), emulation_speed_values.data(), emulation_speed_titles.size(),
|
|
true);
|
|
|
|
MenuHeading(FSUI_VSTR("Latency Control"));
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TV, "Vertical Sync (VSync)"),
|
|
FSUI_VSTR("Synchronizes presentation of the console's frames to the host. GSync/FreeSync users "
|
|
"should enable Optimal Frame Pacing instead."),
|
|
"Display", "VSync", false);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_LIGHTBULB, "Sync To Host Refresh Rate"),
|
|
FSUI_VSTR("Adjusts the emulation speed so the console's refresh rate matches the host when VSync is enabled."),
|
|
"Main", "SyncToHostRefreshRate", false);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_GAUGE_SIMPLE_HIGH, "Optimal Frame Pacing"),
|
|
FSUI_VSTR("Ensures every frame generated is displayed for optimal pacing. Enable for variable refresh displays, "
|
|
"such as GSync/FreeSync. Disable if you are having speed or sound issues."),
|
|
"Display", "OptimalFramePacing", false);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_CHARGING_STATION, "Skip Duplicate Frame Display"),
|
|
FSUI_VSTR("Skips the presentation/display of frames that are not unique. Can result in worse frame pacing."),
|
|
"Display", "SkipPresentingDuplicateFrames", false);
|
|
|
|
const bool optimal_frame_pacing_active = GetEffectiveBoolSetting(bsi, "Display", "OptimalFramePacing", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_STOPWATCH_20, "Reduce Input Latency"),
|
|
FSUI_VSTR("Reduces input latency by delaying the start of frame until closer to the presentation time."), "Display",
|
|
"PreFrameSleep", false, optimal_frame_pacing_active);
|
|
|
|
const bool pre_frame_sleep_active =
|
|
(optimal_frame_pacing_active && GetEffectiveBoolSetting(bsi, "Display", "PreFrameSleep", false));
|
|
if (pre_frame_sleep_active)
|
|
{
|
|
DrawFloatRangeSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_HOURGLASS, "Frame Time Buffer"),
|
|
FSUI_VSTR("Specifies the amount of buffer time added, which reduces the additional sleep time introduced."),
|
|
"Display", "PreFrameSleepBuffer", Settings::DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER, 0.0f, 20.0f,
|
|
FSUI_CSTR("%.1f ms"), 1.0f, pre_frame_sleep_active);
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Runahead/Rewind"));
|
|
|
|
const s32 runahead_frames = GetEffectiveIntSetting(bsi, "Main", "RunaheadFrameCount", 0);
|
|
const bool runahead_enabled = (runahead_frames > 0);
|
|
const bool rewind_enabled = GetEffectiveBoolSetting(bsi, "Main", "RewindEnable", false);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BACKWARD, "Enable Rewinding"),
|
|
FSUI_VSTR("Saves state periodically so you can rewind any mistakes while playing."), "Main",
|
|
"RewindEnable", false, !runahead_enabled);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_PF_GPU_GRAPHICS_CARD, "Use Software Renderer (Low VRAM Mode)"),
|
|
FSUI_VSTR("Uses the software renderer when creating rewind states to prevent additional VRAM "
|
|
"usage. Especially useful when upscaling."),
|
|
"GPU", "UseSoftwareRendererForMemoryStates", false, rewind_enabled);
|
|
|
|
DrawFloatRangeSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_FLOPPY_DISK, "Rewind Save Frequency"),
|
|
FSUI_VSTR("How often a rewind state will be created. Higher frequencies have greater system requirements."), "Main",
|
|
"RewindFrequency", 10.0f, 0.0f, 3600.0f, FSUI_CSTR("%.2f Seconds"), 1.0f, rewind_enabled && !runahead_enabled);
|
|
DrawIntRangeSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_WHISKEY_GLASS, "Rewind Save Slots"),
|
|
FSUI_VSTR("How many saves will be kept for rewinding. Higher values have greater memory requirements."), "Main",
|
|
"RewindSaveSlots", 10, 1, 10000, FSUI_CSTR("%d Frames"), rewind_enabled && !runahead_enabled);
|
|
|
|
static constexpr const std::array runahead_options = {
|
|
FSUI_NSTR("Disabled"), FSUI_NSTR("1 Frame"), FSUI_NSTR("2 Frames"), FSUI_NSTR("3 Frames"),
|
|
FSUI_NSTR("4 Frames"), FSUI_NSTR("5 Frames"), FSUI_NSTR("6 Frames"), FSUI_NSTR("7 Frames"),
|
|
FSUI_NSTR("8 Frames"), FSUI_NSTR("9 Frames"), FSUI_NSTR("10 Frames")};
|
|
|
|
DrawIntListSetting(bsi, FSUI_ICONVSTR(ICON_FA_PERSON_RUNNING, "Runahead"),
|
|
FSUI_VSTR("Simulates the system ahead of time and rolls back/replays to reduce input lag. Very "
|
|
"high system requirements."),
|
|
"Main", "RunaheadFrameCount", 0, runahead_options);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_PF_ANALOG_ANY, "Runahead for Analog Input"),
|
|
FSUI_VSTR("Activates runahead when analog input changes, which significantly increases system requirements."),
|
|
"Main", "RunaheadForAnalogInput", false, runahead_enabled);
|
|
|
|
TinyString rewind_summary;
|
|
if (runahead_enabled)
|
|
{
|
|
rewind_summary = FSUI_VSTR("Rewind is disabled because runahead is enabled. Runahead will significantly increase "
|
|
"system requirements.");
|
|
}
|
|
else if (rewind_enabled)
|
|
{
|
|
const u32 resolution_scale = GetEffectiveUIntSetting(bsi, "GPU", "ResolutionScale", 1);
|
|
const u32 multisamples = GetEffectiveUIntSetting(bsi, "GPU", "Multisamples", 1);
|
|
const bool use_software_renderer = GetEffectiveBoolSetting(bsi, "GPU", "UseSoftwareRendererForMemoryStates", false);
|
|
const bool enable_8mb_ram = GetEffectiveBoolSetting(bsi, "Console", "Enable8MBRAM", false);
|
|
const float rewind_frequency = GetEffectiveFloatSetting(bsi, "Main", "RewindFrequency", 10.0f);
|
|
const s32 rewind_save_slots = GetEffectiveIntSetting(bsi, "Main", "RewindSaveSlots", 10);
|
|
const float duration =
|
|
((rewind_frequency <= std::numeric_limits<float>::epsilon()) ? (1.0f / 60.0f) : rewind_frequency) *
|
|
static_cast<float>(rewind_save_slots);
|
|
|
|
u64 ram_usage, vram_usage;
|
|
System::CalculateRewindMemoryUsage(rewind_save_slots, resolution_scale, multisamples, use_software_renderer,
|
|
enable_8mb_ram, &ram_usage, &vram_usage);
|
|
if (vram_usage > 0)
|
|
{
|
|
rewind_summary.format(
|
|
FSUI_FSTR("Rewind for {0} frames, lasting {1:.2f} seconds will require {2} MB of RAM and {3} MB of VRAM."),
|
|
rewind_save_slots, duration, ram_usage / 1048576, vram_usage / 1048576);
|
|
}
|
|
else
|
|
{
|
|
rewind_summary.format(FSUI_FSTR("Rewind for {0} frames, lasting {1:.2f} seconds will require {2} MB of RAM."),
|
|
rewind_save_slots, duration, ram_usage / 1048576);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rewind_summary = FSUI_VSTR("Rewind is not enabled. Please note that enabling rewind may significantly increase "
|
|
"system requirements.");
|
|
}
|
|
|
|
MenuButtonWithoutSummary(rewind_summary, false);
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::CopyGlobalControllerSettingsToGame()
|
|
{
|
|
SettingsInterface* dsi = GetEditingSettingsInterface(true);
|
|
SettingsInterface* ssi = GetEditingSettingsInterface(false);
|
|
|
|
InputManager::CopyConfiguration(dsi, *ssi, true, true, false);
|
|
SetSettingsChanged(dsi);
|
|
|
|
ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Per-game controller configuration initialized with global settings."));
|
|
}
|
|
|
|
void FullscreenUI::DoLoadInputProfile()
|
|
{
|
|
std::vector<std::string> profiles = InputManager::GetInputProfileNames();
|
|
if (profiles.empty())
|
|
{
|
|
ShowToast(OSDMessageType::Quick, {}, FSUI_STR("No input profiles available."));
|
|
return;
|
|
}
|
|
|
|
ChoiceDialogOptions coptions;
|
|
coptions.reserve(profiles.size());
|
|
for (std::string& name : profiles)
|
|
coptions.emplace_back(std::move(name), false);
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_FOLDER_OPEN, "Load Preset"), false, std::move(coptions),
|
|
[](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
INISettingsInterface ssi(System::GetInputProfilePath(title));
|
|
if (!ssi.Load())
|
|
{
|
|
ShowToast(OSDMessageType::Info, {}, fmt::format(FSUI_FSTR("Failed to load '{}'."), title));
|
|
return;
|
|
}
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* dsi = GetEditingSettingsInterface();
|
|
InputManager::CopyConfiguration(dsi, ssi, true, true, true, IsEditingGameSettings(dsi));
|
|
SetSettingsChanged(dsi);
|
|
ShowToast(OSDMessageType::Quick, {},
|
|
fmt::format(FSUI_FSTR("Controller preset '{}' loaded."), title));
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::DoSaveInputProfile(const std::string& name)
|
|
{
|
|
INISettingsInterface dsi(System::GetInputProfilePath(name));
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* ssi = GetEditingSettingsInterface();
|
|
InputManager::CopyConfiguration(&dsi, *ssi, true, true, true, IsEditingGameSettings(ssi));
|
|
if (dsi.Save())
|
|
ShowToast(OSDMessageType::Quick, {}, fmt::format(FSUI_FSTR("Controller preset '{}' saved."), name));
|
|
else
|
|
ShowToast(OSDMessageType::Info, {}, fmt::format(FSUI_FSTR("Failed to save controller preset '{}'."), name));
|
|
}
|
|
|
|
void FullscreenUI::DoSaveNewInputProfile()
|
|
{
|
|
OpenInputStringDialog(FSUI_ICONSTR(ICON_FA_FLOPPY_DISK, "Save Controller Preset"),
|
|
FSUI_STR("Enter the name of the controller preset you wish to create."), std::string(),
|
|
FSUI_ICONSTR(ICON_FA_FOLDER_PLUS, "Create"), [](std::string title) {
|
|
if (!title.empty())
|
|
DoSaveInputProfile(title);
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::DoSaveInputProfile()
|
|
{
|
|
std::vector<std::string> profiles = InputManager::GetInputProfileNames();
|
|
if (profiles.empty())
|
|
{
|
|
DoSaveNewInputProfile();
|
|
return;
|
|
}
|
|
|
|
ChoiceDialogOptions coptions;
|
|
coptions.reserve(profiles.size() + 1);
|
|
coptions.emplace_back(FSUI_STR("Create New..."), false);
|
|
for (std::string& name : profiles)
|
|
coptions.emplace_back(std::move(name), false);
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_FLOPPY_DISK, "Save Preset"), false, std::move(coptions),
|
|
[](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
if (index > 0)
|
|
DoSaveInputProfile(title);
|
|
else
|
|
DoSaveNewInputProfile();
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::BeginResetControllerSettings()
|
|
{
|
|
OpenConfirmMessageDialog(FSUI_STR("Reset Controller Settings"),
|
|
FSUI_STR("Are you sure you want to restore the default controller configuration?\n\nAll "
|
|
"bindings and configuration will be lost. You cannot undo this action."),
|
|
[](bool result) {
|
|
if (!result)
|
|
return;
|
|
|
|
Host::RequestResetSettings(false, true);
|
|
ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Controller settings reset to default."));
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::DrawControllerSettingsPage()
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
|
|
BeginInnerSplitWindow();
|
|
|
|
BeginSplitWindowSidebar(0.25f);
|
|
|
|
if (SplitWindowSidebarItem(FSUI_ICONVSTR(ICON_FA_GEARS, "Global Settings"),
|
|
(s_settings_locals.selected_controller_port == -1)))
|
|
{
|
|
BeginTransition(DEFAULT_TRANSITION_TIME, []() {
|
|
s_settings_locals.selected_controller_port = -1;
|
|
FullscreenUI::FocusSplitWindowContent();
|
|
});
|
|
}
|
|
|
|
// load mtap settings
|
|
const MultitapMode mtap_mode =
|
|
Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode").c_str())
|
|
.value_or(Settings::DEFAULT_MULTITAP_MODE);
|
|
const std::array<bool, 2> mtap_enabled = {
|
|
{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
|
|
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
|
|
|
|
// create the ports
|
|
for (const u32 global_slot : Controller::PortDisplayOrder)
|
|
{
|
|
const auto [mtap_port, mtap_slot] = Controller::ConvertPadToPortAndSlot(global_slot);
|
|
const bool is_mtap_port = Controller::PortAndSlotIsMultitap(mtap_port, mtap_slot);
|
|
if (is_mtap_port && !mtap_enabled[mtap_port])
|
|
continue;
|
|
|
|
const TinyString type =
|
|
bsi->GetTinyStringValue(TinyString::from_format("Pad{}", global_slot + 1).c_str(), "Type",
|
|
Controller::GetControllerInfo(Settings::GetDefaultControllerType(global_slot)).name);
|
|
const Controller::ControllerInfo* ci = Controller::GetControllerInfo(type);
|
|
|
|
if (SplitWindowSidebarItem(
|
|
TinyString::from_format(fmt::runtime(FSUI_ICONVSTR(ci ? ci->icon_name : ICON_FA_PLUG, "Controller Port {}")),
|
|
Controller::GetPortDisplayName(mtap_port, mtap_slot, mtap_enabled[mtap_port])),
|
|
ci->GetDisplayName(), (s_settings_locals.selected_controller_port == static_cast<s8>(global_slot))))
|
|
{
|
|
BeginTransition(DEFAULT_TRANSITION_TIME, [global_slot]() {
|
|
s_settings_locals.selected_controller_port = static_cast<s8>(global_slot);
|
|
FullscreenUI::FocusSplitWindowContent();
|
|
});
|
|
}
|
|
}
|
|
|
|
const bool show_hotkeys = !IsEditingGameSettings(bsi);
|
|
if (show_hotkeys && SplitWindowSidebarItem(FSUI_ICONVSTR(ICON_PF_KEYBOARD_ALT, "Hotkeys"),
|
|
(s_settings_locals.selected_controller_port == -2)))
|
|
{
|
|
BeginTransition(DEFAULT_TRANSITION_TIME, []() {
|
|
s_settings_locals.selected_controller_port = -2;
|
|
FullscreenUI::FocusSplitWindowContent();
|
|
});
|
|
}
|
|
|
|
EndSplitWindowSidebar();
|
|
|
|
BeginSplitWindowContent(false);
|
|
|
|
BeginMenuButtons();
|
|
|
|
static constexpr auto content_done = []() {
|
|
EndMenuButtons();
|
|
EndSplitWindowContent();
|
|
EndInnerSplitWindow();
|
|
};
|
|
|
|
if (s_settings_locals.selected_controller_port == -1)
|
|
{
|
|
MenuHeading(FSUI_VSTR("Configuration"));
|
|
ResetSplitWindowContentFocusHere();
|
|
|
|
if (IsEditingGameSettings(bsi))
|
|
{
|
|
if (DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_GEARS, "Per-Game Configuration"),
|
|
FSUI_VSTR("Uses game-specific settings for controllers for this game."), "ControllerPorts",
|
|
"UseGameSettingsForController", false, IsEditingGameSettings(bsi), false))
|
|
{
|
|
// did we just enable per-game for the first time?
|
|
if (bsi->GetBoolValue("ControllerPorts", "UseGameSettingsForController", false) &&
|
|
!bsi->GetBoolValue("ControllerPorts", "GameSettingsInitialized", false))
|
|
{
|
|
bsi->SetBoolValue("ControllerPorts", "GameSettingsInitialized", true);
|
|
CopyGlobalControllerSettingsToGame();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsEditingGameSettings(bsi) && !bsi->GetBoolValue("ControllerPorts", "UseGameSettingsForController", false))
|
|
{
|
|
// nothing to edit..
|
|
content_done();
|
|
return;
|
|
}
|
|
|
|
if (IsEditingGameSettings(bsi))
|
|
{
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_COPY, "Copy Global Settings"),
|
|
FSUI_VSTR("Copies the global controller configuration to this game.")))
|
|
{
|
|
CopyGlobalControllerSettingsToGame();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_DUMPSTER_FIRE, "Reset Settings"),
|
|
FSUI_VSTR("Resets all configuration to defaults (including bindings).")))
|
|
{
|
|
BeginResetControllerSettings();
|
|
}
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_FOLDER_OPEN, "Load Preset"),
|
|
FSUI_VSTR("Replaces these settings with a previously saved controller preset.")))
|
|
{
|
|
DoLoadInputProfile();
|
|
}
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_FLOPPY_DISK, "Save Preset"),
|
|
FSUI_VSTR("Stores the current settings to a controller preset.")))
|
|
{
|
|
DoSaveInputProfile();
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Input Sources"));
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_GEAR, "Enable SDL Input Source"),
|
|
FSUI_VSTR("The SDL input source supports most controllers."), "InputSources", "SDL", true, true,
|
|
false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_WIFI, "SDL DualShock 4 / DualSense Enhanced Mode"),
|
|
FSUI_VSTR("Provides vibration and LED control support over Bluetooth."), "InputSources",
|
|
"SDLControllerEnhancedMode", false, bsi->GetBoolValue("InputSources", "SDL", true), false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIGHTBULB, "SDL DualSense Player LED"),
|
|
FSUI_VSTR("Enable/Disable the Player LED on DualSense controllers."), "InputSources",
|
|
"SDLPS5PlayerLED", false, bsi->GetBoolValue("InputSources", "SDL", true), false);
|
|
#ifdef _WIN32
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_GEAR, "Enable XInput Input Source"),
|
|
FSUI_VSTR("Support for controllers that use the XInput protocol. XInput should only be used if you "
|
|
"are using a XInput wrapper library."),
|
|
"InputSources", "XInput", false);
|
|
#endif
|
|
|
|
MenuHeading(FSUI_VSTR("Multitap"));
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_SITEMAP, "Multitap Mode"),
|
|
FSUI_VSTR("Enables an additional three controller slots on each port. Not supported in all games."),
|
|
"ControllerPorts", "MultitapMode", Settings::DEFAULT_MULTITAP_MODE,
|
|
&Settings::ParseMultitapModeName, &Settings::GetMultitapModeName,
|
|
&Settings::GetMultitapModeDisplayName, MultitapMode::Count);
|
|
}
|
|
else if (s_settings_locals.selected_controller_port == -2)
|
|
{
|
|
ResetSplitWindowContentFocusHere();
|
|
|
|
const HotkeyInfo* last_category = nullptr;
|
|
for (const HotkeyInfo* hotkey : s_settings_locals.hotkey_list_cache)
|
|
{
|
|
if (!last_category || std::strcmp(hotkey->category, last_category->category) != 0)
|
|
{
|
|
MenuHeading(Host::TranslateToStringView("Hotkeys", hotkey->category));
|
|
last_category = hotkey;
|
|
}
|
|
|
|
DrawInputBindingButton(bsi, InputBindingInfo::Type::Button, "Hotkeys", hotkey->name,
|
|
Host::TranslateToStringView("Hotkeys", hotkey->display_name), std::string_view(), false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// create the ports
|
|
const u32 global_slot = static_cast<u32>(s_settings_locals.selected_controller_port);
|
|
const auto [mtap_port, mtap_slot] = Controller::ConvertPadToPortAndSlot(global_slot);
|
|
const bool is_mtap_port = Controller::PortAndSlotIsMultitap(mtap_port, mtap_slot);
|
|
if (is_mtap_port && !mtap_enabled[mtap_port])
|
|
{
|
|
content_done();
|
|
return;
|
|
}
|
|
|
|
ImGui::PushID(TinyString::from_format("port_{}", global_slot));
|
|
|
|
MenuHeading(FSUI_VSTR("Controller Type"));
|
|
|
|
ResetSplitWindowContentFocusHere();
|
|
|
|
const TinyString section = TinyString::from_format("Pad{}", global_slot + 1);
|
|
const TinyString type = bsi->GetTinyStringValue(
|
|
section.c_str(), "Type", Controller::GetControllerInfo(Settings::GetDefaultControllerType(global_slot)).name);
|
|
const Controller::ControllerInfo* ci = Controller::GetControllerInfo(type);
|
|
TinyString value;
|
|
if (ci && ci->icon_name)
|
|
value.format("{} {}", ci->icon_name, ci->GetDisplayName());
|
|
else if (ci)
|
|
value = ci->GetDisplayName();
|
|
else
|
|
value = FSUI_VSTR("Unknown");
|
|
|
|
if (MenuButtonWithValue(
|
|
TinyString::from_format("{}##type{}", FSUI_ICONVSTR(ICON_FA_GAMEPAD, "Controller Type"), global_slot),
|
|
FSUI_VSTR("Selects the type of emulated controller for this port."), value))
|
|
{
|
|
const auto& infos = Controller::GetControllerInfoList();
|
|
ChoiceDialogOptions options;
|
|
options.reserve(infos.size());
|
|
for (const Controller::ControllerInfo* it : infos)
|
|
{
|
|
if (it->icon_name)
|
|
options.emplace_back(fmt::format("{} {}", it->icon_name, it->GetDisplayName()), type == it->name);
|
|
else
|
|
options.emplace_back(it->GetDisplayName(), type == it->name);
|
|
}
|
|
|
|
OpenChoiceDialog(TinyString::from_format(FSUI_FSTR("Port {} Controller Type"), global_slot + 1), false,
|
|
std::move(options),
|
|
[game_settings, section, infos](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
bsi->SetStringValue(section.c_str(), "Type", infos[index]->name);
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
|
|
if (!ci || ci->bindings.empty())
|
|
{
|
|
ImGui::PopID();
|
|
content_done();
|
|
return;
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_WAND_MAGIC_SPARKLES, "Automatic Mapping"),
|
|
FSUI_VSTR("Attempts to map the selected port to a chosen controller.")))
|
|
{
|
|
StartAutomaticBindingForPort(global_slot);
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_TRASH, "Clear Mappings"),
|
|
FSUI_VSTR("Removes all bindings for this controller port.")))
|
|
{
|
|
StartClearBindingsForPort(global_slot);
|
|
}
|
|
|
|
MenuHeading(FSUI_ICONVSTR(ICON_FA_MICROCHIP, "Bindings"));
|
|
|
|
for (const Controller::ControllerBindingInfo& bi : ci->bindings)
|
|
{
|
|
DrawInputBindingButton(bsi, bi.type, section.c_str(), bi.name, ci->GetBindingDisplayName(bi),
|
|
bi.icon_name ? std::string_view(bi.icon_name) : std::string_view(), true);
|
|
}
|
|
|
|
if (!ci->settings.empty())
|
|
{
|
|
MenuHeading(FSUI_ICONVSTR(ICON_FA_SLIDERS, "Settings"));
|
|
|
|
for (const SettingInfo& si : ci->settings)
|
|
{
|
|
TinyString title;
|
|
title.format(ICON_FA_GEAR "{}", Host::TranslateToStringView(ci->name, si.display_name));
|
|
std::string_view description = Host::TranslateToStringView(ci->name, si.description);
|
|
switch (si.type)
|
|
{
|
|
case SettingInfo::Type::Boolean:
|
|
{
|
|
DrawToggleSetting(bsi, title, description, section.c_str(), si.name, si.BooleanDefaultValue(), true, false);
|
|
}
|
|
break;
|
|
|
|
case SettingInfo::Type::Integer:
|
|
{
|
|
DrawIntRangeSetting(bsi, title, description, section.c_str(), si.name, si.IntegerDefaultValue(),
|
|
si.IntegerMinValue(), si.IntegerMaxValue(), si.format, true);
|
|
}
|
|
break;
|
|
|
|
case SettingInfo::Type::IntegerList:
|
|
{
|
|
size_t option_count = 0;
|
|
if (si.options)
|
|
{
|
|
while (si.options[option_count])
|
|
option_count++;
|
|
}
|
|
|
|
DrawIntListSetting(bsi, title, description, section.c_str(), si.name, si.IntegerDefaultValue(),
|
|
std::span<const char* const>(si.options, option_count), true, si.IntegerMinValue(), true,
|
|
ci->name);
|
|
}
|
|
break;
|
|
|
|
case SettingInfo::Type::Float:
|
|
{
|
|
DrawFloatSpinBoxSetting(bsi, title, description, section.c_str(), si.name, si.FloatDefaultValue(),
|
|
si.FloatMinValue(), si.FloatMaxValue(), si.FloatStepValue(), si.multiplier,
|
|
si.format, true);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (u32 macro_index = 0; macro_index < InputManager::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_index++)
|
|
{
|
|
ImGui::PushID(TinyString::from_format("macro_{}", macro_index));
|
|
|
|
MenuHeading(
|
|
SmallString::from_format(fmt::runtime(FSUI_ICONVSTR(ICON_PF_EMPTY_KEYCAP, "Macro {}")), macro_index + 1));
|
|
|
|
DrawInputBindingButton(bsi, InputBindingInfo::Type::Macro, section.c_str(),
|
|
TinyString::from_format("Macro{}", macro_index + 1), FSUI_CSTR("Trigger"),
|
|
std::string_view(), true);
|
|
|
|
SmallString binds_string =
|
|
bsi->GetSmallStringValue(section.c_str(), TinyString::from_format("Macro{}Binds", macro_index + 1).c_str());
|
|
TinyString pretty_binds_string;
|
|
if (!binds_string.empty())
|
|
{
|
|
for (const std::string_view& bind : StringUtil::SplitString(binds_string, '&', true))
|
|
{
|
|
std::string_view dispname;
|
|
for (const Controller::ControllerBindingInfo& bi : ci->bindings)
|
|
{
|
|
if (bind == bi.name)
|
|
{
|
|
dispname = bi.icon_name ? std::string_view(bi.icon_name) : ci->GetBindingDisplayName(bi);
|
|
break;
|
|
}
|
|
}
|
|
pretty_binds_string.append_format("{}{}", pretty_binds_string.empty() ? "" : " ", dispname);
|
|
}
|
|
}
|
|
if (MenuButtonWithValue(FSUI_ICONVSTR(ICON_FA_KEYBOARD, "Buttons"), std::string_view(),
|
|
pretty_binds_string.empty() ? FSUI_VSTR("-") : pretty_binds_string.view()))
|
|
{
|
|
std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
|
|
ChoiceDialogOptions options;
|
|
for (const Controller::ControllerBindingInfo& bi : ci->bindings)
|
|
{
|
|
if (bi.type != InputBindingInfo::Type::Button && bi.type != InputBindingInfo::Type::Axis &&
|
|
bi.type != InputBindingInfo::Type::HalfAxis)
|
|
{
|
|
continue;
|
|
}
|
|
options.emplace_back(ci->GetBindingDisplayName(bi),
|
|
std::any_of(buttons_split.begin(), buttons_split.end(),
|
|
[bi](const std::string_view& it) { return (it == bi.name); }));
|
|
}
|
|
|
|
OpenChoiceDialog(
|
|
TinyString::from_format(FSUI_FSTR("Select Macro {} Binds"), macro_index + 1), true, std::move(options),
|
|
[game_settings, section, macro_index, ci](s32 index, const std::string& title, bool checked) {
|
|
// convert display name back to bind name
|
|
std::string_view to_modify;
|
|
for (const Controller::ControllerBindingInfo& bi : ci->bindings)
|
|
{
|
|
if (title == ci->GetBindingDisplayName(bi))
|
|
{
|
|
to_modify = bi.name;
|
|
break;
|
|
}
|
|
}
|
|
if (to_modify.empty())
|
|
{
|
|
// wtf?
|
|
return;
|
|
}
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
const TinyString key = TinyString::from_format("Macro{}Binds", macro_index + 1);
|
|
|
|
std::string binds_string = bsi->GetStringValue(section.c_str(), key.c_str());
|
|
std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
|
|
auto it = std::find(buttons_split.begin(), buttons_split.end(), to_modify);
|
|
if (checked)
|
|
{
|
|
if (it == buttons_split.end())
|
|
buttons_split.push_back(to_modify);
|
|
}
|
|
else
|
|
{
|
|
if (it != buttons_split.end())
|
|
buttons_split.erase(it);
|
|
}
|
|
|
|
binds_string = StringUtil::JoinString(buttons_split.begin(), buttons_split.end(), " & ");
|
|
if (binds_string.empty())
|
|
bsi->DeleteValue(section.c_str(), key.c_str());
|
|
else
|
|
bsi->SetStringValue(section.c_str(), key.c_str(), binds_string.c_str());
|
|
});
|
|
}
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_GAMEPAD, "Press To Toggle"),
|
|
FSUI_VSTR("Toggles the macro when the button is pressed, instead of held."), section.c_str(),
|
|
TinyString::from_format("Macro{}Toggle", macro_index + 1), false, true, false);
|
|
|
|
const TinyString freq_key = TinyString::from_format("Macro{}Frequency", macro_index + 1);
|
|
const TinyString freq_label =
|
|
TinyString::from_format(ICON_FA_CLOCK " {}##macro_{}_frequency", FSUI_VSTR("Frequency"), macro_index + 1);
|
|
s32 frequency = bsi->GetIntValue(section.c_str(), freq_key.c_str(), 0);
|
|
const TinyString freq_summary = ((frequency == 0) ? TinyString(FSUI_VSTR("Disabled")) :
|
|
TinyString::from_format(FSUI_FSTR("{} Frames"), frequency));
|
|
if (MenuButtonWithValue(
|
|
freq_label,
|
|
FSUI_VSTR(
|
|
"Determines the frequency at which the macro will toggle the buttons on and off (aka auto fire)."),
|
|
freq_summary, true))
|
|
{
|
|
OpenFixedPopupDialog(freq_label);
|
|
}
|
|
|
|
DrawFloatSpinBoxSetting(bsi, FSUI_ICONVSTR(ICON_FA_ARROW_DOWN, "Pressure"),
|
|
FSUI_VSTR("Determines how much pressure is simulated when macro is active."), section,
|
|
TinyString::from_format("Macro{}Pressure", macro_index + 1), 1.0f, 0.01f, 1.0f, 0.01f,
|
|
100.0f, "%.0f%%");
|
|
|
|
DrawFloatSpinBoxSetting(bsi, FSUI_ICONVSTR(ICON_FA_SKULL, "Deadzone"),
|
|
FSUI_VSTR("Determines how much button pressure is ignored before activating the macro."),
|
|
section, TinyString::from_format("Macro{}Deadzone", macro_index + 1).c_str(), 0.0f, 0.00f,
|
|
1.0f, 0.01f, 100.0f, "%.0f%%");
|
|
|
|
if (IsFixedPopupDialogOpen(freq_label) &&
|
|
BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(500.0f, 200.0f)))
|
|
{
|
|
BeginMenuButtons();
|
|
|
|
ImGui::SetNextItemWidth(ImGui::GetCurrentWindow()->WorkRect.GetWidth());
|
|
if (ImGui::SliderInt("##value", &frequency, 0, 60,
|
|
(frequency == 0) ? FSUI_CSTR("Disabled") : FSUI_CSTR("Toggle every %d frames"),
|
|
ImGuiSliderFlags_NoInput))
|
|
{
|
|
if (frequency == 0)
|
|
bsi->DeleteValue(section.c_str(), freq_key.c_str());
|
|
else
|
|
bsi->SetIntValue(section.c_str(), freq_key.c_str(), frequency);
|
|
}
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
content_done();
|
|
}
|
|
|
|
void FullscreenUI::DrawMemoryCardSettingsPage()
|
|
{
|
|
static constexpr const std::array type_keys = {"Card1Type", "Card2Type"};
|
|
static constexpr const std::array path_keys = {"Card1Path", "Card2Path"};
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
|
|
BeginMenuButtons();
|
|
|
|
MenuHeading(FSUI_VSTR("Settings and Operations"));
|
|
ResetFocusHere();
|
|
|
|
DrawFolderSetting(bsi, FSUI_ICONVSTR(ICON_FA_FOLDER_OPEN, "Memory Card Directory"), "MemoryCards", "Directory",
|
|
EmuFolders::MemoryCards);
|
|
|
|
if (!game_settings && MenuButton(FSUI_ICONVSTR(ICON_FA_ARROW_ROTATE_LEFT, "Reset Memory Card Directory"),
|
|
FSUI_VSTR("Resets memory card directory to default (user directory).")))
|
|
{
|
|
bsi->SetStringValue("MemoryCards", "Directory", "memcards");
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_SHARE_NODES, "Use Single Card For Multi-Disc Games"),
|
|
FSUI_VSTR("When playing a multi-disc game and using per-game (title) memory cards, "
|
|
"use a single memory card for all discs."),
|
|
"MemoryCards", "UsePlaylistTitle", true);
|
|
|
|
for (u32 i = 0; i < 2; i++)
|
|
{
|
|
MenuHeading(TinyString::from_format(FSUI_FSTR("Memory Card Port {}"), i + 1));
|
|
|
|
const MemoryCardType default_type =
|
|
(i == 0) ? Settings::DEFAULT_MEMORY_CARD_1_TYPE : Settings::DEFAULT_MEMORY_CARD_2_TYPE;
|
|
DrawEnumSetting(
|
|
bsi, TinyString::from_format(fmt::runtime(FSUI_ICONVSTR(ICON_PF_MEMORY_CARD, "Memory Card {} Type")), i + 1),
|
|
SmallString::from_format(FSUI_FSTR("Sets which sort of memory card image will be used for slot {}."), i + 1),
|
|
"MemoryCards", type_keys[i], default_type, &Settings::ParseMemoryCardTypeName, &Settings::GetMemoryCardTypeName,
|
|
&Settings::GetMemoryCardTypeDisplayName, MemoryCardType::Count);
|
|
|
|
const MemoryCardType effective_type =
|
|
Settings::ParseMemoryCardTypeName(
|
|
GetEffectiveTinyStringSetting(bsi, "MemoryCards", type_keys[i], Settings::GetMemoryCardTypeName(default_type))
|
|
.c_str())
|
|
.value_or(default_type);
|
|
const bool is_shared = (effective_type == MemoryCardType::Shared);
|
|
std::optional<SmallString> path_value(bsi->GetOptionalSmallStringValue(
|
|
"MemoryCards", path_keys[i],
|
|
IsEditingGameSettings(bsi) ? std::nullopt :
|
|
std::optional<const char*>((i == 0) ? "shared_card_1.mcd" : "shared_card_2.mcd")));
|
|
|
|
TinyString title;
|
|
title.format("{}##card_name_{}", FSUI_ICONVSTR(ICON_FA_FILE, "Shared Card Name"), i);
|
|
if (MenuButtonWithValue(title,
|
|
FSUI_VSTR("The selected memory card image will be used in shared mode for this slot."),
|
|
path_value.has_value() ? path_value->view() : FSUI_VSTR("Use Global Setting"), is_shared))
|
|
{
|
|
ChoiceDialogOptions options;
|
|
std::vector<std::string> names;
|
|
if (IsEditingGameSettings(bsi))
|
|
options.emplace_back("Use Global Setting", !path_value.has_value());
|
|
if (path_value.has_value() && !path_value->empty())
|
|
{
|
|
options.emplace_back(fmt::format("{} (Current)", path_value.value()), true);
|
|
names.emplace_back(path_value.value().view());
|
|
}
|
|
|
|
FileSystem::FindResultsArray results;
|
|
FileSystem::FindFiles(EmuFolders::MemoryCards.c_str(), "*.mcd",
|
|
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS,
|
|
&results);
|
|
for (FILESYSTEM_FIND_DATA& ffd : results)
|
|
{
|
|
const bool selected = (path_value.has_value() && path_value.value() == ffd.FileName);
|
|
options.emplace_back(std::move(ffd.FileName), selected);
|
|
}
|
|
|
|
OpenChoiceDialog(
|
|
title, false, std::move(options),
|
|
[game_settings = IsEditingGameSettings(bsi), i](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings && index == 0)
|
|
{
|
|
bsi->DeleteValue("MemoryCards", path_keys[i]);
|
|
}
|
|
else
|
|
{
|
|
if (game_settings)
|
|
index--;
|
|
bsi->SetStringValue("MemoryCards", path_keys[i], title.c_str());
|
|
}
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
}
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawGraphicsSettingsPage()
|
|
{
|
|
static constexpr const std::array resolution_scales = {
|
|
FSUI_NSTR("Automatic based on window size"),
|
|
FSUI_NSTR("1x"),
|
|
FSUI_NSTR("2x"),
|
|
FSUI_NSTR("3x (for 720p)"),
|
|
FSUI_NSTR("4x"),
|
|
FSUI_NSTR("5x (for 1080p)"),
|
|
FSUI_NSTR("6x (for 1440p)"),
|
|
FSUI_NSTR("7x"),
|
|
FSUI_NSTR("8x"),
|
|
FSUI_NSTR("9x (for 4K)"),
|
|
FSUI_NSTR("10x"),
|
|
FSUI_NSTR("11x"),
|
|
FSUI_NSTR("12x"),
|
|
FSUI_NSTR("13x"),
|
|
FSUI_NSTR("14x"),
|
|
FSUI_NSTR("15x"),
|
|
FSUI_NSTR("16x"),
|
|
};
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
const bool game_settings = IsEditingGameSettings(bsi);
|
|
const u32 resolution_scale = GetEffectiveUIntSetting(bsi, "GPU", "ResolutionScale", 1);
|
|
|
|
BeginMenuButtons();
|
|
|
|
MenuHeading(FSUI_VSTR("Device Settings"));
|
|
|
|
ResetFocusHere();
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_PF_PICTURE, "GPU Renderer"),
|
|
FSUI_VSTR("Selects the backend to use for rendering the console/game visuals."), "GPU", "Renderer",
|
|
Settings::DEFAULT_GPU_RENDERER, &Settings::ParseRendererName, &Settings::GetRendererName,
|
|
&Settings::GetRendererDisplayName, GPURenderer::Count);
|
|
|
|
const GPURenderer renderer =
|
|
Settings::ParseRendererName(
|
|
GetEffectiveTinyStringSetting(bsi, "GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER))
|
|
.c_str())
|
|
.value_or(Settings::DEFAULT_GPU_RENDERER);
|
|
const bool is_hardware = (renderer != GPURenderer::Software);
|
|
|
|
std::optional<SmallString> current_adapter =
|
|
bsi->GetOptionalSmallStringValue("GPU", "Adapter", game_settings ? std::nullopt : std::optional<const char*>(""));
|
|
|
|
if (MenuButtonWithValue(
|
|
FSUI_ICONVSTR(ICON_PF_GPU_GRAPHICS_CARD, "GPU Adapter"), FSUI_VSTR("Selects the GPU to use for rendering."),
|
|
current_adapter.has_value() ? (current_adapter->empty() ? FSUI_VSTR("Default") : current_adapter->view()) :
|
|
FSUI_VSTR("Use Global Setting")))
|
|
{
|
|
ChoiceDialogOptions options;
|
|
options.reserve(s_settings_locals.graphics_adapter_list_cache.size() + 2);
|
|
if (game_settings)
|
|
options.emplace_back(FSUI_STR("Use Global Setting"), !current_adapter.has_value());
|
|
options.emplace_back(FSUI_STR("Default"), current_adapter.has_value() && current_adapter->empty());
|
|
for (const GPUDevice::AdapterInfo& adapter : s_settings_locals.graphics_adapter_list_cache)
|
|
{
|
|
const bool checked = (current_adapter.has_value() && current_adapter.value() == adapter.name);
|
|
options.emplace_back(adapter.name, checked);
|
|
}
|
|
|
|
auto callback = [game_settings](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const char* value;
|
|
if (game_settings && index == 0)
|
|
value = nullptr;
|
|
else if ((!game_settings && index == 0) || (game_settings && index == 1))
|
|
value = "";
|
|
else
|
|
value = title.c_str();
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (!value)
|
|
bsi->DeleteValue("GPU", "Adapter");
|
|
else
|
|
bsi->SetStringValue("GPU", "Adapter", value);
|
|
SetSettingsChanged(bsi);
|
|
};
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_PF_GPU_GRAPHICS_CARD, "GPU Adapter"), false, std::move(options),
|
|
std::move(callback));
|
|
}
|
|
|
|
const bool pgxp_enabled = (is_hardware && GetEffectiveBoolSetting(bsi, "GPU", "PGXPEnable", false));
|
|
const bool texture_correction_enabled =
|
|
(pgxp_enabled && GetEffectiveBoolSetting(bsi, "GPU", "PGXPTextureCorrection", true));
|
|
|
|
MenuHeading(FSUI_VSTR("Rendering"));
|
|
|
|
if (is_hardware)
|
|
{
|
|
DrawIntListSetting(bsi, FSUI_ICONVSTR(ICON_FA_EXPAND, "Internal Resolution"),
|
|
FSUI_VSTR("Upscales the game's rendering by the specified multiplier."), "GPU",
|
|
"ResolutionScale", 1, resolution_scales);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_COMPRESS, "Downsampling"),
|
|
FSUI_VSTR("Downsamples the rendered image prior to displaying it. Can improve "
|
|
"overall image quality in mixed 2D/3D games."),
|
|
"GPU", "DownsampleMode", Settings::DEFAULT_GPU_DOWNSAMPLE_MODE, &Settings::ParseDownsampleModeName,
|
|
&Settings::GetDownsampleModeName, &Settings::GetDownsampleModeDisplayName, GPUDownsampleMode::Count,
|
|
(renderer != GPURenderer::Software));
|
|
if (Settings::ParseDownsampleModeName(
|
|
GetEffectiveTinyStringSetting(bsi, "GPU", "DownsampleMode",
|
|
Settings::GetDownsampleModeName(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE))
|
|
.c_str())
|
|
.value_or(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE) == GPUDownsampleMode::Box)
|
|
{
|
|
DrawIntRangeSetting(bsi, FSUI_ICONVSTR(ICON_FA_COMPRESS, "Downsampling Display Scale"),
|
|
FSUI_VSTR("Selects the resolution scale that will be applied to the final image. 1x will "
|
|
"downsample to the original console resolution."),
|
|
"GPU", "DownsampleScale", 1, 1, GPU::MAX_RESOLUTION_SCALE, "%dx");
|
|
}
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_TABLE_CELLS, "Texture Filtering"),
|
|
FSUI_VSTR("Smooths out the blockiness of magnified textures on 3D objects."), "GPU",
|
|
"TextureFilter", Settings::DEFAULT_GPU_TEXTURE_FILTER, &Settings::ParseTextureFilterName,
|
|
&Settings::GetTextureFilterName, &Settings::GetTextureFilterDisplayName, GPUTextureFilter::Count);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_SQUARE_ARROW_UP_RIGHT, "Sprite Texture Filtering"),
|
|
FSUI_VSTR("Smooths out the blockiness of magnified textures on 2D objects."), "GPU",
|
|
"SpriteTextureFilter", Settings::DEFAULT_GPU_TEXTURE_FILTER, &Settings::ParseTextureFilterName,
|
|
&Settings::GetTextureFilterName, &Settings::GetTextureFilterDisplayName, GPUTextureFilter::Count);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_DROPLET_SLASH, "Dithering"),
|
|
FSUI_VSTR("Controls how dithering is applied in the emulated GPU. True Color disables dithering "
|
|
"and produces the nicest looking gradients."),
|
|
"GPU", "DitheringMode", Settings::DEFAULT_GPU_DITHERING_MODE, &Settings::ParseGPUDitheringModeName,
|
|
&Settings::GetGPUDitheringModeName, &Settings::GetGPUDitheringModeDisplayName,
|
|
GPUDitheringMode::MaxCount);
|
|
}
|
|
|
|
static constexpr const char* ASPECT_RATIO_SECTION = "Display";
|
|
static constexpr const char* ASPECT_RATIO_KEY = "AspectRatio";
|
|
if (MenuButtonWithValue(FSUI_ICONVSTR(ICON_FA_SHAPES, "Aspect Ratio"),
|
|
FSUI_VSTR("Changes the aspect ratio used to display the console's output to the screen."),
|
|
(game_settings && !bsi->ContainsValue(ASPECT_RATIO_SECTION, ASPECT_RATIO_KEY)) ?
|
|
TinyString(FSUI_VSTR("Use Global Setting")) :
|
|
Settings::GetDisplayAspectRatioDisplayName(
|
|
Settings::ParseDisplayAspectRatio(
|
|
GetEffectiveTinyStringSetting(bsi, ASPECT_RATIO_SECTION, ASPECT_RATIO_KEY, ""))
|
|
.value_or(Settings::DEFAULT_DISPLAY_ASPECT_RATIO))))
|
|
{
|
|
static constexpr const DisplayAspectRatio INHERIT_ASPECT_RATIO = {0, -1};
|
|
ChoiceDialogOptions options;
|
|
const DisplayAspectRatio current_ar =
|
|
(bsi && !bsi->ContainsValue(ASPECT_RATIO_SECTION, ASPECT_RATIO_KEY)) ?
|
|
INHERIT_ASPECT_RATIO :
|
|
Settings::ParseDisplayAspectRatio(
|
|
GetEffectiveTinyStringSetting(bsi, ASPECT_RATIO_SECTION, ASPECT_RATIO_KEY, "").c_str())
|
|
.value_or(Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
|
|
if (game_settings)
|
|
{
|
|
options.emplace_back(FSUI_STR("Use Global Setting"), current_ar == INHERIT_ASPECT_RATIO);
|
|
}
|
|
for (const DisplayAspectRatio& ratio : Settings::GetPredefinedDisplayAspectRatios())
|
|
options.emplace_back(Settings::GetDisplayAspectRatioDisplayName(ratio), current_ar == ratio);
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_SHAPES, "Aspect Ratio"), false, std::move(options),
|
|
[game_settings](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (game_settings && index == 0)
|
|
{
|
|
bsi->DeleteValue(ASPECT_RATIO_SECTION, ASPECT_RATIO_KEY);
|
|
}
|
|
else
|
|
{
|
|
bsi->SetStringValue(
|
|
ASPECT_RATIO_SECTION, ASPECT_RATIO_KEY,
|
|
Settings::GetDisplayAspectRatioName(
|
|
Settings::GetPredefinedDisplayAspectRatios()[game_settings ? (index - 1) : index]));
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
});
|
|
}
|
|
|
|
DrawEnumSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_GRIP_LINES, "Deinterlacing Mode"),
|
|
FSUI_VSTR(
|
|
"Determines which algorithm is used to convert interlaced frames to progressive for display on your system."),
|
|
"GPU", "DeinterlacingMode", Settings::DEFAULT_DISPLAY_DEINTERLACING_MODE, &Settings::ParseDisplayDeinterlacingMode,
|
|
&Settings::GetDisplayDeinterlacingModeName, &Settings::GetDisplayDeinterlacingModeDisplayName,
|
|
DisplayDeinterlacingMode::Count);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_CROP, "Crop Mode"),
|
|
FSUI_VSTR("Determines how much of the area typically not visible on a consumer TV set to crop/hide."),
|
|
"Display", "CropMode", Settings::DEFAULT_DISPLAY_CROP_MODE, &Settings::ParseDisplayCropMode,
|
|
&Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName,
|
|
DisplayCropMode::MaxCount);
|
|
|
|
DrawEnumSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_EXPAND, "Scaling"),
|
|
FSUI_VSTR("Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution."),
|
|
"Display", "Scaling", Settings::DEFAULT_DISPLAY_SCALING, &Settings::ParseDisplayScaling,
|
|
&Settings::GetDisplayScalingName, &Settings::GetDisplayScalingDisplayName, DisplayScalingMode::Count);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_VIDEO, "FMV Scaling"),
|
|
FSUI_VSTR("Determines the scaling algorithm used when 24-bit content is active, typically FMVs."),
|
|
"Display", "Scaling24Bit", Settings::DEFAULT_DISPLAY_SCALING, &Settings::ParseDisplayScaling,
|
|
&Settings::GetDisplayScalingName, &Settings::GetDisplayScalingDisplayName, DisplayScalingMode::Count);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE, "Widescreen Rendering"),
|
|
FSUI_VSTR("Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."),
|
|
"GPU", "WidescreenHack", false);
|
|
|
|
if (is_hardware)
|
|
{
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_BEZIER_CURVE, "PGXP Geometry Correction"),
|
|
FSUI_VSTR("Reduces \"wobbly\" polygons by attempting to preserve the fractional component through memory "
|
|
"transfers."),
|
|
"GPU", "PGXPEnable", false);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_SITEMAP, "PGXP Depth Buffer"),
|
|
FSUI_VSTR("Reduces polygon Z-fighting through depth testing. Low compatibility with games."),
|
|
"GPU", "PGXPDepthBuffer", false, pgxp_enabled && texture_correction_enabled);
|
|
|
|
const GPUTextureFilter texture_filtering =
|
|
Settings::ParseTextureFilterName(GetEffectiveTinyStringSetting(bsi, "GPU", "TextureFilter"))
|
|
.value_or(Settings::DEFAULT_GPU_TEXTURE_FILTER);
|
|
const GPUTextureFilter sprite_texture_filtering =
|
|
Settings::ParseTextureFilterName(GetEffectiveTinyStringSetting(bsi, "GPU", "SpriteTextureFilter"))
|
|
.value_or(texture_filtering);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_EYE_DROPPER, "Round Upscaled Texture Coordinates"),
|
|
FSUI_VSTR("Rounds texture coordinates instead of flooring when upscaling. Can fix misaligned "
|
|
"textures in some games, but break others, and is incompatible with texture filtering."),
|
|
"GPU", "ForceRoundTextureCoordinates", false,
|
|
resolution_scale != 1 &&
|
|
(texture_filtering == GPUTextureFilter::Nearest || sprite_texture_filtering == GPUTextureFilter::Nearest));
|
|
}
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_COMPRESS, "Force 4:3 For FMVs"),
|
|
FSUI_VSTR("Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs."), "Display",
|
|
"Force4_3For24Bit", false);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_PAINT_ROLLER, "FMV Chroma Smoothing"),
|
|
FSUI_VSTR("Smooths out blockyness between colour transitions in 24-bit content, usually FMVs."),
|
|
"GPU", "ChromaSmoothing24Bit", false);
|
|
|
|
MenuHeading(FSUI_VSTR("Advanced Display Options"));
|
|
|
|
std::optional<SmallString> strvalue = bsi->GetOptionalSmallStringValue(
|
|
"GPU", "FullscreenMode", game_settings ? std::nullopt : std::optional<const char*>(""));
|
|
|
|
if (MenuButtonWithValue(FSUI_ICONVSTR(ICON_FA_TV, "Fullscreen Resolution"),
|
|
FSUI_VSTR("Selects the resolution to use in fullscreen modes."),
|
|
strvalue.has_value() ?
|
|
(strvalue->empty() ? FSUI_VSTR("Borderless Fullscreen") : strvalue->view()) :
|
|
FSUI_VSTR("Use Global Setting")))
|
|
{
|
|
const GPUDevice::AdapterInfo* selected_adapter = nullptr;
|
|
if (current_adapter.has_value())
|
|
{
|
|
for (const GPUDevice::AdapterInfo& ai : s_settings_locals.graphics_adapter_list_cache)
|
|
{
|
|
if (ai.name == current_adapter->view())
|
|
{
|
|
selected_adapter = &ai;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!s_settings_locals.graphics_adapter_list_cache.empty())
|
|
selected_adapter = &s_settings_locals.graphics_adapter_list_cache.front();
|
|
}
|
|
|
|
ChoiceDialogOptions options;
|
|
options.reserve((selected_adapter ? selected_adapter->fullscreen_modes.size() : 0) + 2);
|
|
if (game_settings)
|
|
options.emplace_back(FSUI_STR("Use Global Setting"), !strvalue.has_value());
|
|
options.emplace_back(FSUI_STR("Borderless Fullscreen"), strvalue.has_value() && strvalue->empty());
|
|
if (selected_adapter)
|
|
{
|
|
for (const GPUDevice::ExclusiveFullscreenMode& mode : selected_adapter->fullscreen_modes)
|
|
{
|
|
const TinyString mode_str = mode.ToString();
|
|
const bool checked = (strvalue.has_value() && strvalue.value() == mode_str);
|
|
options.emplace_back(std::string(mode_str.view()), checked);
|
|
}
|
|
}
|
|
|
|
auto callback = [game_settings](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const char* value;
|
|
if (game_settings && index == 0)
|
|
value = nullptr;
|
|
else if ((!game_settings && index == 0) || (game_settings && index == 1))
|
|
value = "";
|
|
else
|
|
value = title.c_str();
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
|
if (!value)
|
|
bsi->DeleteValue("GPU", "FullscreenMode");
|
|
else
|
|
bsi->SetStringValue("GPU", "FullscreenMode", value);
|
|
SetSettingsChanged(bsi);
|
|
ShowToast(OSDMessageType::Info, std::string(), FSUI_STR("Resolution change will be applied after restarting."));
|
|
};
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_TV, "Fullscreen Resolution"), false, std::move(options),
|
|
std::move(callback));
|
|
}
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT, "Screen Position"),
|
|
FSUI_VSTR("Determines the position on the screen when black borders must be added."), "Display",
|
|
"Alignment", Settings::DEFAULT_DISPLAY_ALIGNMENT, &Settings::ParseDisplayAlignment,
|
|
&Settings::GetDisplayAlignmentName, &Settings::GetDisplayAlignmentDisplayName,
|
|
DisplayAlignment::Count);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_ARROWS_SPIN, "Screen Rotation"),
|
|
FSUI_VSTR("Determines the rotation of the simulated TV screen."), "Display", "Rotation",
|
|
Settings::DEFAULT_DISPLAY_ROTATION, &Settings::ParseDisplayRotation,
|
|
&Settings::GetDisplayRotationName, &Settings::GetDisplayRotationDisplayName, DisplayRotation::Count);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_CROP_SIMPLE, "Fine Crop Mode"),
|
|
FSUI_VSTR("Enables manual fine cropping of the display area, while preserving the aspect ratio of "
|
|
"the image. Useful for removing black borders in certain games."),
|
|
"Display", "FineCropMode", Settings::DEFAULT_DISPLAY_FINE_CROP_MODE,
|
|
&Settings::ParseDisplayFineCropMode, &Settings::GetDisplayFineCropModeName,
|
|
&Settings::GetDisplayFineCropModeDisplayName, DisplayFineCropMode::MaxCount);
|
|
if (Settings::ParseDisplayFineCropMode(GetEffectiveTinyStringSetting(bsi, "Display", "FineCropMode", "").c_str())
|
|
.value_or(Settings::DEFAULT_DISPLAY_FINE_CROP_MODE) != DisplayFineCropMode::None)
|
|
{
|
|
DrawIntRectSetting(bsi, FSUI_ICONVSTR(ICON_FA_CROP_SIMPLE, "Fine Crop Amount"),
|
|
FSUI_VSTR("Determines how much to crop the display area."), "Display", "FineCropLeft", 0,
|
|
"FineCropTop", 0, "FineCropRight", 0, "FineCropBottom", 0, std::numeric_limits<s16>::min(),
|
|
std::numeric_limits<s16>::max(), "%dpx");
|
|
}
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_ENVELOPE, "Disable Mailbox Presentation"),
|
|
FSUI_VSTR("Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. "
|
|
"Usually results in worse frame pacing."),
|
|
"Display", "DisableMailboxPresentation", false);
|
|
|
|
#ifdef _WIN32
|
|
if (renderer == GPURenderer::HardwareD3D11 || renderer == GPURenderer::Software)
|
|
{
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_PAINTBRUSH, "Use Blit Swap Chain"),
|
|
FSUI_VSTR("Uses a blit presentation model instead of flipping. This may be needed on some systems."), "Display",
|
|
"UseBlitSwapChain", false);
|
|
}
|
|
#endif
|
|
|
|
MenuHeading(FSUI_VSTR("Advanced Rendering Options"));
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BOLT, "Threaded Rendering"),
|
|
FSUI_VSTR("Uses a second thread for drawing graphics. Provides a significant speed improvement "
|
|
"particularly with the software renderer, and is safe to use."),
|
|
"GPU", "UseThread", true);
|
|
|
|
if (is_hardware)
|
|
{
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_GRIP_LINES_VERTICAL, "Line Detection"),
|
|
FSUI_VSTR("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization "
|
|
"behavior, filling in gaps introduced by upscaling."),
|
|
"GPU", "LineDetectMode", Settings::DEFAULT_GPU_LINE_DETECT_MODE, &Settings::ParseLineDetectModeName,
|
|
&Settings::GetLineDetectModeName, &Settings::GetLineDetectModeDisplayName, GPULineDetectMode::Count,
|
|
resolution_scale > 1);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_DROPLET_SLASH, "Scaled Interlacing"),
|
|
FSUI_VSTR("Scales line skipping in interlaced rendering to the internal resolution, making it "
|
|
"less noticeable. Usually safe to enable."),
|
|
"GPU", "ScaledInterlacing", true, resolution_scale > 1);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_SWATCHBOOK, "Texture Modulation Cropping (\"Old/v0\" GPU)"),
|
|
FSUI_VSTR("Crops vertex colours to 5:5:5 before modulating with the texture colour, which "
|
|
"typically results in more visible banding."),
|
|
"GPU", "EnableModulationCrop", false);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_DOWNLOAD, "Use Software Renderer For Readbacks"),
|
|
FSUI_VSTR("Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in "
|
|
"greater performance when using graphical enhancements with the hardware renderer."),
|
|
"GPU", "UseSoftwareRendererForReadbacks", false);
|
|
}
|
|
|
|
if (is_hardware && pgxp_enabled)
|
|
{
|
|
MenuHeading(FSUI_VSTR("PGXP (Precision Geometry Transform Pipeline)"));
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_IMAGES, "Perspective Correct Textures"),
|
|
FSUI_VSTR("Uses perspective-correct interpolation for texture coordinates, straightening out warped textures."),
|
|
"GPU", "PGXPTextureCorrection", true, pgxp_enabled);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_BRUSH, "Perspective Correct Colors"),
|
|
FSUI_VSTR("Uses perspective-correct interpolation for colors, which can improve visuals in some games."), "GPU",
|
|
"PGXPColorCorrection", false, pgxp_enabled);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_TEXT_SLASH, "Culling Correction"),
|
|
FSUI_VSTR("Increases the precision of polygon culling, reducing the number of holes in geometry."), "GPU",
|
|
"PGXPCulling", true, pgxp_enabled);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_DRAW_POLYGON, "Preserve Projection Precision"),
|
|
FSUI_VSTR("Adds additional precision to PGXP data post-projection. May improve visuals in some games."), "GPU",
|
|
"PGXPPreserveProjFP", false, pgxp_enabled);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_MICROCHIP, "CPU Mode"),
|
|
FSUI_VSTR("Uses PGXP for all instructions, not just memory operations."), "GPU", "PGXPCPU", false,
|
|
pgxp_enabled);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_CIRCLE_NODES, "Vertex Cache"),
|
|
FSUI_VSTR("Uses screen positions to resolve PGXP data. May improve visuals in some games."),
|
|
"GPU", "PGXPVertexCache", pgxp_enabled);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_SQUARE_MINUS, "Disable on 2D Polygons"),
|
|
FSUI_VSTR("Uses native resolution coordinates for 2D polygons, instead of precise coordinates. Can "
|
|
"fix misaligned UI in some games, but otherwise should be left disabled."),
|
|
"GPU", "PGXPDisableOn2DPolygons", false, pgxp_enabled);
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_RULER, "Depth Test Transparent Polygons"),
|
|
FSUI_VSTR("Enables depth testing for semi-transparent polygons. Usually these include shadows, "
|
|
"and tend to clip through the ground when depth testing is enabled."),
|
|
"GPU", "PGXPTransparentDepthTest", false, pgxp_enabled);
|
|
|
|
DrawFloatRangeSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_PENTAGON, "Geometry Tolerance"),
|
|
FSUI_VSTR("Sets a threshold for discarding precise values when exceeded. May help with glitches in some games."),
|
|
"GPU", "PGXPTolerance", -1.0f, -1.0f, 10.0f, "%.1f", pgxp_enabled);
|
|
|
|
DrawFloatRangeSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_CIRCLE_MINUS, "Depth Clear Threshold"),
|
|
FSUI_VSTR("Sets a threshold for discarding the emulated depth buffer. May help in some games."), "GPU",
|
|
"PGXPDepthThreshold", Settings::DEFAULT_GPU_PGXP_DEPTH_THRESHOLD, 0.0f, static_cast<float>(GTE::MAX_Z), "%.1f",
|
|
pgxp_enabled);
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Capture"));
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_CAMERA, "Screenshot Size"),
|
|
FSUI_VSTR("Determines the size of screenshots created by DuckStation."), "Display", "ScreenshotMode",
|
|
Settings::DEFAULT_DISPLAY_SCREENSHOT_MODE, &Settings::ParseDisplayScreenshotMode,
|
|
&Settings::GetDisplayScreenshotModeName, &Settings::GetDisplayScreenshotModeDisplayName,
|
|
DisplayScreenshotMode::Count);
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_FILE_IMAGE, "Screenshot Format"),
|
|
FSUI_VSTR("Determines the format that screenshots will be saved/compressed with."), "Display",
|
|
"ScreenshotFormat", Settings::DEFAULT_DISPLAY_SCREENSHOT_FORMAT,
|
|
&Settings::ParseDisplayScreenshotFormat, &Settings::GetDisplayScreenshotFormatName,
|
|
&Settings::GetDisplayScreenshotFormatDisplayName, DisplayScreenshotFormat::Count);
|
|
DrawIntRangeSetting(bsi, FSUI_ICONVSTR(ICON_FA_CAMERA_RETRO, "Screenshot Quality"),
|
|
FSUI_VSTR("Selects the quality at which screenshots will be compressed."), "Display",
|
|
"ScreenshotQuality", Settings::DEFAULT_DISPLAY_SCREENSHOT_QUALITY, 1, 100, "%d%%");
|
|
|
|
MenuHeading(FSUI_VSTR("Texture Replacements"));
|
|
|
|
const bool texture_cache_enabled = GetEffectiveBoolSetting(bsi, "GPU", "EnableTextureCache", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_ID_BADGE, "Enable Texture Cache"),
|
|
FSUI_VSTR("Enables caching of guest textures, required for texture replacement."), "GPU",
|
|
"EnableTextureCache", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_DATABASE, "Preload Replacement Textures"),
|
|
FSUI_VSTR("Loads all replacement texture to RAM, reducing stuttering at runtime."),
|
|
"TextureReplacements", "PreloadTextures", false,
|
|
((texture_cache_enabled &&
|
|
GetEffectiveBoolSetting(bsi, "TextureReplacements", "EnableTextureReplacements", false)) ||
|
|
GetEffectiveBoolSetting(bsi, "TextureReplacements", "EnableVRAMWriteReplacements", false)));
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_FILE_IMPORT, "Enable Texture Replacements"),
|
|
FSUI_VSTR("Enables loading of replacement textures. Not compatible with all games."),
|
|
"TextureReplacements", "EnableTextureReplacements", false, texture_cache_enabled);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIST_CHECK, "Always Track Uploads"),
|
|
FSUI_VSTR("Forces texture upload tracking to be enabled regardless of whether it is needed."),
|
|
"TextureReplacements", "AlwaysTrackUploads", false, texture_cache_enabled);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_FILE_EXPORT, "Enable Texture Dumping"),
|
|
FSUI_VSTR("Enables dumping of textures to image files, which can be replaced. Not compatible with all games."),
|
|
"TextureReplacements", "DumpTextures", false, texture_cache_enabled);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_FILE, "Dump Replaced Textures"),
|
|
FSUI_VSTR("Dumps textures that have replacements already loaded."), "TextureReplacements", "DumpReplacedTextures",
|
|
false,
|
|
(texture_cache_enabled && GetEffectiveBoolSetting(bsi, "TextureReplacements", "DumpTextures", false)) ||
|
|
GetEffectiveBoolSetting(bsi, "TextureReplacements", "DumpVRAMWrites", false));
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_FILE_PEN, "Enable VRAM Write Replacement"),
|
|
FSUI_VSTR("Enables the replacement of background textures in supported games."),
|
|
"TextureReplacements", "EnableVRAMWriteReplacements", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_FILE_INVOICE, "Enable VRAM Write Dumping"),
|
|
FSUI_VSTR("Writes backgrounds that can be replaced to the dump directory."), "TextureReplacements",
|
|
"DumpVRAMWrites", false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_VIDEO, "Use Old MDEC Routines"),
|
|
FSUI_VSTR("Enables the older, less accurate MDEC decoding routines. May be required for old "
|
|
"replacement backgrounds to match/load."),
|
|
"Hacks", "UseOldMDECRoutines", false);
|
|
|
|
DrawFolderSetting(bsi, FSUI_ICONVSTR(ICON_FA_FOLDER, "Textures Directory"), "Folders", "Textures",
|
|
EmuFolders::Textures);
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::PopulatePostProcessingChain(const SettingsInterface& si, const char* section)
|
|
{
|
|
const u32 stages = PostProcessing::Config::GetStageCount(si, section);
|
|
s_settings_locals.postprocessing_stages.clear();
|
|
s_settings_locals.postprocessing_stages.reserve(stages);
|
|
for (u32 i = 0; i < stages; i++)
|
|
{
|
|
PostProcessingStageInfo psi;
|
|
psi.name = PostProcessing::Config::GetStageShaderName(si, section, i);
|
|
psi.options = PostProcessing::Config::GetStageOptions(si, section, i);
|
|
psi.enabled = PostProcessing::Config::IsStageEnabled(si, section, i);
|
|
s_settings_locals.postprocessing_stages.push_back(std::move(psi));
|
|
}
|
|
}
|
|
|
|
enum
|
|
{
|
|
POSTPROCESSING_ACTION_NONE = 0,
|
|
POSTPROCESSING_ACTION_REMOVE,
|
|
POSTPROCESSING_ACTION_MOVE_UP,
|
|
POSTPROCESSING_ACTION_MOVE_DOWN,
|
|
};
|
|
|
|
void FullscreenUI::DrawPostProcessingSettingsPage()
|
|
{
|
|
static constexpr const char* section = PostProcessing::Config::DISPLAY_CHAIN_SECTION;
|
|
|
|
static constexpr auto queue_reload = []() {
|
|
if (GPUThread::HasGPUBackend())
|
|
{
|
|
Host::RunOnCoreThread([]() {
|
|
if (System::IsValid())
|
|
GPUPresenter::ReloadPostProcessingSettings(true, false, false);
|
|
});
|
|
}
|
|
};
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
bool reload_pending = false;
|
|
|
|
BeginMenuButtons();
|
|
|
|
MenuHeading(FSUI_VSTR("Controls"));
|
|
ResetFocusHere();
|
|
|
|
reload_pending |= DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_WAND_MAGIC_SPARKLES, "Enable Post Processing"),
|
|
FSUI_VSTR("If not enabled, the current post processing chain will be ignored."),
|
|
"PostProcessing", "Enabled", false);
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_ARROWS_ROTATE, "Reload Shaders"),
|
|
FSUI_VSTR("Reloads the shaders from disk, applying any changes."),
|
|
bsi->GetBoolValue("PostProcessing", "Enabled", false)))
|
|
{
|
|
// Have to defer because of the settings lock.
|
|
if (GPUThread::HasGPUBackend())
|
|
{
|
|
Host::RunOnCoreThread([]() { GPUPresenter::ReloadPostProcessingSettings(true, true, true); });
|
|
ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Post-processing shaders reloaded."));
|
|
}
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Operations"));
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_PF_ADD, "Add Shader"), FSUI_VSTR("Adds a new shader to the chain.")))
|
|
{
|
|
std::vector<std::pair<std::string, PostProcessing::ShaderType>> shaders = PostProcessing::GetAvailableShaderNames();
|
|
ChoiceDialogOptions options;
|
|
options.reserve(shaders.size());
|
|
for (auto& [name, type] : shaders)
|
|
{
|
|
std::string display_name = fmt::format("{} [{}]", name, PostProcessing::GetShaderTypeDisplayName(type));
|
|
options.emplace_back(std::move(display_name), false);
|
|
}
|
|
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_PF_ADD, "Add Shader"), false, std::move(options),
|
|
[shaders = std::move(shaders)](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0 || static_cast<u32>(index) >= shaders.size())
|
|
return;
|
|
|
|
const std::string& shader_name = shaders[index].first;
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
Error error;
|
|
if (PostProcessing::Config::AddStage(*bsi, section, shader_name, &error))
|
|
{
|
|
ShowToast(OSDMessageType::Quick, {},
|
|
fmt::format(FSUI_FSTR("Shader {} added as stage {}."), title,
|
|
PostProcessing::Config::GetStageCount(*bsi, section)));
|
|
PopulatePostProcessingChain(*bsi, section);
|
|
SetSettingsChanged(bsi);
|
|
queue_reload();
|
|
}
|
|
else
|
|
{
|
|
ShowToast(OSDMessageType::Quick, {},
|
|
fmt::format(FSUI_FSTR("Failed to load shader {}. It may be invalid.\nError was:"),
|
|
title, error.GetDescription()));
|
|
}
|
|
});
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_PF_TRASH, "Clear Shaders"), FSUI_VSTR("Clears a shader from the chain.")))
|
|
{
|
|
OpenConfirmMessageDialog(
|
|
FSUI_ICONVSTR(ICON_PF_TRASH, "Clear Shaders"),
|
|
FSUI_STR("Are you sure you want to clear the current post-processing chain? All configuration will be lost."),
|
|
[](bool confirmed) {
|
|
if (!confirmed)
|
|
return;
|
|
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
PostProcessing::Config::ClearStages(*bsi, section);
|
|
PopulatePostProcessingChain(*bsi, section);
|
|
SetSettingsChanged(bsi);
|
|
ShowToast(OSDMessageType::Quick, {}, FSUI_STR("Post-processing chain cleared."));
|
|
queue_reload();
|
|
});
|
|
}
|
|
|
|
u32 postprocessing_action = POSTPROCESSING_ACTION_NONE;
|
|
u32 postprocessing_action_index = 0;
|
|
|
|
SmallString str;
|
|
SmallString tstr;
|
|
for (u32 stage_index = 0; stage_index < static_cast<u32>(s_settings_locals.postprocessing_stages.size());
|
|
stage_index++)
|
|
{
|
|
PostProcessingStageInfo& si = s_settings_locals.postprocessing_stages[stage_index];
|
|
|
|
ImGui::PushID(stage_index);
|
|
str.format(FSUI_FSTR("Stage {}: {}"), stage_index + 1, si.name);
|
|
MenuHeading(str);
|
|
|
|
tstr.format("PostProcessing/Stage{}", stage_index + 1);
|
|
|
|
if (ToggleButton(FSUI_ICONVSTR(ICON_FA_WAND_MAGIC_SPARKLES, "Enable Stage"),
|
|
FSUI_VSTR("If disabled, the shader in this stage will not be applied."), &si.enabled))
|
|
{
|
|
PostProcessing::Config::SetStageEnabled(*bsi, section, stage_index, si.enabled);
|
|
SetSettingsChanged(bsi);
|
|
reload_pending = true;
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_PF_REMOVE, "Remove From Chain"),
|
|
FSUI_VSTR("Removes this shader from the chain.")))
|
|
{
|
|
postprocessing_action = POSTPROCESSING_ACTION_REMOVE;
|
|
postprocessing_action_index = stage_index;
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_ARROW_UP, "Move Up"),
|
|
FSUI_VSTR("Moves this shader higher in the chain, applying it earlier."), (stage_index > 0)))
|
|
{
|
|
postprocessing_action = POSTPROCESSING_ACTION_MOVE_UP;
|
|
postprocessing_action_index = stage_index;
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_ARROW_DOWN, "Move Down"),
|
|
FSUI_VSTR("Moves this shader lower in the chain, applying it later."),
|
|
(stage_index != (s_settings_locals.postprocessing_stages.size() - 1))))
|
|
{
|
|
postprocessing_action = POSTPROCESSING_ACTION_MOVE_DOWN;
|
|
postprocessing_action_index = stage_index;
|
|
}
|
|
|
|
for (PostProcessing::ShaderOption& opt : si.options)
|
|
{
|
|
if (!opt.help_text.empty())
|
|
{
|
|
str.format("##help_{}{}", stage_index, opt.name);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
|
|
MenuButton(str, opt.help_text, false);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
if (opt.ShouldHide())
|
|
continue;
|
|
|
|
switch (opt.type)
|
|
{
|
|
case PostProcessing::ShaderOption::Type::Bool:
|
|
{
|
|
bool value = (opt.value[0].int_value != 0);
|
|
tstr.format(ICON_FA_GEAR " {}", opt.ui_name);
|
|
if (ToggleButton(tstr,
|
|
(opt.default_value[0].int_value != 0) ? FSUI_VSTR("Default: Enabled") :
|
|
FSUI_VSTR("Default: Disabled"),
|
|
&value))
|
|
{
|
|
opt.value[0].int_value = (value != 0);
|
|
PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt);
|
|
SetSettingsChanged(bsi);
|
|
reload_pending = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PostProcessing::ShaderOption::Type::Float:
|
|
{
|
|
tstr.format(ICON_FA_RULER_VERTICAL " {}###{}", opt.ui_name, opt.name);
|
|
str.format(FSUI_FSTR("Value: {} | Default: {} | Minimum: {} | Maximum: {}"), opt.value[0].float_value,
|
|
opt.default_value[0].float_value, opt.min_value[0].float_value, opt.max_value[0].float_value);
|
|
if (MenuButton(tstr, str))
|
|
OpenFixedPopupDialog(tstr);
|
|
|
|
if (IsFixedPopupDialogOpen(tstr) &&
|
|
BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(500.0f, 200.0f)))
|
|
{
|
|
BeginMenuButtons();
|
|
|
|
const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth();
|
|
|
|
ImGui::SetNextItemWidth(end);
|
|
|
|
bool changed = false;
|
|
switch (opt.vector_size)
|
|
{
|
|
case 1:
|
|
{
|
|
changed = ImGui::SliderFloat("##value", &opt.value[0].float_value, opt.min_value[0].float_value,
|
|
opt.max_value[0].float_value);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
{
|
|
changed = ImGui::SliderFloat2("##value", &opt.value[0].float_value, opt.min_value[0].float_value,
|
|
opt.max_value[0].float_value);
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
{
|
|
changed = ImGui::SliderFloat3("##value", &opt.value[0].float_value, opt.min_value[0].float_value,
|
|
opt.max_value[0].float_value);
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
{
|
|
changed = ImGui::SliderFloat4("##value", &opt.value[0].float_value, opt.min_value[0].float_value,
|
|
opt.max_value[0].float_value);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt);
|
|
SetSettingsChanged(bsi);
|
|
reload_pending = true;
|
|
}
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PostProcessing::ShaderOption::Type::Int:
|
|
{
|
|
tstr.format(ICON_FA_RULER_VERTICAL " {}##{}", opt.ui_name, opt.name);
|
|
str.format(FSUI_FSTR("Value: {} | Default: {} | Minimum: {} | Maximum: {}"), opt.value[0].int_value,
|
|
opt.default_value[0].int_value, opt.min_value[0].int_value, opt.max_value[0].int_value);
|
|
if (MenuButton(tstr, str))
|
|
OpenFixedPopupDialog(tstr);
|
|
|
|
if (IsFixedPopupDialogOpen(tstr) &&
|
|
BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(500.0f, 200.0f)))
|
|
{
|
|
BeginMenuButtons();
|
|
|
|
const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth();
|
|
bool changed = false;
|
|
ImGui::SetNextItemWidth(end);
|
|
switch (opt.vector_size)
|
|
{
|
|
case 1:
|
|
{
|
|
changed = ImGui::SliderInt("##value", &opt.value[0].int_value, opt.min_value[0].int_value,
|
|
opt.max_value[0].int_value);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
{
|
|
changed = ImGui::SliderInt2("##value", &opt.value[0].int_value, opt.min_value[0].int_value,
|
|
opt.max_value[0].int_value);
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
{
|
|
changed = ImGui::SliderInt3("##value", &opt.value[0].int_value, opt.min_value[0].int_value,
|
|
opt.max_value[0].int_value);
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
{
|
|
changed = ImGui::SliderInt4("##value", &opt.value[0].int_value, opt.min_value[0].int_value,
|
|
opt.max_value[0].int_value);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt);
|
|
SetSettingsChanged(bsi);
|
|
reload_pending = true;
|
|
}
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
switch (postprocessing_action)
|
|
{
|
|
case POSTPROCESSING_ACTION_REMOVE:
|
|
{
|
|
const PostProcessingStageInfo& si = s_settings_locals.postprocessing_stages[postprocessing_action_index];
|
|
ShowToast(OSDMessageType::Quick, {},
|
|
fmt::format(FSUI_FSTR("Removed stage {} ({})."), postprocessing_action_index + 1, si.name));
|
|
PostProcessing::Config::RemoveStage(*bsi, section, postprocessing_action_index);
|
|
PopulatePostProcessingChain(*bsi, section);
|
|
SetSettingsChanged(bsi);
|
|
reload_pending = true;
|
|
}
|
|
break;
|
|
case POSTPROCESSING_ACTION_MOVE_UP:
|
|
{
|
|
PostProcessing::Config::MoveStageUp(*bsi, section, postprocessing_action_index);
|
|
PopulatePostProcessingChain(*bsi, section);
|
|
SetSettingsChanged(bsi);
|
|
reload_pending = true;
|
|
}
|
|
break;
|
|
case POSTPROCESSING_ACTION_MOVE_DOWN:
|
|
{
|
|
PostProcessing::Config::MoveStageDown(*bsi, section, postprocessing_action_index);
|
|
PopulatePostProcessingChain(*bsi, section);
|
|
SetSettingsChanged(bsi);
|
|
reload_pending = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
MenuHeading(FSUI_VSTR("Border Overlay"));
|
|
|
|
{
|
|
const std::optional<TinyString> preset_name = bsi->GetOptionalTinyStringValue("BorderOverlay", "PresetName");
|
|
const bool is_null = !preset_name.has_value();
|
|
const bool is_none = (!is_null && preset_name->empty());
|
|
const bool is_custom = (!is_null && preset_name.value() == "Custom");
|
|
const std::string_view visible_value =
|
|
is_null ? FSUI_VSTR("Use Global Setting") :
|
|
(is_none ? FSUI_VSTR("None") : (is_custom ? FSUI_VSTR("Custom") : preset_name->view()));
|
|
if (MenuButtonWithValue(
|
|
FSUI_ICONVSTR(ICON_FA_BORDER_ALL, "Selected Preset"),
|
|
FSUI_VSTR("Select from the list of preset borders, or manually specify a custom configuration."),
|
|
visible_value))
|
|
{
|
|
std::vector<std::string> preset_names = GPUPresenter::EnumerateBorderOverlayPresets();
|
|
ChoiceDialogOptions options;
|
|
options.reserve(preset_names.size() + 2 + BoolToUInt32(IsEditingGameSettings(bsi)));
|
|
if (IsEditingGameSettings(bsi))
|
|
options.emplace_back(FSUI_STR("Use Global Setting"), is_null);
|
|
options.emplace_back(FSUI_STR("None"), is_none);
|
|
options.emplace_back(FSUI_STR("Custom"), is_custom);
|
|
for (std::string& name : preset_names)
|
|
{
|
|
const bool is_selected = (preset_name.has_value() && preset_name.value() == name);
|
|
options.emplace_back(std::move(name), is_selected);
|
|
}
|
|
|
|
OpenChoiceDialog(FSUI_ICONVSTR(ICON_FA_BORDER_ALL, "Border Overlay"), false, std::move(options),
|
|
[game_settings = IsEditingGameSettings(bsi)](s32 index, const std::string& title, bool) mutable {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const auto lock = Core::GetSettingsLock();
|
|
SettingsInterface* const bsi = GetEditingSettingsInterface(game_settings);
|
|
const s32 offset = static_cast<s32>(BoolToUInt32(game_settings));
|
|
if (game_settings && index == 0)
|
|
{
|
|
bsi->DeleteValue("BorderOverlay", "PresetName");
|
|
}
|
|
else
|
|
{
|
|
const char* new_value =
|
|
(index == (offset + 0)) ? "" : ((index == (offset + 1)) ? "Custom" : title.c_str());
|
|
bsi->SetStringValue("BorderOverlay", "PresetName", new_value);
|
|
}
|
|
SetSettingsChanged(bsi);
|
|
queue_reload();
|
|
});
|
|
}
|
|
|
|
if (is_custom)
|
|
{
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_IMAGE, "Image Path"),
|
|
GetEffectiveTinyStringSetting(bsi, "BorderOverlay", "ImagePath", "")))
|
|
{
|
|
OpenFileSelector(
|
|
FSUI_ICONVSTR(ICON_FA_IMAGE, "Image Path"), false,
|
|
[game_settings = IsEditingGameSettings(bsi)](const std::string& path) {
|
|
if (path.empty())
|
|
return;
|
|
|
|
SettingsInterface* const bsi = GetEditingSettingsInterface(game_settings);
|
|
bsi->SetStringValue("BorderOverlay", "ImagePath", path.c_str());
|
|
SetSettingsChanged(bsi);
|
|
queue_reload();
|
|
},
|
|
GetImageFilters());
|
|
}
|
|
|
|
reload_pending |= DrawIntRectSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_BORDER_ALL, "Display Area"),
|
|
FSUI_VSTR("Determines the area of the overlay image that the display will be drawn within."), "BorderOverlay",
|
|
"DisplayStartX", 0, "DisplayStartY", 0, "DisplayEndX", 0, "DisplayEndY", 0, 0, 16384, "%dpx");
|
|
|
|
reload_pending |=
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_BLENDER, "Alpha Blending"),
|
|
FSUI_VSTR("If enabled, the transparency of the overlay image will be applied."),
|
|
"BorderOverlay", "AlphaBlend", false);
|
|
|
|
reload_pending |= DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_BLENDER, "Destination Alpha Blending"),
|
|
FSUI_VSTR("If enabled, the display will be blended with the transparency of the overlay image."),
|
|
"BorderOverlay", "DestinationAlphaBlend", false);
|
|
}
|
|
}
|
|
|
|
EndMenuButtons();
|
|
|
|
if (reload_pending)
|
|
queue_reload();
|
|
}
|
|
|
|
void FullscreenUI::DrawAudioSettingsPage()
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
|
|
BeginMenuButtons();
|
|
|
|
MenuHeading(FSUI_VSTR("Audio Control"));
|
|
ResetFocusHere();
|
|
|
|
DrawIntRangeSetting(bsi, FSUI_ICONVSTR(ICON_FA_VOLUME_HIGH, "Output Volume"),
|
|
FSUI_VSTR("Controls the volume of the audio played on the host."), "Audio", "OutputVolume", 100,
|
|
0, 200, "%d%%");
|
|
DrawIntRangeSetting(bsi, FSUI_ICONVSTR(ICON_FA_FORWARD, "Fast Forward Volume"),
|
|
FSUI_VSTR("Controls the volume of the audio played on the host when fast forwarding."), "Audio",
|
|
"FastForwardVolume", 100, 0, 200, "%d%%");
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_VOLUME_XMARK, "Mute All Sound"),
|
|
FSUI_VSTR("Prevents the emulator from producing any audible sound."), "Audio", "OutputMuted",
|
|
false);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_COMPACT_DISC, "Mute CD Audio"),
|
|
FSUI_VSTR("Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to "
|
|
"disable background music in some games."),
|
|
"CDROM", "MuteCDAudio", false);
|
|
|
|
MenuHeading(FSUI_VSTR("Backend Settings"));
|
|
|
|
DrawEnumSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_PF_SPEAKER, "Audio Backend"),
|
|
FSUI_VSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "Audio",
|
|
"Backend", AudioStream::DEFAULT_BACKEND, &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
|
|
&AudioStream::GetBackendDisplayName, AudioBackend::Count);
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_PF_SFX_SOUND_EFFECT_NOISE, "Stretch Mode"),
|
|
FSUI_CSTR("Determines quality of audio when not running at 100% speed."), "Audio", "StretchMode",
|
|
AudioStreamParameters::DEFAULT_STRETCH_MODE, &CoreAudioStream::ParseStretchMode,
|
|
&CoreAudioStream::GetStretchModeName, &CoreAudioStream::GetStretchModeDisplayName,
|
|
AudioStretchMode::Count);
|
|
DrawIntRangeSetting(bsi, FSUI_ICONVSTR(ICON_FA_BUCKET, "Buffer Size"),
|
|
FSUI_VSTR("Determines the amount of audio buffered before being pulled by the host API."),
|
|
"Audio", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS, 10, 500, FSUI_CSTR("%d ms"));
|
|
if (!GetEffectiveBoolSetting(bsi, "Audio", "OutputLatencyMinimal",
|
|
AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MINIMAL))
|
|
{
|
|
DrawIntRangeSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_STOPWATCH_20, "Output Latency"),
|
|
FSUI_VSTR("Determines how much latency there is between the audio being picked up by the host API, and "
|
|
"played through speakers."),
|
|
"Audio", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS, 1, 500, FSUI_CSTR("%d ms"));
|
|
}
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_STOPWATCH, "Minimal Output Latency"),
|
|
FSUI_VSTR("When enabled, the minimum supported output latency will be used for the host API."),
|
|
"Audio", "OutputLatencyMinimal", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MINIMAL);
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawAchievementsSettingsHeader(SettingsInterface* bsi, std::unique_lock<std::mutex>& settings_lock)
|
|
{
|
|
ImDrawList* const dl = ImGui::GetWindowDrawList();
|
|
|
|
const float panel_height = LayoutScale(100.0f);
|
|
const float panel_rounding = LayoutScale(20.0f);
|
|
const float spacing = LayoutScale(10.0f);
|
|
const float line_spacing = LayoutScale(5.0f);
|
|
const float badge_size = LayoutScale(60.0f);
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
|
|
|
const ImVec2 bg_pos = ImGui::GetCursorScreenPos() + ImVec2(0.0f, spacing * 2.0f);
|
|
const ImVec2 bg_size = ImVec2(ImGui::GetContentRegionAvail().x, LayoutScale(100.0f));
|
|
dl->AddRectFilled(bg_pos, bg_pos + bg_size,
|
|
ImGui::GetColorU32(ModAlpha(DarkerColor(UIStyle.BackgroundColor), GetBackgroundAlpha())),
|
|
panel_rounding);
|
|
|
|
// must be after background rect
|
|
BeginMenuButtons();
|
|
|
|
ImVec2 pos = bg_pos + ImVec2(panel_rounding, panel_rounding);
|
|
const float max_content_width = bg_size.x - panel_rounding;
|
|
|
|
TinyString tstr;
|
|
SmallString sstr;
|
|
|
|
const ImVec2 pos_backup = ImGui::GetCursorPos();
|
|
const bool logged_in = (bsi->ContainsValue("Cheevos", "Token"));
|
|
{
|
|
// avoid locking order issues
|
|
settings_lock.unlock();
|
|
{
|
|
const auto lock = Achievements::GetLock();
|
|
std::string_view badge_path = Achievements::GetLoggedInUserBadgePath();
|
|
if (badge_path.empty())
|
|
badge_path = "images/ra-generic-user.png";
|
|
|
|
if (Achievements::IsLoggedIn())
|
|
{
|
|
const char* username_ptr = Achievements::GetLoggedInUserName();
|
|
if (username_ptr)
|
|
tstr = username_ptr;
|
|
}
|
|
else if (Achievements::IsLoggedInOrLoggingIn())
|
|
{
|
|
tstr = FSUI_VSTR("Logging In...");
|
|
}
|
|
else if (!logged_in)
|
|
{
|
|
tstr = FSUI_VSTR("Not Logged In");
|
|
}
|
|
else
|
|
{
|
|
// client not active
|
|
tstr = bsi->GetTinyStringValue("Cheevos", "Username");
|
|
}
|
|
|
|
sstr = Achievements::GetLoggedInUserPointsSummary();
|
|
if (sstr.empty())
|
|
{
|
|
if (logged_in)
|
|
sstr = FSUI_VSTR("Enable Achievements to see your user summary.");
|
|
else
|
|
sstr = FSUI_VSTR("To use achievements, please log in with your retroachievements.org account.");
|
|
}
|
|
|
|
if (GPUTexture* badge_tex = GetCachedTextureAsync(badge_path))
|
|
{
|
|
const ImRect badge_rect = CenterImage(ImRect(pos, pos + ImVec2(badge_size, badge_size)), badge_tex);
|
|
dl->AddImage(reinterpret_cast<ImTextureID>(GetCachedTextureAsync(badge_path)), badge_rect.Min, badge_rect.Max);
|
|
}
|
|
|
|
pos.x += badge_size + LayoutScale(15.0f);
|
|
|
|
RenderShadowedTextClipped(dl, UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight, pos,
|
|
pos + ImVec2(max_content_width - pos.x, UIStyle.LargeFontSize),
|
|
ImGui::GetColorU32(ImGuiCol_Text), tstr);
|
|
|
|
pos.y += UIStyle.LargeFontSize + line_spacing;
|
|
|
|
RenderAutoLabelText(dl, UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight,
|
|
UIStyle.BoldFontWeight, pos, pos + ImVec2(max_content_width - pos.x, UIStyle.LargeFontSize),
|
|
ImGui::GetColorU32(ImGuiCol_Text), sstr, ':');
|
|
}
|
|
settings_lock.lock();
|
|
}
|
|
|
|
if (!IsEditingGameSettings(bsi))
|
|
{
|
|
if (logged_in)
|
|
FormatIconString(tstr, ICON_FA_KEY, FSUI_VSTR("Logout"));
|
|
else
|
|
FormatIconString(tstr, ICON_FA_KEY, FSUI_VSTR("Login"));
|
|
|
|
const ImVec2 login_logout_button_size =
|
|
UIStyle.Font->CalcTextSizeA(UIStyle.LargeFontSize, UIStyle.BoldFontWeight, FLT_MAX, 0.0f, IMSTR_START_END(tstr)) +
|
|
style.FramePadding * 2.0f;
|
|
const ImVec2 login_logout_button_pos =
|
|
ImVec2(bg_pos.x + bg_size.x - panel_rounding - login_logout_button_size.x - LayoutScale(10.0f),
|
|
bg_pos.y + ((panel_height - UIStyle.LargeFontSize - (style.FramePadding.y * 2.0f)) * 0.5f));
|
|
|
|
ImGui::SetCursorPos(login_logout_button_pos);
|
|
|
|
bool visible, hovered;
|
|
const bool clicked = MenuButtonFrame(
|
|
"login_logout", true, ImRect(login_logout_button_pos, login_logout_button_pos + login_logout_button_size),
|
|
&visible, &hovered);
|
|
if (visible)
|
|
{
|
|
const ImRect text_bb = ImRect(login_logout_button_pos + style.FramePadding,
|
|
login_logout_button_pos + login_logout_button_size - style.FramePadding);
|
|
RenderShadowedTextClipped(dl, UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight, text_bb.Min,
|
|
text_bb.Max, ImGui::GetColorU32(ImGuiCol_Text), tstr, nullptr, ImVec2(0.0f, 0.0f), 0.0f,
|
|
&text_bb);
|
|
}
|
|
|
|
if (clicked)
|
|
{
|
|
if (logged_in)
|
|
Host::RunOnCoreThread(&Achievements::Logout);
|
|
else
|
|
OpenFixedPopupDialog(ACHIEVEMENTS_LOGIN_DIALOG_NAME);
|
|
}
|
|
}
|
|
|
|
ImGui::SetCursorPos(ImVec2(pos_backup.x, pos_backup.y + panel_height + (spacing * 3.0f)));
|
|
}
|
|
|
|
void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& settings_lock)
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
|
|
DrawAchievementsSettingsHeader(bsi, settings_lock);
|
|
|
|
ResetFocusHere();
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TROPHY, "Enable Achievements"),
|
|
FSUI_VSTR("When enabled and logged in, DuckStation will scan for achievements on startup."),
|
|
"Cheevos", "Enabled", false);
|
|
|
|
const bool enabled = GetEffectiveBoolSetting(bsi, "Cheevos", "Enabled", false);
|
|
|
|
if (DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_HAT_COWBOY, "Hardcore Mode"),
|
|
FSUI_VSTR("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, "
|
|
"cheats, and slowdown functions."),
|
|
"Cheevos", "ChallengeMode", false, enabled))
|
|
{
|
|
if (GPUThread::HasGPUBackend() && bsi->GetBoolValue("Cheevos", "ChallengeMode", false))
|
|
{
|
|
// prevent locking order deadlock
|
|
settings_lock.unlock();
|
|
const auto lock = Achievements::GetLock();
|
|
if (Achievements::HasActiveGame())
|
|
{
|
|
OpenConfirmMessageDialog(
|
|
FSUI_ICONVSTR(ICON_FA_HAT_COWBOY, "Hardcore Mode"),
|
|
FSUI_STR("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?"),
|
|
[](bool result) {
|
|
if (result)
|
|
Host::RunOnCoreThread(&System::ResetSystem);
|
|
});
|
|
}
|
|
settings_lock.lock();
|
|
}
|
|
}
|
|
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_ARROW_ROTATE_RIGHT, "Encore Mode"),
|
|
FSUI_VSTR("When enabled, each session will behave as if no achievements have been unlocked."),
|
|
"Cheevos", "EncoreMode", false, enabled);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_USER_LOCK, "Spectator Mode"),
|
|
FSUI_VSTR("When enabled, DuckStation will assume all achievements are locked and not send any "
|
|
"unlock notifications to the server."),
|
|
"Cheevos", "SpectatorMode", false, enabled);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_FLASK_VIAL, "Test Unofficial Achievements"),
|
|
FSUI_VSTR("When enabled, DuckStation will list achievements from unofficial sets. These achievements are not "
|
|
"tracked by RetroAchievements."),
|
|
"Cheevos", "UnofficialTestMode", false, enabled);
|
|
|
|
MenuHeading(FSUI_VSTR("Notifications"));
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_BELL, "Achievement Notifications"),
|
|
FSUI_VSTR("Displays popup messages on events such as achievement unlocks and leaderboard submissions."), "Cheevos",
|
|
"Notifications", true, enabled);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIST_OL, "Leaderboard Notifications"),
|
|
FSUI_VSTR("Displays popup messages when starting, submitting, or failing a leaderboard challenge."),
|
|
"Cheevos", "LeaderboardNotifications", true, enabled);
|
|
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_CLOCK, "Leaderboard Trackers"),
|
|
FSUI_VSTR("Shows a timer in the selected location when leaderboard challenges are active."),
|
|
"Cheevos", "LeaderboardTrackers", true, enabled);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_MUSIC, "Sound Effects"),
|
|
FSUI_VSTR("Plays sound effects for events such as achievement unlocks and leaderboard submissions."), "Cheevos",
|
|
"SoundEffects", true, enabled);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_ENVELOPE, "Notification Location"),
|
|
FSUI_VSTR("Selects the screen location for achievement and leaderboard notifications."), "Cheevos",
|
|
"NotificationLocation", Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_LOCATION,
|
|
&Settings::ParseNotificationLocation, &Settings::GetNotificationLocationName,
|
|
&Settings::GetNotificationLocationDisplayName, NotificationLocation::MaxCount, enabled);
|
|
|
|
MenuHeading(FSUI_VSTR("Progress Tracking"));
|
|
|
|
DrawEnumSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_TEMPERATURE_ARROW_UP, "Challenge Indicators"),
|
|
FSUI_VSTR("Shows a notification or icons in the selected location when a challenge/primed achievement is active."),
|
|
"Cheevos", "ChallengeIndicatorMode", Settings::DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE,
|
|
&Settings::ParseAchievementChallengeIndicatorMode, &Settings::GetAchievementChallengeIndicatorModeName,
|
|
&Settings::GetAchievementChallengeIndicatorModeDisplayName, AchievementChallengeIndicatorMode::MaxCount, enabled);
|
|
|
|
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_LOCATION_DOT, "Indicator Location"),
|
|
FSUI_VSTR("Selects the screen location for challenge/progress indicators, and leaderboard trackers."),
|
|
"Cheevos", "IndicatorLocation", Settings::DEFAULT_ACHIEVEMENT_INDICATOR_LOCATION,
|
|
&Settings::ParseNotificationLocation, &Settings::GetNotificationLocationName,
|
|
&Settings::GetNotificationLocationDisplayName, NotificationLocation::MaxCount, enabled);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_ICONVSTR(ICON_FA_BARS_PROGRESS, "Progress Indicators"),
|
|
FSUI_VSTR("Shows a popup in the selected location when progress towards a measured achievement changes."),
|
|
"Cheevos", "ProgressIndicators", true, enabled);
|
|
|
|
if (!IsEditingGameSettings(bsi))
|
|
{
|
|
MenuHeading(FSUI_VSTR("Operations"));
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_ARROWS_ROTATE, "Refresh Achievement Progress"),
|
|
FSUI_VSTR("Updates the progress database for achievements shown in the game list.")))
|
|
{
|
|
StartAchievementsProgressRefresh();
|
|
}
|
|
|
|
if (MenuButton(FSUI_ICONVSTR(ICON_FA_DOWNLOAD, "Download Game Icons"),
|
|
FSUI_VSTR("Downloads icons for all games from RetroAchievements.")))
|
|
{
|
|
StartAchievementsGameIconDownload();
|
|
}
|
|
|
|
if (bsi->ContainsValue("Cheevos", "Token"))
|
|
{
|
|
const std::string ts_string = Host::FormatNumber(
|
|
Host::NumberFormatType::LongDateTime,
|
|
StringUtil::FromChars<s64>(bsi->GetTinyStringValue("Cheevos", "LoginTimestamp", "0")).value_or(0));
|
|
MenuButtonWithoutSummary(
|
|
SmallString::from_format(fmt::runtime(FSUI_ICONVSTR(ICON_FA_USER_CLOCK, "Login token generated on {}")),
|
|
ts_string),
|
|
false);
|
|
}
|
|
}
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawAchievementsLoginWindow()
|
|
{
|
|
static constexpr const char* LOGIN_PROGRESS_NAME = "AchievementsLogin";
|
|
|
|
static char username[256] = {};
|
|
static char password[256] = {};
|
|
static std::unique_ptr<std::string> login_error;
|
|
|
|
if (!BeginFixedPopupDialog(LayoutScale(LAYOUT_LARGE_POPUP_PADDING), LayoutScale(LAYOUT_LARGE_POPUP_ROUNDING),
|
|
LayoutScale(600.0f, 0.0f)))
|
|
{
|
|
std::memset(username, 0, sizeof(username));
|
|
std::memset(password, 0, sizeof(password));
|
|
login_error.reset();
|
|
return;
|
|
}
|
|
|
|
const std::string_view ra_title = "RetroAchivements";
|
|
const ImVec2 ra_title_size = UIStyle.Font->CalcTextSizeA(UIStyle.LargeFontSize, UIStyle.BoldFontWeight, FLT_MAX, 0.0f,
|
|
IMSTR_START_END(ra_title));
|
|
const float ra_title_spacing = LayoutScale(10.0f);
|
|
GPUTexture* ra_logo = GetCachedTexture("images/ra-icon.webp");
|
|
const ImVec2 ra_logo_size = ImVec2(UIStyle.LargeFontSize * 1.85f, UIStyle.LargeFontSize);
|
|
const ImVec2 ra_logo_imgsize = CenterImage(ra_logo_size, ra_logo).GetSize();
|
|
const ImRect work_rect = ImGui::GetCurrentWindow()->WorkRect;
|
|
const float indent = (work_rect.GetWidth() - (ra_logo_size.x + ra_title_spacing + ra_title_size.x)) * 0.5f;
|
|
ImDrawList* const dl = ImGui::GetWindowDrawList();
|
|
const ImVec2 ra_logo_pos = work_rect.Min + ImVec2(indent, 0.0f);
|
|
dl->AddImage(ra_logo, ra_logo_pos, ra_logo_pos + ra_logo_imgsize);
|
|
dl->AddText(UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight,
|
|
ra_logo_pos + ImVec2(ra_logo_size.x + ra_title_spacing, 0.0f), ImGui::GetColorU32(ImGuiCol_Text),
|
|
IMSTR_START_END(ra_title));
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ra_logo_size.y + LayoutScale(15.0f));
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text, DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text]));
|
|
|
|
if (!login_error)
|
|
{
|
|
ImGui::TextWrapped(
|
|
FSUI_CSTR("Please enter your user name and password for retroachievements.org below. Your password will "
|
|
"not be saved in DuckStation, an access token will be generated and used instead."));
|
|
}
|
|
else
|
|
{
|
|
ImGui::TextWrapped("%s", login_error->c_str());
|
|
}
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
ImGui::ItemSize(ImVec2(0.0f, LayoutScale(LAYOUT_MENU_BUTTON_SPACING * 2.0f)));
|
|
|
|
const bool is_logging_in = IsBackgroundProgressDialogOpen(LOGIN_PROGRESS_NAME);
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(LAYOUT_WIDGET_FRAME_ROUNDING));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
|
|
const float item_width = LayoutScale(550.0f);
|
|
|
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - item_width) * 0.5f);
|
|
ImGui::SetNextItemWidth(item_width);
|
|
ImGui::InputTextWithHint("##username", FSUI_CSTR("User Name"), username, sizeof(username),
|
|
is_logging_in ? ImGuiInputTextFlags_ReadOnly : 0);
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - item_width) * 0.5f);
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
|
ImGui::SetNextItemWidth(item_width);
|
|
ImGui::InputTextWithHint("##password", FSUI_CSTR("Password"), password, sizeof(password),
|
|
is_logging_in ? (ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_Password) :
|
|
ImGuiInputTextFlags_Password);
|
|
|
|
ImGui::PopStyleVar(2);
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(15.0f));
|
|
|
|
const bool login_enabled = (std::strlen(username) > 0 && std::strlen(password) > 0 && !is_logging_in);
|
|
|
|
if (MenuButtonWithoutSummary(FSUI_ICONVSTR(ICON_FA_KEY, "Login"), login_enabled, LAYOUT_CENTER_ALIGN_TEXT))
|
|
{
|
|
OpenBackgroundProgressDialog(LOGIN_PROGRESS_NAME, FSUI_STR("Logging in to RetroAchievements..."), 0, 0, 0);
|
|
|
|
Host::RunOnCoreThread([username = std::string(username), password = std::string(password)]() {
|
|
Error error;
|
|
const bool result = Achievements::Login(username.c_str(), password.c_str(), &error);
|
|
GPUThread::RunOnThread([result, error = std::move(error)]() {
|
|
CloseBackgroundProgressDialog(LOGIN_PROGRESS_NAME);
|
|
|
|
if (result)
|
|
{
|
|
CloseFixedPopupDialog();
|
|
return;
|
|
}
|
|
|
|
login_error = std::make_unique<std::string>(
|
|
fmt::format(FSUI_FSTR("Login Failed.\nError: {}\nPlease check your username and password, and try again."),
|
|
error.GetDescription()));
|
|
});
|
|
});
|
|
}
|
|
|
|
if (MenuButtonWithoutSummary(FSUI_ICONVSTR(ICON_FA_XMARK, "Cancel"), !is_logging_in, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
|
|
void FullscreenUI::StartAchievementsProgressRefresh()
|
|
{
|
|
auto progress = OpenModalProgressDialog(FSUI_STR("Refresh Achievement Progress"));
|
|
|
|
Host::QueueAsyncTask([progress = progress.release()]() {
|
|
Error error;
|
|
const bool result = Achievements::RefreshAllProgressDatabase(progress, &error);
|
|
Host::RunOnCoreThread([error = std::move(error), progress, result]() mutable {
|
|
GPUThread::RunOnThread([error = std::move(error), progress, result]() mutable {
|
|
delete progress;
|
|
if (result)
|
|
ShowToast(OSDMessageType::Info, {}, FSUI_STR("Progress database updated."));
|
|
else
|
|
FullscreenUI::OpenInfoMessageDialog(FSUI_STR("Update Progress"), error.TakeDescription());
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::StartAchievementsGameIconDownload()
|
|
{
|
|
auto progress = OpenModalProgressDialog(FSUI_STR("Download Game Icons"));
|
|
|
|
Host::QueueAsyncTask([progress = progress.release()]() {
|
|
Error error;
|
|
const bool result = Achievements::DownloadGameIcons(progress, &error);
|
|
Host::RunOnCoreThread([error = std::move(error), progress, result]() mutable {
|
|
GPUThread::RunOnThread([error = std::move(error), progress, result]() mutable {
|
|
delete progress;
|
|
if (result)
|
|
ShowToast(OSDMessageType::Info, {}, FSUI_STR("Game icons downloaded."));
|
|
else
|
|
FullscreenUI::OpenInfoMessageDialog(FSUI_STR("Download Game Icons"), error.TakeDescription());
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
void FullscreenUI::DrawAdvancedSettingsPage()
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
|
|
BeginMenuButtons();
|
|
|
|
MenuHeading(FSUI_VSTR("Logging Settings"));
|
|
ResetFocusHere();
|
|
|
|
DrawEnumSetting(bsi, FSUI_VSTR("Log Level"),
|
|
FSUI_VSTR("Sets the verbosity of messages logged. Higher levels will log more messages."), "Logging",
|
|
"LogLevel", Log::DEFAULT_LOG_LEVEL, &Settings::ParseLogLevelName, &Settings::GetLogLevelName,
|
|
&Settings::GetLogLevelDisplayName, Log::Level::MaxCount);
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Log To System Console"), FSUI_VSTR("Logs messages to the console window."),
|
|
"Logging", "LogToConsole", false);
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Log To Debug Console"),
|
|
FSUI_VSTR("Logs messages to the debug console where supported."), "Logging", "LogToDebug", false);
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Log To File"), FSUI_VSTR("Logs messages to duckstation.log in the user directory."),
|
|
"Logging", "LogToFile", false);
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Log Timestamps"),
|
|
FSUI_VSTR("Includes the elapsed time since the application start in window and console logs."),
|
|
"Logging", "LogTimestamps", true);
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Log File Timestamps"),
|
|
FSUI_VSTR("Includes the elapsed time since the application start in file logs."), "Logging",
|
|
"LogFileTimestamps", false);
|
|
|
|
MenuHeading(FSUI_VSTR("Debugging Settings"));
|
|
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Use Debug GPU Device"),
|
|
FSUI_VSTR("Enable debugging when supported by the host's renderer API. Only for developer use."),
|
|
"GPU", "UseDebugDevice", false);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_VSTR("Enable GPU-Based Validation"),
|
|
FSUI_VSTR("Enable GPU-based validation when supported by the host's renderer API. Only for developer use."), "GPU",
|
|
"UseGPUBasedValidation", false);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_VSTR("Prefer OpenGL ES Context"),
|
|
FSUI_VSTR("Uses OpenGL ES even when desktop OpenGL is supported. May improve performance on some SBC drivers."),
|
|
"GPU", "PreferGLESContext", Settings::DEFAULT_GPU_PREFER_GLES_CONTEXT);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_VSTR("Load Devices From Save States"),
|
|
FSUI_VSTR("When enabled, memory cards and controllers will be overwritten when save states are loaded."), "Main",
|
|
"LoadDevicesFromSaveStates", false);
|
|
DrawEnumSetting(bsi, FSUI_VSTR("Save State Compression"),
|
|
FSUI_VSTR("Reduces the size of save states by compressing the data before saving."), "Main",
|
|
"SaveStateCompression", Settings::DEFAULT_SAVE_STATE_COMPRESSION_MODE,
|
|
&Settings::ParseSaveStateCompressionModeName, &Settings::GetSaveStateCompressionModeName,
|
|
&Settings::GetSaveStateCompressionModeDisplayName, SaveStateCompressionMode::Count);
|
|
|
|
DrawEnumSetting(bsi, FSUI_VSTR("Wireframe Rendering"),
|
|
FSUI_VSTR("Overlays or replaces normal triangle drawing with a wireframe/line view."), "GPU",
|
|
"WireframeMode", GPUWireframeMode::Disabled, &Settings::ParseGPUWireframeMode,
|
|
&Settings::GetGPUWireframeModeName, &Settings::GetGPUWireframeModeDisplayName,
|
|
GPUWireframeMode::Count);
|
|
|
|
MenuHeading(FSUI_VSTR("CPU Emulation"));
|
|
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Enable Recompiler Memory Exceptions"),
|
|
FSUI_VSTR("Enables alignment and bus exceptions. Not needed for any known games."), "CPU",
|
|
"RecompilerMemoryExceptions", false);
|
|
DrawToggleSetting(
|
|
bsi, FSUI_VSTR("Enable Recompiler Block Linking"),
|
|
FSUI_VSTR("Performance enhancement - jumps directly between blocks instead of returning to the dispatcher."), "CPU",
|
|
"RecompilerBlockLinking", true);
|
|
DrawEnumSetting(bsi, FSUI_VSTR("Recompiler Fast Memory Access"),
|
|
FSUI_VSTR("Avoids calls to C++ code, significantly speeding up the recompiler."), "CPU",
|
|
"FastmemMode", Settings::DEFAULT_CPU_FASTMEM_MODE, &Settings::ParseCPUFastmemMode,
|
|
&Settings::GetCPUFastmemModeName, &Settings::GetCPUFastmemModeDisplayName, CPUFastmemMode::Count);
|
|
|
|
MenuHeading(FSUI_VSTR("CD-ROM Emulation"));
|
|
|
|
DrawIntRangeSetting(
|
|
bsi, FSUI_VSTR("Readahead Sectors"),
|
|
FSUI_VSTR("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread."),
|
|
"CDROM", "ReadaheadSectors", Settings::DEFAULT_CDROM_READAHEAD_SECTORS, 0, 32, FSUI_CSTR("%d sectors"));
|
|
|
|
DrawIntRangeSetting(bsi, FSUI_VSTR("Maximum Seek Speedup Cycles"),
|
|
FSUI_VSTR("Sets the minimum delay for the 'Maximum' seek speedup level."), "CDROM",
|
|
"MaxSeekSpeedupCycles", Settings::DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES, 1, 1000000,
|
|
FSUI_CSTR("%d cycles"));
|
|
|
|
DrawIntRangeSetting(bsi, FSUI_VSTR("Maximum Read Speedup Cycles"),
|
|
FSUI_VSTR("Sets the minimum delay for the 'Maximum' read speedup level."), "CDROM",
|
|
"MaxReadSpeedupCycles", Settings::DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES, 1, 1000000,
|
|
FSUI_CSTR("%d cycles"));
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_VSTR("Disable Speedup on MDEC"),
|
|
FSUI_VSTR("Tries to detect FMVs and disable read speedup during games that don't use XA streaming audio."), "CDROM",
|
|
"DisableSpeedupOnMDEC", false);
|
|
|
|
DrawToggleSetting(bsi, FSUI_VSTR("Enable Region Check"),
|
|
FSUI_VSTR("Simulates the region check present in original, unmodified consoles."), "CDROM",
|
|
"RegionCheck", false);
|
|
|
|
DrawToggleSetting(
|
|
bsi, FSUI_VSTR("Allow Booting Without SBI File"),
|
|
FSUI_VSTR("Allows booting to continue even without a required SBI file. These games will not run correctly."),
|
|
"CDROM", "AllowBootingWithoutSBIFile", false);
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
void FullscreenUI::DrawPatchesOrCheatsSettingsPage(bool cheats)
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
|
|
const Cheats::CodeInfoList& code_list =
|
|
cheats ? s_settings_locals.game_cheats_list : s_settings_locals.game_patch_list;
|
|
std::vector<std::string>& enable_list =
|
|
cheats ? s_settings_locals.enabled_game_cheat_cache : s_settings_locals.enabled_game_patch_cache;
|
|
const char* section = cheats ? Cheats::CHEATS_CONFIG_SECTION : Cheats::PATCHES_CONFIG_SECTION;
|
|
|
|
BeginMenuButtons();
|
|
ResetFocusHere();
|
|
|
|
static constexpr auto draw_code = [](SettingsInterface* bsi, const char* section, const Cheats::CodeInfo& ci,
|
|
std::vector<std::string>& enable_list, bool cheats) {
|
|
const auto enable_it = std::find(enable_list.begin(), enable_list.end(), ci.name);
|
|
|
|
SmallString title;
|
|
if (!cheats)
|
|
title = std::string_view(ci.name);
|
|
else
|
|
title.format("{}##{}", ci.GetNamePart(), ci.name);
|
|
|
|
bool state = (enable_it != enable_list.end());
|
|
|
|
if (ci.HasOptionChoices())
|
|
{
|
|
TinyString visible_value(FSUI_VSTR("Disabled"));
|
|
bool has_option = false;
|
|
if (state)
|
|
{
|
|
// Need to map the value to an option.
|
|
visible_value = ci.MapOptionValueToName(bsi->GetTinyStringValue(section, ci.name.c_str()));
|
|
has_option = true;
|
|
}
|
|
|
|
if (MenuButtonWithValue(title.c_str(), ci.description.c_str(), visible_value))
|
|
{
|
|
ChoiceDialogOptions options;
|
|
options.reserve(ci.options.size() + 1);
|
|
options.emplace_back(FSUI_STR("Disabled"), !has_option);
|
|
|
|
for (const Cheats::CodeOption& opt : ci.options)
|
|
options.emplace_back(opt.first, has_option && (visible_value.view() == opt.first));
|
|
|
|
OpenChoiceDialog(
|
|
ci.name, false, std::move(options),
|
|
[cheat_name = ci.name, cheats, section](s32 index, const std::string& title, bool checked) {
|
|
if (index < 0)
|
|
return;
|
|
|
|
const Cheats::CodeInfo* ci = Cheats::FindCodeInInfoList(
|
|
cheats ? s_settings_locals.game_cheats_list : s_settings_locals.game_patch_list, cheat_name);
|
|
if (ci)
|
|
{
|
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
|
std::vector<std::string>& enable_list =
|
|
cheats ? s_settings_locals.enabled_game_cheat_cache : s_settings_locals.enabled_game_patch_cache;
|
|
const auto it = std::find(enable_list.begin(), enable_list.end(), ci->name);
|
|
if (index == 0)
|
|
{
|
|
bsi->RemoveFromStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci->name.c_str());
|
|
if (it != enable_list.end())
|
|
enable_list.erase(it);
|
|
}
|
|
else
|
|
{
|
|
bsi->AddToStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci->name.c_str());
|
|
bsi->SetUIntValue(section, ci->name.c_str(), ci->MapOptionNameToValue(title));
|
|
if (it == enable_list.end())
|
|
enable_list.push_back(std::move(cheat_name));
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
else if (ci.HasOptionRange())
|
|
{
|
|
TinyString visible_value(FSUI_VSTR("Disabled"));
|
|
s32 range_value = static_cast<s32>(ci.option_range_start) - 1;
|
|
if (state)
|
|
{
|
|
const std::optional<s32> value =
|
|
bsi->GetOptionalIntValue(section, ci.name.c_str(), std::nullopt).value_or(ci.option_range_start);
|
|
if (value.has_value())
|
|
{
|
|
range_value = value.value();
|
|
visible_value.format("{}", value.value());
|
|
}
|
|
}
|
|
|
|
constexpr s32 step_value = 1;
|
|
|
|
if (MenuButtonWithValue(title.c_str(), ci.description.c_str(), visible_value.c_str()))
|
|
OpenFixedPopupDialog(title);
|
|
|
|
if (IsFixedPopupDialogOpen(title) &&
|
|
BeginFixedPopupDialog(LayoutScale(LAYOUT_SMALL_POPUP_PADDING), LayoutScale(LAYOUT_SMALL_POPUP_PADDING),
|
|
LayoutScale(600.0f, 0.0f)))
|
|
{
|
|
BeginMenuButtons();
|
|
|
|
bool range_value_changed = false;
|
|
|
|
BeginHorizontalMenuButtons(4);
|
|
HorizontalMenuButton(visible_value, false);
|
|
|
|
s32 step = 0;
|
|
ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
|
|
if (HorizontalMenuButton(ICON_FA_CHEVRON_UP))
|
|
step = step_value;
|
|
if (HorizontalMenuButton(ICON_FA_CHEVRON_DOWN))
|
|
step = -step_value;
|
|
ImGui::PopItemFlag();
|
|
if (HorizontalMenuButton(ICON_FA_ARROW_ROTATE_LEFT))
|
|
{
|
|
range_value = ci.option_range_start - 1;
|
|
range_value_changed = true;
|
|
}
|
|
|
|
EndHorizontalMenuButtons(10.0f);
|
|
|
|
if (step != 0)
|
|
{
|
|
range_value += step;
|
|
range_value_changed = true;
|
|
}
|
|
|
|
if (range_value_changed)
|
|
{
|
|
const auto it = std::find(enable_list.begin(), enable_list.end(), ci.name);
|
|
range_value =
|
|
std::clamp(range_value, static_cast<s32>(ci.option_range_start) - 1, static_cast<s32>(ci.option_range_end));
|
|
if (range_value < static_cast<s32>(ci.option_range_start))
|
|
{
|
|
bsi->RemoveFromStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci.name.c_str());
|
|
if (it != enable_list.end())
|
|
enable_list.erase(it);
|
|
}
|
|
else
|
|
{
|
|
bsi->AddToStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci.name.c_str());
|
|
bsi->SetIntValue(section, ci.name.c_str(), range_value);
|
|
if (it == enable_list.end())
|
|
enable_list.push_back(ci.name);
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
|
|
if (MenuButtonWithoutSummary(FSUI_VSTR("OK"), true, LAYOUT_CENTER_ALIGN_TEXT))
|
|
CloseFixedPopupDialog();
|
|
|
|
EndMenuButtons();
|
|
|
|
EndFixedPopupDialog();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const bool changed = ToggleButton(title.c_str(), ci.description.c_str(), &state);
|
|
if (changed)
|
|
{
|
|
if (state)
|
|
{
|
|
bsi->AddToStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci.name.c_str());
|
|
enable_list.push_back(ci.name);
|
|
}
|
|
else
|
|
{
|
|
bsi->RemoveFromStringList(section, Cheats::PATCH_ENABLE_CONFIG_KEY, ci.name.c_str());
|
|
enable_list.erase(enable_it);
|
|
}
|
|
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (cheats)
|
|
{
|
|
MenuButtonWithoutSummary(
|
|
FSUI_ICONVSTR(
|
|
ICON_EMOJI_WARNING,
|
|
"WARNING: Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games."),
|
|
false);
|
|
|
|
MenuHeading(FSUI_VSTR("Settings"));
|
|
|
|
bool enable_cheats = bsi->GetBoolValue("Cheats", "EnableCheats", false);
|
|
if (ToggleButton(FSUI_ICONVSTR(ICON_PF_CHEATS, "Enable Cheats"),
|
|
FSUI_VSTR("Enables the cheats that are selected below."), &enable_cheats))
|
|
{
|
|
if (enable_cheats)
|
|
bsi->SetBoolValue("Cheats", "EnableCheats", true);
|
|
else
|
|
bsi->DeleteValue("Cheats", "EnableCheats");
|
|
SetSettingsChanged(bsi);
|
|
}
|
|
|
|
bool load_database_cheats = bsi->GetBoolValue("Cheats", "LoadCheatsFromDatabase", true);
|
|
if (ToggleButton(FSUI_ICONVSTR(ICON_FA_DATABASE, "Load Database Cheats"),
|
|
FSUI_VSTR("Enables loading of cheats for this game from DuckStation's database."),
|
|
&load_database_cheats))
|
|
{
|
|
if (load_database_cheats)
|
|
bsi->DeleteValue("Cheats", "LoadCheatsFromDatabase");
|
|
else
|
|
bsi->SetBoolValue("Cheats", "LoadCheatsFromDatabase", false);
|
|
SetSettingsChanged(bsi);
|
|
PopulatePatchesAndCheatsList();
|
|
}
|
|
|
|
bool sort_list = bsi->GetBoolValue("Cheats", "SortList", false);
|
|
if (ToggleButton(FSUI_ICONVSTR(ICON_FA_ARROW_DOWN_A_Z, "Sort Alphabetically"),
|
|
FSUI_VSTR("Sorts the cheat list alphabetically by the name of the code."), &sort_list))
|
|
{
|
|
if (!sort_list)
|
|
bsi->DeleteValue("Cheats", "SortList");
|
|
else
|
|
bsi->SetBoolValue("Cheats", "SortList", true);
|
|
SetSettingsChanged(bsi);
|
|
s_settings_locals.game_cheats_list =
|
|
Cheats::GetCodeInfoList(s_settings_locals.game_settings_entry->serial,
|
|
s_settings_locals.game_settings_entry->hash, true, load_database_cheats, sort_list);
|
|
}
|
|
|
|
if (code_list.empty())
|
|
{
|
|
MenuButtonWithoutSummary(FSUI_ICONVSTR(ICON_FA_STORE_SLASH, "No cheats are available for this game."), false);
|
|
}
|
|
else
|
|
{
|
|
for (const std::string_view& group : s_settings_locals.game_cheat_groups)
|
|
{
|
|
MenuHeading(group.empty() ? FSUI_VSTR("Ungrouped") : group);
|
|
for (const Cheats::CodeInfo& ci : code_list)
|
|
{
|
|
if (ci.GetNameParentPart() != group)
|
|
continue;
|
|
|
|
draw_code(bsi, section, ci, enable_list, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MenuButtonWithoutSummary(
|
|
FSUI_ICONVSTR(
|
|
ICON_EMOJI_WARNING,
|
|
"WARNING: Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken "
|
|
"saved games."),
|
|
false);
|
|
|
|
if (code_list.empty())
|
|
{
|
|
MenuButtonWithoutSummary(FSUI_ICONVSTR(ICON_FA_STORE_SLASH, "No patches are available for this game."), false);
|
|
}
|
|
else
|
|
{
|
|
MenuHeading(FSUI_VSTR("Game Patches"));
|
|
|
|
for (const Cheats::CodeInfo& ci : code_list)
|
|
draw_code(bsi, section, ci, enable_list, false);
|
|
}
|
|
}
|
|
|
|
EndMenuButtons();
|
|
}
|
|
|
|
#endif // __ANDROID__
|