Misc: Backports from PCSX2 UI

This commit is contained in:
Connor McLaughlin
2022-10-23 14:09:54 +10:00
parent 8438506206
commit 72dfbaf6cc
37 changed files with 1037 additions and 271 deletions

View File

@ -478,7 +478,7 @@ void Achievements::UpdateSettings(const Settings& old_config)
if (!g_settings.achievements_enabled)
{
// we're done here
OnSystemShutdown();
Shutdown();
return;
}
@ -510,7 +510,7 @@ void Achievements::UpdateSettings(const Settings& old_config)
g_settings.achievements_use_first_disc_from_playlist != old_config.achievements_use_first_disc_from_playlist ||
g_settings.achievements_rich_presence != old_config.achievements_rich_presence)
{
OnSystemShutdown();
Shutdown();
Initialize();
return;
}
@ -603,14 +603,11 @@ void Achievements::SetChallengeMode(bool enabled)
GetUserUnlocks();
}
bool Achievements::OnSystemShutdown()
bool Achievements::Shutdown()
{
#ifdef WITH_RAINTEGRATION
if (IsUsingRAIntegration())
{
if (!RA_ConfirmLoadNewRom(true))
return false;
RA_SetPaused(false);
RA_ActivateGame(0);
return true;

View File

@ -91,7 +91,7 @@ void UpdateSettings(const Settings& old_config);
bool ConfirmSystemReset();
/// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted.
bool OnSystemShutdown();
bool Shutdown();
/// Called when the system is being paused and resumed.
void OnSystemPaused(bool paused);

View File

@ -32,8 +32,8 @@
#include "imgui_fullscreen.h"
#include "imgui_manager.h"
#include "imgui_overlays.h"
#include "platform_misc.h"
#include "input_manager.h"
#include "platform_misc.h"
#include "scmversion/scmversion.h"
#include "util/audio_stream.h"
#include "util/ini_settings_interface.h"
@ -116,7 +116,7 @@ void CommonHost::Shutdown()
#endif
#ifdef WITH_CHEEVOS
Achievements::OnSystemShutdown();
Achievements::Shutdown();
#endif
InputManager::CloseSources();
@ -391,7 +391,8 @@ void CommonHost::UpdateSessionTime(const std::string& new_serial)
if (!s_session_serial.empty())
{
// round up to seconds
const std::time_t etime = static_cast<std::time_t>(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_session_start_time)));
const std::time_t etime =
static_cast<std::time_t>(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_session_start_time)));
const std::time_t wtime = std::time(nullptr);
GameList::AddPlayedTimeForSerial(s_session_serial, wtime, etime);
}
@ -400,6 +401,12 @@ void CommonHost::UpdateSessionTime(const std::string& new_serial)
s_session_start_time = ctime;
}
u64 CommonHost::GetSessionPlayedTime()
{
const u64 ctime = Common::Timer::GetCurrentValue();
return static_cast<u64>(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_session_start_time)));
}
void Host::SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensity, float small_motor_intensity)
{
InputManager::SetPadVibrationIntensity(pad_index, large_or_single_motor_intensity, small_motor_intensity);

View File

@ -32,6 +32,9 @@ void PumpMessagesOnCPUThread();
bool CreateHostDisplayResources();
void ReleaseHostDisplayResources();
/// Returns the time elapsed in the current play session.
u64 GetSessionPlayedTime();
#ifdef WITH_CUBEB
std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
AudioStretchMode stretch);

View File

@ -85,10 +85,10 @@ bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
// need to release the lock while we're enumerating, because we call winId().
settings_lock.unlock();
HWND toplevel_window = static_cast<HWND>(Host::GetTopLevelWindowHandle());
AddDevices(toplevel_window);
m_toplevel_window = static_cast<HWND>(Host::GetTopLevelWindowHandle());
settings_lock.lock();
ReloadDevices();
return true;
}
@ -112,16 +112,29 @@ static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef)
return DIENUM_CONTINUE;
}
void DInputSource::AddDevices(HWND toplevel_window)
bool DInputSource::ReloadDevices()
{
// detect any removals
PollEvents();
std::vector<DIDEVICEINSTANCEW> devices;
m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY);
Log_InfoPrintf("Enumerated %zu devices", devices.size());
Log_VerbosePrintf("Enumerated %zu devices", devices.size());
bool changed = false;
for (DIDEVICEINSTANCEW inst : devices)
{
// do we already have this one?
if (std::any_of(m_controllers.begin(), m_controllers.end(),
[&inst](const ControllerData& cd) { return inst.guidInstance == cd.guid; }))
{
// yup, so skip it
continue;
}
ControllerData cd;
cd.guid = inst.guidInstance;
HRESULT hr = m_dinput->CreateDevice(inst.guidInstance, cd.device.GetAddressOf(), nullptr);
if (FAILED(hr))
{
@ -130,21 +143,24 @@ void DInputSource::AddDevices(HWND toplevel_window)
}
const std::string name(StringUtil::WideStringToUTF8String(inst.tszProductName));
if (AddDevice(cd, toplevel_window, name))
if (AddDevice(cd, name))
{
const u32 index = static_cast<u32>(m_controllers.size());
m_controllers.push_back(std::move(cd));
Host::OnInputDeviceConnected(GetDeviceIdentifier(index), name);
changed = true;
}
}
return changed;
}
bool DInputSource::AddDevice(ControllerData& cd, HWND toplevel_window, const std::string& name)
bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
{
HRESULT hr = cd.device->SetCooperativeLevel(toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE);
HRESULT hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE);
if (FAILED(hr))
{
hr = cd.device->SetCooperativeLevel(toplevel_window, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
if (FAILED(hr))
{
Log_ErrorPrintf("Failed to set cooperative level for '%s'", name.c_str());
@ -225,9 +241,6 @@ void DInputSource::PollEvents()
for (size_t i = 0; i < m_controllers.size();)
{
ControllerData& cd = m_controllers[i];
if (!cd.device)
continue;
if (cd.needs_poll)
cd.device->Poll();

View File

@ -32,6 +32,7 @@ public:
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;
@ -54,6 +55,7 @@ private:
{
ComPtr<IDirectInputDevice8W> device;
DIJOYSTATE last_state = {};
GUID guid = {};
std::vector<u32> axis_offsets;
u32 num_buttons = 0;
@ -68,8 +70,7 @@ private:
static std::array<bool, NUM_HAT_DIRECTIONS> GetHatButtons(DWORD hat);
static std::string GetDeviceIdentifier(u32 index);
void AddDevices(HWND toplevel_window);
bool AddDevice(ControllerData& cd, HWND toplevel_window, const std::string& name);
bool AddDevice(ControllerData& cd, const std::string& name);
void CheckForStateChanges(size_t index, const DIJOYSTATE& new_state);
@ -78,4 +79,5 @@ private:
HMODULE m_dinput_module{};
LPCDIDATAFORMAT m_joystick_data_format{};
ComPtr<IDirectInput8W> m_dinput;
HWND m_toplevel_window = NULL;
};

View File

@ -85,6 +85,7 @@ using ImGuiFullscreen::CenterImage;
using ImGuiFullscreen::CloseChoiceDialog;
using ImGuiFullscreen::CloseFileSelector;
using ImGuiFullscreen::DPIScale;
using ImGuiFullscreen::DrawShadowedText;
using ImGuiFullscreen::EndFullscreenColumns;
using ImGuiFullscreen::EndFullscreenColumnWindow;
using ImGuiFullscreen::EndFullscreenWindow;
@ -308,6 +309,11 @@ static void DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const
const char* format = "%d", bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
static void DrawIntSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* 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,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
static void DrawFloatRangeSetting(SettingsInterface* bsi, const char* title, const char* 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,
@ -395,7 +401,7 @@ static u32 PopulateSaveStateListEntries(const std::string& title, const std::str
static bool OpenLoadStateSelectorForGame(const std::string& game_path);
static bool OpenSaveStateSelector(bool is_loading);
static void CloseSaveStateSelector();
static void DrawSaveStateSelector(bool is_loading, bool fullscreen);
static void DrawSaveStateSelector(bool is_loading);
static bool OpenLoadStateSelectorForGameResume(const GameList::Entry* entry);
static void DrawResumeStateSelector();
static void DoLoadState(std::string path);
@ -403,6 +409,7 @@ static void DoSaveState(s32 slot, bool global);
static std::vector<SaveStateListEntry> s_save_state_selector_slots;
static std::string s_save_state_selector_game_path;
static s32 s_save_state_selector_submenu_index = -1;
static bool s_save_state_selector_open = false;
static bool s_save_state_selector_loading = true;
static bool s_save_state_selector_resuming = false;
@ -773,7 +780,7 @@ void FullscreenUI::Render()
if (s_save_state_selector_resuming)
DrawResumeStateSelector();
else
DrawSaveStateSelector(s_save_state_selector_loading, false);
DrawSaveStateSelector(s_save_state_selector_loading);
}
if (s_about_window_open)
@ -1748,6 +1755,133 @@ void FullscreenUI::DrawIntRectSetting(SettingsInterface* bsi, const char* title,
ImGui::PopFont();
}
void FullscreenUI::DrawIntSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* summary,
const char* section, const char* key, int default_value, int min_value,
int max_value, int step_value, const char* format, bool enabled, float height,
ImFont* font, ImFont* summary_font)
{
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 value_text;
if (value.has_value())
value_text.Format(format, value.value());
else
value_text = "Use Global Setting";
static bool manual_input = false;
static u32 repeat_count = 0;
if (MenuButtonWithValue(title, summary, value_text, enabled, height, font, summary_font))
{
ImGui::OpenPopup(title);
manual_input = false;
}
ImGui::SetNextWindowSize(LayoutScale(500.0f, 190.0f));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::PushFont(g_large_font);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f));
bool is_open = true;
if (ImGui::BeginPopupModal(title, &is_open,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
{
BeginMenuButtons();
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);
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::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
}
else
{
const ImVec2& padding(ImGui::GetStyle().FramePadding);
ImVec2 button_pos(ImGui::GetCursorPos());
// Align value text in middle.
ImGui::SetCursorPosY(
button_pos.y +
((LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + padding.y * 2.0f) - g_large_font->FontSize) * 0.5f);
ImGui::TextUnformatted(str_value);
s32 step = 0;
if (FloatingButton(ICON_FA_CHEVRON_UP, padding.x, button_pos.y, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font,
&button_pos, true))
{
step = step_value;
}
if (FloatingButton(ICON_FA_CHEVRON_DOWN, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true,
g_large_font, &button_pos, true))
{
step = -step_value;
}
if (FloatingButton(ICON_FA_KEYBOARD, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true,
g_large_font, &button_pos))
{
manual_input = true;
}
if (FloatingButton(ICON_FA_TRASH, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true,
g_large_font, &button_pos))
{
dlg_value = default_value;
dlg_value_changed = true;
}
if (step != 0)
{
dlg_value += step;
dlg_value_changed = true;
}
ImGui::SetCursorPosY(button_pos.y + (padding.y * 2.0f) +
LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + 10.0f));
}
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("OK", true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, ImVec2(0.5f, 0.0f)))
{
ImGui::CloseCurrentPopup();
}
EndMenuButtons();
ImGui::EndPopup();
}
ImGui::PopStyleVar(4);
ImGui::PopFont();
}
void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary,
const char* section, const char* key, const char* default_value,
const char* const* options, const char* const* option_values,
@ -1955,6 +2089,7 @@ void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, const char* title,
SetSettingsChanged(bsi);
// Host::RunOnCPUThread(&Host::Internal::UpdateEmuFolders);
s_cover_image_map.clear();
CloseFileSelector();
});
@ -2368,8 +2503,9 @@ void FullscreenUI::DrawInterfaceSettingsPage()
#endif
MenuHeading("On-Screen Display");
DrawIntRangeSetting(bsi, ICON_FA_SEARCH " OSD Scale", "Determines how large the on-screen messages and monitor are.",
"Display", "OSDScale", 100, 25, 500, "%d%%");
DrawIntSpinBoxSetting(bsi, ICON_FA_SEARCH " OSD Scale",
"Determines how large the on-screen messages and monitor are.", "Display", "OSDScale", 100, 25,
500, 1, "%d%%");
DrawToggleSetting(bsi, ICON_FA_LIST " Show OSD Messages", "Shows on-screen-display messages when events occur.",
"Display", "ShowOSDMessages", true);
DrawToggleSetting(
@ -4200,6 +4336,8 @@ void FullscreenUI::DrawAdvancedSettingsPage()
void FullscreenUI::DrawPauseMenu(MainWindowType type)
{
SmallString buffer;
ImDrawList* dl = ImGui::GetBackgroundDrawList();
const ImVec2 display_size(ImGui::GetIO().DisplaySize);
dl->AddRectFilled(ImVec2(0.0f, 0.0f), display_size, IM_COL32(0x21, 0x21, 0x21, 200));
@ -4209,15 +4347,14 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
const std::string& title = System::GetRunningTitle();
const std::string& serial = System::GetRunningSerial();
SmallString subtitle;
if (!serial.empty())
subtitle.Format("%s - ", serial.c_str());
subtitle.AppendString(Path::GetFileName(System::GetRunningPath()));
buffer.Format("%s - ", serial.c_str());
buffer.AppendString(Path::GetFileName(System::GetRunningPath()));
const ImVec2 title_size(
g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, title.c_str()));
const ImVec2 subtitle_size(
g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(), -1.0f, subtitle));
g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(), -1.0f, buffer));
ImVec2 title_pos(display_size.x - LayoutScale(20.0f + 50.0f + 20.0f) - title_size.x,
display_size.y - LayoutScale(20.0f + 50.0f));
@ -4244,14 +4381,14 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
subtitle_pos.x -= rp_height;
subtitle_pos.y -= rp_height;
dl->AddText(g_medium_font, g_medium_font->FontSize, rp_pos, IM_COL32(255, 255, 255, 255), rp.data(),
rp.data() + rp.size(), wrap_width);
DrawShadowedText(dl, g_medium_font, rp_pos, IM_COL32(255, 255, 255, 255), rp.data(), rp.data() + rp.size(),
wrap_width);
}
}
#endif
dl->AddText(g_large_font, g_large_font->FontSize, title_pos, IM_COL32(255, 255, 255, 255), title.c_str());
dl->AddText(g_medium_font, g_medium_font->FontSize, subtitle_pos, IM_COL32(255, 255, 255, 255), subtitle);
DrawShadowedText(dl, g_large_font, title_pos, IM_COL32(255, 255, 255, 255), title.c_str());
DrawShadowedText(dl, g_medium_font, subtitle_pos, IM_COL32(255, 255, 255, 255), buffer);
const ImVec2 image_min(display_size.x - LayoutScale(20.0f + 50.0f) - rp_height,
display_size.y - LayoutScale(20.0f + 50.0f) - rp_height);
@ -4259,6 +4396,43 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
dl->AddImage(GetCoverForCurrentGame(), image_min, image_max);
}
// current time / play time
{
buffer.Fmt("{:%X}", fmt::localtime(std::time(nullptr)));
const ImVec2 time_size(g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f,
buffer.GetCharArray(),
buffer.GetCharArray() + buffer.GetLength()));
const ImVec2 time_pos(display_size.x - LayoutScale(10.0f) - time_size.x, LayoutScale(10.0f));
DrawShadowedText(dl, g_large_font, time_pos, IM_COL32(255, 255, 255, 255), buffer.GetCharArray(),
buffer.GetCharArray() + buffer.GetLength());
const std::string& serial = System::GetRunningSerial();
if (!serial.empty())
{
const std::time_t cached_played_time = GameList::GetCachedPlayedTimeForSerial(serial);
const std::time_t session_time = static_cast<std::time_t>(CommonHost::GetSessionPlayedTime());
buffer.Fmt("Session: {}", GameList::FormatTimespan(session_time, true).GetStringView());
const ImVec2 session_size(g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(),
-1.0f, buffer.GetCharArray(),
buffer.GetCharArray() + buffer.GetLength()));
const ImVec2 session_pos(display_size.x - LayoutScale(10.0f) - session_size.x,
time_pos.y + g_large_font->FontSize + LayoutScale(4.0f));
DrawShadowedText(dl, g_medium_font, session_pos, IM_COL32(255, 255, 255, 255), buffer.GetCharArray(),
buffer.GetCharArray() + buffer.GetLength());
buffer.Fmt("All Time: {}", GameList::FormatTimespan(cached_played_time + session_time, true).GetStringView());
const ImVec2 total_size(g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(),
-1.0f, buffer.GetCharArray(),
buffer.GetCharArray() + buffer.GetLength()));
const ImVec2 total_pos(display_size.x - LayoutScale(10.0f) - total_size.x,
session_pos.y + g_medium_font->FontSize + LayoutScale(4.0f));
DrawShadowedText(dl, g_medium_font, total_pos, IM_COL32(255, 255, 255, 255), buffer.GetCharArray(),
buffer.GetCharArray() + buffer.GetLength());
}
}
const ImVec2 window_size(LayoutScale(500.0f, LAYOUT_SCREEN_HEIGHT));
const ImVec2 window_pos(0.0f, display_size.y - window_size.y);
@ -4420,12 +4594,14 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
void FullscreenUI::InitializePlaceholderSaveStateListEntry(SaveStateListEntry* li, const std::string& title,
const std::string& serial, s32 slot, bool global)
{
li->title = fmt::format("{0} {1} Slot {2}##{1}_slot_{2}", title, global ? "Global" : "Game", slot);
li->summary = "No Save State";
li->title = (global || slot > 0) ? fmt::format("{0} Slot {1}##{0}_slot_{1}", global ? "Global" : "Game", slot) :
std::string("Quick Save");
li->summary = "No save present in this slot.";
li->path = {};
li->timestamp = 0;
li->slot = slot;
li->preview_texture = {};
li->global = global;
}
bool FullscreenUI::InitializeSaveStateListEntry(SaveStateListEntry* li, const std::string& title,
@ -4442,17 +4618,18 @@ bool FullscreenUI::InitializeSaveStateListEntry(SaveStateListEntry* li, const st
if (global)
{
li->title = StringUtil::StdStringFromFormat("Global Save %d - %s##global_slot_%d", slot, ssi->title.c_str(), slot);
li->title = fmt::format("Global Slot {0} - {1}##global_slot_{0}", slot, ssi->serial);
}
else
{
li->title = StringUtil::StdStringFromFormat("%s Slot %d##game_slot_%d", ssi->title.c_str(), slot, slot);
li->title = (slot > 0) ? fmt::format("Game Slot {0}##game_slot_{0}", slot) : std::string("Game Quick Save");
}
li->summary = fmt::format("{} - Saved {:%c}", ssi->serial.c_str(), fmt::localtime(ssi->timestamp));
li->summary = fmt::format("Saved {:%c}", fmt::localtime(ssi->timestamp));
li->timestamp = ssi->timestamp;
li->slot = slot;
li->path = std::move(filename);
li->global = global;
PopulateSaveStateScreenshot(li, &ssi.value());
return true;
@ -4571,125 +4748,297 @@ void FullscreenUI::CloseSaveStateSelector()
ReturnToMainWindow();
}
void FullscreenUI::DrawSaveStateSelector(bool is_loading, bool fullscreen)
void FullscreenUI::DrawSaveStateSelector(bool is_loading)
{
if (fullscreen)
ImGuiIO& io = ImGui::GetIO();
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
ImGui::SetNextWindowSize(io.DisplaySize);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
const char* window_title = is_loading ? "Load State" : "Save State";
ImGui::OpenPopup(window_title);
bool is_open = true;
const bool valid =
ImGui::BeginPopupModal(window_title, &is_open,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBackground);
if (!valid || !is_open)
{
if (!BeginFullscreenColumns())
{
EndFullscreenColumns();
return;
}
if (valid)
ImGui::EndPopup();
if (!BeginFullscreenColumnWindow(0.0f, LAYOUT_SCREEN_WIDTH, "save_state_selector_slots"))
{
EndFullscreenColumnWindow();
EndFullscreenColumns();
return;
}
}
else
{
const char* window_title = is_loading ? "Load State" : "Save State";
ImGui::PushFont(g_large_font);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING));
ImGui::SetNextWindowSize(LayoutScale(1000.0f, 680.0f));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::OpenPopup(window_title);
bool is_open = !WantsToCloseMenu();
if (!ImGui::BeginPopupModal(window_title, &is_open,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove) ||
!is_open)
{
ImGui::PopStyleVar(2);
ImGui::PopFont();
ImGui::PopStyleVar(5);
if (!is_open)
CloseSaveStateSelector();
return;
}
return;
}
BeginMenuButtons();
ImVec2 heading_size = ImVec2(
io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f + 2.0f));
static constexpr float padding = 10.0f;
static constexpr float button_height = 96.0f;
static constexpr float max_image_width = 96.0f;
static constexpr float max_image_height = 96.0f;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ModAlpha(UIPrimaryColor, 0.9f));
for (const SaveStateListEntry& entry : s_save_state_selector_slots)
if (ImGui::BeginChild("state_titlebar", heading_size, false, ImGuiWindowFlags_NoNav))
{
ImRect bb;
bool visible, hovered;
bool pressed = MenuButtonFrame(entry.title.c_str(), true, button_height, &visible, &hovered, &bb.Min, &bb.Max);
if (!visible)
continue;
BeginNavBar();
if (NavButton(ICON_FA_BACKWARD, true, true))
CloseSaveStateSelector();
ImVec2 pos(bb.Min);
NavTitle(is_loading ? "Load State" : "Save State");
EndNavBar();
ImGui::EndChild();
}
// use aspect ratio of screenshot to determine height
const GPUTexture* image = entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get();
const float image_height =
max_image_width / (static_cast<float>(image->GetWidth()) / static_cast<float>(image->GetHeight()));
const float image_margin = (max_image_height - image_height) / 2.0f;
const ImRect image_bb(ImVec2(pos.x, pos.y + LayoutScale(image_margin)),
pos + LayoutScale(max_image_width, image_margin + image_height));
pos.x += LayoutScale(max_image_width + padding);
ImGui::PopStyleColor();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ModAlpha(UIBackgroundColor, 0.9f));
ImGui::SetCursorPos(ImVec2(0.0f, heading_size.y));
ImRect text_bb(pos, ImVec2(bb.Max.x, pos.y + g_large_font->FontSize));
ImGui::PushFont(g_large_font);
ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, entry.title.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f),
&text_bb);
ImGui::PopFont();
bool close_handled = false;
if (s_save_state_selector_open &&
ImGui::BeginChild("state_list", ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y), false,
ImGuiWindowFlags_NavFlattened))
{
BeginMenuButtons();
ImGui::PushFont(g_medium_font);
const ImGuiStyle& style = ImGui::GetStyle();
if (!entry.summary.empty())
const float title_spacing = LayoutScale(10.0f);
const float summary_spacing = LayoutScale(4.0f);
const float item_spacing = LayoutScale(20.0f);
const float item_width_with_spacing = std::floor(LayoutScale(LAYOUT_SCREEN_WIDTH / 4.0f));
const float item_width = item_width_with_spacing - item_spacing;
const float image_width = item_width - (style.FramePadding.x * 2.0f);
const float image_height = image_width / 1.33f;
const ImVec2 image_size(image_width, image_height);
const float item_height = (style.FramePadding.y * 2.0f) + image_height + title_spacing + g_large_font->FontSize +
summary_spacing + g_medium_font->FontSize;
const ImVec2 item_size(item_width, item_height);
const u32 grid_count_x = static_cast<u32>(std::floor(ImGui::GetWindowWidth() / item_width_with_spacing));
const float start_x =
(static_cast<float>(ImGui::GetWindowWidth()) - (item_width_with_spacing * static_cast<float>(grid_count_x))) *
0.5f;
u32 grid_x = 0;
u32 grid_y = 0;
ImGui::SetCursorPos(ImVec2(start_x, 0.0f));
for (u32 i = 0; i < s_save_state_selector_slots.size(); i++)
{
text_bb.Min.y = text_bb.Max.y + LayoutScale(4.0f);
text_bb.Max.y = text_bb.Min.y + g_medium_font->FontSize;
ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, entry.summary.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f),
&text_bb);
}
if (i == 0)
ResetFocusHere();
if (!entry.path.empty())
{
text_bb.Min.y = text_bb.Max.y + LayoutScale(4.0f);
text_bb.Max.y = text_bb.Min.y + g_medium_font->FontSize;
ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, entry.path.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f),
&text_bb);
}
const SaveStateListEntry& entry = s_save_state_selector_slots[i];
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
continue;
ImGui::PopFont();
const ImGuiID id = window->GetID(static_cast<int>(i));
const ImVec2 pos(window->DC.CursorPos);
ImRect bb(pos, pos + item_size);
ImGui::ItemSize(item_size);
if (ImGui::ItemAdd(bb, id))
{
bool held;
bool hovered;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
if (hovered)
{
const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f);
ImGui::GetWindowDrawList()->AddImage(
static_cast<ImTextureID>(entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get()),
image_bb.Min, image_bb.Max);
const float t = std::min(static_cast<float>(std::abs(std::sin(ImGui::GetTime() * 0.75) * 1.1)), 1.0f);
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetColorU32(ImGuiCol_Border, t));
if (pressed)
{
if (is_loading)
DoLoadState(entry.path);
ImGui::RenderFrame(bb.Min, bb.Max, col, true, 0.0f);
ImGui::PopStyleColor();
}
bb.Min += style.FramePadding;
bb.Max -= style.FramePadding;
GPUTexture* const screenshot =
entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get();
const ImRect image_rect(
CenterImage(ImRect(bb.Min, bb.Min + image_size),
ImVec2(static_cast<float>(screenshot->GetWidth()), static_cast<float>(screenshot->GetHeight()))));
ImGui::GetWindowDrawList()->AddImage(screenshot, image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f),
ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
const ImVec2 title_pos(bb.Min.x, bb.Min.y + image_height + title_spacing);
const ImRect title_bb(title_pos, ImVec2(bb.Max.x, title_pos.y + g_large_font->FontSize));
ImGui::PushFont(g_large_font);
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, entry.title.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f),
&title_bb);
ImGui::PopFont();
if (!entry.summary.empty())
{
const ImVec2 summary_pos(bb.Min.x, title_pos.y + g_large_font->FontSize + summary_spacing);
const ImRect summary_bb(summary_pos, ImVec2(bb.Max.x, summary_pos.y + g_medium_font->FontSize));
ImGui::PushFont(g_medium_font);
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, entry.summary.c_str(), nullptr, nullptr,
ImVec2(0.0f, 0.0f), &summary_bb);
ImGui::PopFont();
}
if (pressed)
{
if (is_loading)
{
DoLoadState(entry.path);
CloseSaveStateSelector();
break;
}
else
{
DoSaveState(entry.slot, entry.global);
CloseSaveStateSelector();
break;
}
}
if (hovered && (ImGui::IsItemClicked(ImGuiMouseButton_Right) ||
ImGui::IsNavInputTest(ImGuiNavInput_Input, ImGuiNavReadMode_Pressed)))
{
s_save_state_selector_submenu_index = static_cast<s32>(i);
}
if (static_cast<s32>(i) == s_save_state_selector_submenu_index)
{
// can't use a choice dialog here, because we're already in a modal...
ImGuiFullscreen::PushResetLayout();
ImGui::PushFont(g_large_font);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, MulAlpha(UIBackgroundColor, 0.95f));
const float width = LayoutScale(600.0f);
const float title_height =
g_large_font->FontSize + ImGui::GetStyle().FramePadding.y * 2.0f + ImGui::GetStyle().WindowPadding.y * 2.0f;
const float height =
title_height +
LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + (LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f)) * 3.0f;
ImGui::SetNextWindowSize(ImVec2(width, height));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::OpenPopup(entry.title.c_str());
// don't let the back button flow through to the main window
bool submenu_open = !WantsToCloseMenu();
close_handled ^= submenu_open;
bool closed = false;
if (ImGui::BeginPopupModal(entry.title.c_str(), &is_open,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
{
ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor);
BeginMenuButtons();
if (ActiveButton(is_loading ? ICON_FA_FOLDER_OPEN " Load State" : ICON_FA_FOLDER_OPEN " Save State", false,
true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY))
{
if (is_loading)
DoLoadState(std::move(entry.path));
else
DoSaveState(entry.slot, entry.global);
CloseSaveStateSelector();
closed = true;
}
if (ActiveButton(ICON_FA_FOLDER_MINUS " Delete Save", false, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY))
{
if (!FileSystem::FileExists(entry.path.c_str()))
{
ShowToast({}, fmt::format("{} does not exist.", ImGuiFullscreen::RemoveHash(entry.title)));
is_open = true;
}
else if (FileSystem::DeleteFile(entry.path.c_str()))
{
ShowToast({}, fmt::format("{} deleted.", ImGuiFullscreen::RemoveHash(entry.title)));
s_save_state_selector_slots.erase(s_save_state_selector_slots.begin() + i);
if (s_save_state_selector_slots.empty())
{
CloseSaveStateSelector();
closed = true;
}
else
{
is_open = false;
}
}
else
{
ShowToast({}, fmt::format("Failed to delete {}.", ImGuiFullscreen::RemoveHash(entry.title)));
is_open = false;
}
}
if (ActiveButton(ICON_FA_WINDOW_CLOSE " Close Menu", false, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY))
{
is_open = false;
}
EndMenuButtons();
ImGui::PopStyleColor();
ImGui::EndPopup();
}
if (!is_open)
{
s_save_state_selector_submenu_index = -1;
if (!closed)
QueueResetFocus();
}
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(3);
ImGui::PopFont();
ImGuiFullscreen::PopResetLayout();
if (closed)
break;
}
}
grid_x++;
if (grid_x == grid_count_x)
{
grid_x = 0;
grid_y++;
ImGui::SetCursorPosX(start_x);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + item_spacing);
}
else
DoSaveState(entry.slot, entry.global);
{
ImGui::SameLine(start_x + static_cast<float>(grid_x) * (item_width + item_spacing));
}
}
EndMenuButtons();
ImGui::EndChild();
}
EndMenuButtons();
ImGui::PopStyleColor();
if (fullscreen)
{
EndFullscreenColumnWindow();
EndFullscreenColumns();
}
else
{
ImGui::EndPopup();
ImGui::PopStyleVar(2);
ImGui::PopFont();
}
ImGui::EndPopup();
ImGui::PopStyleVar(5);
if (!close_handled && WantsToCloseMenu())
CloseSaveStateSelector();
}
bool FullscreenUI::OpenLoadStateSelectorForGameResume(const GameList::Entry* entry)
@ -4821,15 +5170,79 @@ void FullscreenUI::DoSaveState(s32 slot, bool global)
void FullscreenUI::PopulateGameListEntryList()
{
const s32 sort = Host::GetBaseIntSettingValue("Main", "FullscreenUIGameSort", 0);
const bool reverse = Host::GetBaseBoolSettingValue("Main", "FullscreenUIGameSortReverse", false);
const u32 count = GameList::GetEntryCount();
s_game_list_sorted_entries.resize(count);
for (u32 i = 0; i < count; i++)
s_game_list_sorted_entries[i] = GameList::GetEntryByIndex(i);
// TODO: Custom sort types
std::sort(s_game_list_sorted_entries.begin(), s_game_list_sorted_entries.end(),
[](const GameList::Entry* lhs, const GameList::Entry* rhs) {
return StringUtil::Strcasecmp(lhs->title.c_str(), rhs->title.c_str()) < 0;
[sort, reverse](const GameList::Entry* lhs, const GameList::Entry* rhs) {
switch (sort)
{
case 0: // Type
{
if (lhs->type != rhs->type)
return reverse ? (lhs->type > rhs->type) : (lhs->type < rhs->type);
}
break;
case 1: // Serial
{
if (lhs->serial != rhs->serial)
return reverse ? (lhs->serial > rhs->serial) : (lhs->serial < rhs->serial);
}
break;
case 2: // Title
break;
case 3: // File Title
{
const std::string_view lhs_title(Path::GetFileTitle(lhs->path));
const std::string_view rhs_title(Path::GetFileTitle(rhs->path));
const int res = StringUtil::Strncasecmp(lhs_title.data(), rhs_title.data(),
std::min(lhs_title.size(), rhs_title.size()));
if (res != 0)
return reverse ? (res > 0) : (res < 0);
}
break;
case 4: // Time Played
{
if (lhs->total_played_time != rhs->total_played_time)
{
return reverse ? (lhs->total_played_time > rhs->total_played_time) :
(lhs->total_played_time < rhs->total_played_time);
}
}
break;
case 5: // Last Played (reversed by default)
{
if (lhs->last_played_time != rhs->last_played_time)
{
return reverse ? (lhs->last_played_time < rhs->last_played_time) :
(lhs->last_played_time > rhs->last_played_time);
}
}
break;
case 6: // Size
{
if (lhs->total_size != rhs->total_size)
{
return reverse ? (lhs->total_size > rhs->total_size) : (lhs->total_size < rhs->total_size);
}
}
break;
}
// fallback to title when all else is equal
const int res = StringUtil::Strcasecmp(lhs->title.c_str(), rhs->title.c_str());
return reverse ? (res > 0) : (res < 0);
});
}
@ -5371,20 +5784,35 @@ void FullscreenUI::DrawGameListSettingsPage(const ImVec2& heading_size)
}
}
static constexpr const char* view_types[] = {"Game Grid", "Game List"};
MenuHeading("List Settings");
{
static constexpr const char* view_types[] = {"Game Grid", "Game List"};
static constexpr const char* sort_types[] = {"Type", "Serial", "Title", "File Title",
"Time Played", "Last Played", "Size"};
DrawIntListSetting(bsi, ICON_FA_BORDER_ALL " Default View", "Sets which view the game list will open to.", "Main",
"DefaultFullscreenUIGameView", 0, view_types, std::size(view_types));
DrawIntListSetting(bsi, ICON_FA_SORT " Sort By", "Determines which field the game list will be sorted by.", "Main",
"FullscreenUIGameSort", 0, sort_types, std::size(sort_types));
DrawToggleSetting(bsi, ICON_FA_SORT_ALPHA_DOWN " Sort Reversed",
"Reverses the game list sort order from the default (usually ascending to descending).", "Main",
"FullscreenUIGameSortReverse", false);
}
MenuHeading("Cover Settings");
DrawFolderSetting(bsi, ICON_FA_FOLDER " Covers Directory", "Folders", "Covers", EmuFolders::Covers);
if (MenuButton(ICON_FA_DOWNLOAD " Download Covers", "Downloads covers from a user-specified URL template."))
ImGui::OpenPopup("Download Covers");
DrawIntListSetting(bsi, ICON_FA_BORDER_ALL " Default View", "Sets which view the game list will open to.", "Main",
"DefaultFullscreenUIGameView", 0, view_types, std::size(view_types));
{
DrawFolderSetting(bsi, ICON_FA_FOLDER " Covers Directory", "Folders", "Covers", EmuFolders::Covers);
if (MenuButton(ICON_FA_DOWNLOAD " Download Covers", "Downloads covers from a user-specified URL template."))
ImGui::OpenPopup("Download Covers");
}
MenuHeading("Operations");
if (MenuButton(ICON_FA_SEARCH " Scan For New Games", "Identifies any new files added to the game directories."))
Host::RefreshGameListAsync(false);
if (MenuButton(ICON_FA_SEARCH_PLUS " Rescan All Games", "Forces a full rescan of all games previously identified."))
Host::RefreshGameListAsync(true);
{
if (MenuButton(ICON_FA_SEARCH " Scan For New Games", "Identifies any new files added to the game directories."))
Host::RefreshGameListAsync(false);
if (MenuButton(ICON_FA_SEARCH_PLUS " Rescan All Games", "Forces a full rescan of all games previously identified."))
Host::RefreshGameListAsync(true);
}
EndMenuButtons();

View File

@ -82,8 +82,8 @@ static PlayedTimeEntry UpdatePlayedTimeFile(const std::string& path, const std::
static std::vector<GameList::Entry> s_entries;
static std::recursive_mutex s_mutex;
static GameList::CacheMap m_cache_map;
static std::unique_ptr<ByteStream> m_cache_write_stream;
static GameList::CacheMap s_cache_map;
static std::unique_ptr<ByteStream> s_cache_write_stream;
static bool m_game_list_loaded = false;
@ -276,12 +276,12 @@ bool GameList::PopulateEntryFromPath(const std::string& path, Entry* entry)
bool GameList::GetGameListEntryFromCache(const std::string& path, Entry* entry)
{
auto iter = m_cache_map.find(path);
if (iter == m_cache_map.end())
auto iter = UnorderedStringMapFind(s_cache_map, path);
if (iter == s_cache_map.end())
return false;
*entry = std::move(iter->second);
m_cache_map.erase(iter);
s_cache_map.erase(iter);
return true;
}
@ -324,11 +324,11 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
ge.type = static_cast<EntryType>(type);
ge.compatibility = static_cast<GameDatabase::CompatibilityRating>(compatibility_rating);
auto iter = m_cache_map.find(ge.path);
if (iter != m_cache_map.end())
auto iter = UnorderedStringMapFind(s_cache_map, ge.path);
if (iter != s_cache_map.end())
iter->second = std::move(ge);
else
m_cache_map.emplace(std::move(path), std::move(ge));
s_cache_map.emplace(std::move(path), std::move(ge));
}
return true;
@ -337,23 +337,23 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
bool GameList::WriteEntryToCache(const Entry* entry)
{
bool result = true;
result &= m_cache_write_stream->WriteU8(static_cast<u8>(entry->type));
result &= m_cache_write_stream->WriteU8(static_cast<u8>(entry->region));
result &= m_cache_write_stream->WriteSizePrefixedString(entry->path);
result &= m_cache_write_stream->WriteSizePrefixedString(entry->serial);
result &= m_cache_write_stream->WriteSizePrefixedString(entry->title);
result &= m_cache_write_stream->WriteSizePrefixedString(entry->genre);
result &= m_cache_write_stream->WriteSizePrefixedString(entry->publisher);
result &= m_cache_write_stream->WriteSizePrefixedString(entry->developer);
result &= m_cache_write_stream->WriteU64(entry->total_size);
result &= m_cache_write_stream->WriteU64(entry->last_modified_time);
result &= m_cache_write_stream->WriteU64(entry->release_date);
result &= m_cache_write_stream->WriteU32(entry->supported_controllers);
result &= m_cache_write_stream->WriteU8(entry->min_players);
result &= m_cache_write_stream->WriteU8(entry->max_players);
result &= m_cache_write_stream->WriteU8(entry->min_blocks);
result &= m_cache_write_stream->WriteU8(entry->max_blocks);
result &= m_cache_write_stream->WriteU8(static_cast<u8>(entry->compatibility));
result &= s_cache_write_stream->WriteU8(static_cast<u8>(entry->type));
result &= s_cache_write_stream->WriteU8(static_cast<u8>(entry->region));
result &= s_cache_write_stream->WriteSizePrefixedString(entry->path);
result &= s_cache_write_stream->WriteSizePrefixedString(entry->serial);
result &= s_cache_write_stream->WriteSizePrefixedString(entry->title);
result &= s_cache_write_stream->WriteSizePrefixedString(entry->genre);
result &= s_cache_write_stream->WriteSizePrefixedString(entry->publisher);
result &= s_cache_write_stream->WriteSizePrefixedString(entry->developer);
result &= s_cache_write_stream->WriteU64(entry->total_size);
result &= s_cache_write_stream->WriteU64(entry->last_modified_time);
result &= s_cache_write_stream->WriteU64(entry->release_date);
result &= s_cache_write_stream->WriteU32(entry->supported_controllers);
result &= s_cache_write_stream->WriteU8(entry->min_players);
result &= s_cache_write_stream->WriteU8(entry->max_players);
result &= s_cache_write_stream->WriteU8(entry->min_blocks);
result &= s_cache_write_stream->WriteU8(entry->max_blocks);
result &= s_cache_write_stream->WriteU8(static_cast<u8>(entry->compatibility));
return result;
}
@ -374,7 +374,7 @@ void GameList::LoadCache()
{
Log_WarningPrintf("Deleting corrupted cache file '%s'", filename.c_str());
stream.reset();
m_cache_map.clear();
s_cache_map.clear();
DeleteCacheFile();
return;
}
@ -383,37 +383,37 @@ void GameList::LoadCache()
bool GameList::OpenCacheForWriting()
{
const std::string cache_filename(GetCacheFilename());
Assert(!m_cache_write_stream);
Assert(!s_cache_write_stream);
m_cache_write_stream = ByteStream::OpenFile(cache_filename.c_str(),
s_cache_write_stream = ByteStream::OpenFile(cache_filename.c_str(),
BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_SEEKABLE);
if (m_cache_write_stream)
if (s_cache_write_stream)
{
// check the header
u32 signature, version;
if (m_cache_write_stream->ReadU32(&signature) && signature == GAME_LIST_CACHE_SIGNATURE &&
m_cache_write_stream->ReadU32(&version) && version == GAME_LIST_CACHE_VERSION &&
m_cache_write_stream->SeekToEnd())
if (s_cache_write_stream->ReadU32(&signature) && signature == GAME_LIST_CACHE_SIGNATURE &&
s_cache_write_stream->ReadU32(&version) && version == GAME_LIST_CACHE_VERSION &&
s_cache_write_stream->SeekToEnd())
{
return true;
}
m_cache_write_stream.reset();
s_cache_write_stream.reset();
}
Log_InfoPrintf("Creating new game list cache file: '%s'", cache_filename.c_str());
m_cache_write_stream = ByteStream::OpenFile(
s_cache_write_stream = ByteStream::OpenFile(
cache_filename.c_str(), BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_TRUNCATE | BYTESTREAM_OPEN_WRITE);
if (!m_cache_write_stream)
if (!s_cache_write_stream)
return false;
// new cache file, write header
if (!m_cache_write_stream->WriteU32(GAME_LIST_CACHE_SIGNATURE) ||
!m_cache_write_stream->WriteU32(GAME_LIST_CACHE_VERSION))
if (!s_cache_write_stream->WriteU32(GAME_LIST_CACHE_SIGNATURE) ||
!s_cache_write_stream->WriteU32(GAME_LIST_CACHE_VERSION))
{
Log_ErrorPrintf("Failed to write game list cache header");
m_cache_write_stream.reset();
s_cache_write_stream.reset();
FileSystem::DeleteFile(cache_filename.c_str());
return false;
}
@ -423,16 +423,16 @@ bool GameList::OpenCacheForWriting()
void GameList::CloseCacheFileStream()
{
if (!m_cache_write_stream)
if (!s_cache_write_stream)
return;
m_cache_write_stream->Commit();
m_cache_write_stream.reset();
s_cache_write_stream->Commit();
s_cache_write_stream.reset();
}
void GameList::DeleteCacheFile()
{
Assert(!m_cache_write_stream);
Assert(!s_cache_write_stream);
const std::string filename(GetCacheFilename());
if (!FileSystem::FileExists(filename.c_str()))
@ -528,7 +528,7 @@ bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_loc
entry.path = std::move(path);
entry.last_modified_time = timestamp;
if (m_cache_write_stream || OpenCacheForWriting())
if (s_cache_write_stream || OpenCacheForWriting())
{
if (!WriteEntryToCache(&entry))
Log_WarningPrintf("Failed to write entry '%s' to cache", entry.path.c_str());
@ -606,9 +606,9 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback*
old_entries.swap(s_entries);
}
const std::vector<std::string> excluded_paths(Host::GetStringListSetting("GameList", "ExcludedPaths"));
const std::vector<std::string> dirs(Host::GetStringListSetting("GameList", "Paths"));
const std::vector<std::string> recursive_dirs(Host::GetStringListSetting("GameList", "RecursivePaths"));
const std::vector<std::string> excluded_paths(Host::GetBaseStringListSetting("GameList", "ExcludedPaths"));
const std::vector<std::string> dirs(Host::GetBaseStringListSetting("GameList", "Paths"));
const std::vector<std::string> recursive_dirs(Host::GetBaseStringListSetting("GameList", "RecursivePaths"));
const PlayedTimeMap played_time(LoadPlayedTimeMap(GetPlayedTimeFile()));
if (!dirs.empty() || !recursive_dirs.empty())
@ -638,7 +638,7 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback*
// don't need unused cache entries
CloseCacheFileStream();
m_cache_map.clear();
s_cache_map.clear();
}
std::string GameList::GetCoverImagePathForEntry(const Entry* entry)
@ -774,7 +774,8 @@ GameList::PlayedTimeMap GameList::LoadPlayedTimeMap(const std::string& path)
{
PlayedTimeMap ret;
auto fp = FileSystem::OpenManagedCFile(path.c_str(), "rb");
// Use write mode here, even though we're not writing, so we can lock the file from other updates.
auto fp = FileSystem::OpenManagedCFile(path.c_str(), "r+b");
#ifdef _WIN32
// On Windows, the file is implicitly locked.
@ -902,6 +903,21 @@ void GameList::AddPlayedTimeForSerial(const std::string& serial, std::time_t las
}
}
std::time_t GameList::GetCachedPlayedTimeForSerial(const std::string& serial)
{
if (serial.empty())
return 0;
std::unique_lock<std::recursive_mutex> lock(s_mutex);
for (GameList::Entry& entry : s_entries)
{
if (entry.serial == serial)
return entry.total_played_time;
}
return 0;
}
TinyString GameList::FormatTimestamp(std::time_t timestamp)
{
TinyString ret;
@ -943,23 +959,33 @@ TinyString GameList::FormatTimestamp(std::time_t timestamp)
return ret;
}
TinyString GameList::FormatTimespan(std::time_t timespan)
TinyString GameList::FormatTimespan(std::time_t timespan, bool long_format)
{
const u32 hours = static_cast<u32>(timespan / 3600);
const u32 minutes = static_cast<u32>((timespan % 3600) / 60);
const u32 seconds = static_cast<u32>((timespan % 3600) % 60);
TinyString ret;
if (hours >= 100)
ret.Fmt(Host::TranslateString("GameList", "{}h {}m").GetCharArray(), hours, minutes);
else if (hours > 0)
ret.Fmt(Host::TranslateString("GameList", "{}h {}m {}s").GetCharArray(), hours, minutes, seconds);
else if (minutes > 0)
ret.Fmt(Host::TranslateString("GameList", "{}m {}s").GetCharArray(), minutes, seconds);
else if (seconds > 0)
ret.Fmt(Host::TranslateString("GameList", "{}s").GetCharArray(), seconds);
if (!long_format)
{
if (hours >= 100)
ret.Fmt(Host::TranslateString("GameList", "{}h {}m").GetCharArray(), hours, minutes);
else if (hours > 0)
ret.Fmt(Host::TranslateString("GameList", "{}h {}m {}s").GetCharArray(), hours, minutes, seconds);
else if (minutes > 0)
ret.Fmt(Host::TranslateString("GameList", "{}m {}s").GetCharArray(), minutes, seconds);
else if (seconds > 0)
ret.Fmt(Host::TranslateString("GameList", "{}s").GetCharArray(), seconds);
else
ret = Host::TranslateString("GameList", "None");
}
else
ret = Host::TranslateString("GameList", "None");
{
if (hours > 0)
ret = fmt::format(Host::TranslateString("GameList", "{} hours").GetCharArray(), hours);
else
ret = fmt::format(Host::TranslateString("GameList", "{} minutes").GetCharArray(), minutes);
}
return ret;
}

View File

@ -79,11 +79,14 @@ void Refresh(bool invalidate_cache, bool only_cache = false, ProgressCallback* p
/// Add played time for the specified serial.
void AddPlayedTimeForSerial(const std::string& serial, std::time_t last_time, std::time_t add_time);
/// Returns the total time played for a game. Requires the game to be scanned in the list.
std::time_t GetCachedPlayedTimeForSerial(const std::string& serial);
/// Formats a timestamp to something human readable (e.g. Today, Yesterday, 10/11/12).
TinyString FormatTimestamp(std::time_t timestamp);
/// Formats a timespan to something human readable (e.g. 1h2m3s).
TinyString FormatTimespan(std::time_t timespan);
/// Formats a timespan to something human readable (e.g. 1h2m3s or 1 hour).
TinyString FormatTimespan(std::time_t timespan, bool long_format = false);
std::string GetCoverImagePathForEntry(const Entry* entry);
std::string GetCoverImagePath(const std::string& path, const std::string& serial, const std::string& title);

View File

@ -440,7 +440,31 @@ void ImGuiFullscreen::BeginLayout()
// we evict from the texture cache at the start of the frame, in case we go over mid-frame,
// we need to keep all those textures alive until the end of the frame
s_texture_cache.ManualEvict();
PushResetLayout();
}
void ImGuiFullscreen::EndLayout()
{
DrawFileSelector();
DrawChoiceDialog();
DrawInputDialog();
DrawMessageDialog();
const float notification_margin = LayoutScale(10.0f);
const float spacing = LayoutScale(10.0f);
const float notification_vertical_pos = GetNotificationVerticalPosition();
ImVec2 position(notification_margin,
notification_vertical_pos * ImGui::GetIO().DisplaySize.y +
((notification_vertical_pos >= 0.5f) ? -notification_margin : notification_margin));
DrawBackgroundProgressDialogs(position, spacing);
DrawNotifications(position, spacing);
DrawToast();
PopResetLayout();
}
void ImGuiFullscreen::PushResetLayout()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(8.0f, 8.0f));
@ -465,23 +489,8 @@ void ImGuiFullscreen::BeginLayout()
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, UIPrimaryDarkColor);
}
void ImGuiFullscreen::EndLayout()
void ImGuiFullscreen::PopResetLayout()
{
DrawFileSelector();
DrawChoiceDialog();
DrawInputDialog();
DrawMessageDialog();
const float notification_margin = LayoutScale(10.0f);
const float spacing = LayoutScale(10.0f);
const float notification_vertical_pos = GetNotificationVerticalPosition();
ImVec2 position(notification_margin,
notification_vertical_pos * ImGui::GetIO().DisplaySize.y +
((notification_vertical_pos >= 0.5f) ? -notification_margin : notification_margin));
DrawBackgroundProgressDialogs(position, spacing);
DrawNotifications(position, spacing);
DrawToast();
ImGui::PopStyleColor(10);
ImGui::PopStyleVar(12);
}
@ -665,7 +674,7 @@ void ImGuiFullscreen::BeginMenuButtons(u32 num_items, float y_align, float x_pad
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(x_padding, y_padding));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, LayoutScale(1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
if (y_align != 0.0f)
@ -993,7 +1002,8 @@ bool ImGuiFullscreen::MenuImageButton(const char* title, const char* summary, Im
}
bool ImGuiFullscreen::FloatingButton(const char* text, float x, float y, float width, float height, float anchor_x,
float anchor_y, bool enabled, ImFont* font, ImVec2* out_position)
float anchor_y, bool enabled, ImFont* font, ImVec2* out_position,
bool repeat_button)
{
const ImVec2 text_size(font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), 0.0f, text));
const ImVec2& padding(ImGui::GetStyle().FramePadding);
@ -1047,11 +1057,14 @@ bool ImGuiFullscreen::FloatingButton(const char* text, float x, float y, float w
bool pressed;
if (enabled)
{
pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, repeat_button ? ImGuiButtonFlags_Repeat : 0);
if (hovered)
{
const float t = std::min(static_cast<float>(std::abs(std::sin(ImGui::GetTime() * 0.75) * 1.1)), 1.0f);
const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetColorU32(ImGuiCol_Border, t));
ImGui::RenderFrame(bb.Min, bb.Max, col, true, 0.0f);
ImGui::PopStyleColor();
}
}
else
@ -1456,6 +1469,13 @@ bool ImGuiFullscreen::EnumChoiceButtonImpl(const char* title, const char* summar
return changed;
}
void ImGuiFullscreen::DrawShadowedText(ImDrawList* dl, ImFont* font, const ImVec2& pos, u32 col, const char* text,
const char* text_end /*= nullptr*/, float wrap_width /*= 0.0f*/)
{
dl->AddText(font, font->FontSize, pos + LayoutScale(1.0f, 1.0f), IM_COL32(0, 0, 0, 100), text, text_end, wrap_width);
dl->AddText(font, font->FontSize, pos, col, text, text_end, wrap_width);
}
void ImGuiFullscreen::BeginNavBar(float x_padding /*= LAYOUT_MENU_BUTTON_X_PADDING*/,
float y_padding /*= LAYOUT_MENU_BUTTON_Y_PADDING*/)
{
@ -1711,6 +1731,7 @@ void ImGuiFullscreen::DrawFileSelector()
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
@ -1755,7 +1776,7 @@ void ImGuiFullscreen::DrawFileSelector()
}
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(3);
ImGui::PopFont();
if (selected)
@ -1821,6 +1842,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
@ -1893,7 +1915,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
}
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(3);
ImGui::PopFont();
if (choice >= 0)
@ -1938,6 +1960,7 @@ void ImGuiFullscreen::DrawInputDialog()
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
@ -1995,7 +2018,7 @@ void ImGuiFullscreen::DrawInputDialog()
CloseInputDialog();
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(3);
ImGui::PopFont();
}
@ -2087,6 +2110,7 @@ void ImGuiFullscreen::DrawMessageDialog()
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
@ -2120,7 +2144,7 @@ void ImGuiFullscreen::DrawMessageDialog()
}
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(3);
ImGui::PopStyleVar(4);
ImGui::PopFont();
if (!is_open || result.has_value())

View File

@ -106,6 +106,12 @@ static ALWAYS_INLINE ImVec4 MulAlpha(const ImVec4& v, float a)
return ImVec4(v.x, v.y, v.z, v.w * a);
}
static ALWAYS_INLINE std::string_view RemoveHash(const std::string_view& s)
{
const std::string_view::size_type pos = s.find('#');
return (pos != std::string_view::npos) ? s.substr(0, pos) : s;
}
/// Centers an image within the specified bounds, scaling up or down as needed.
ImRect CenterImage(const ImVec2& fit_size, const ImVec2& image_size);
ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size);
@ -131,6 +137,9 @@ void UploadAsyncTextures();
void BeginLayout();
void EndLayout();
void PushResetLayout();
void PopResetLayout();
void QueueResetFocus();
bool ResetFocusHere();
bool WantsToCloseMenu();
@ -181,7 +190,8 @@ bool MenuImageButton(const char* title, const char* summary, ImTextureID user_te
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool FloatingButton(const char* text, float x, float y, float width = -1.0f,
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, float anchor_x = 0.0f, float anchor_y = 0.0f,
bool enabled = true, ImFont* font = g_large_font, ImVec2* out_position = nullptr);
bool enabled = true, ImFont* font = g_large_font, ImVec2* out_position = nullptr,
bool repeat_button = false);
bool ToggleButton(const char* title, const char* summary, bool* v, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
@ -221,6 +231,9 @@ ALWAYS_INLINE static bool EnumChoiceButton(const char* title, const char* summar
}
}
void DrawShadowedText(ImDrawList* dl, ImFont* font, const ImVec2& pos, u32 col, const char* text,
const char* text_end = nullptr, float wrap_width = 0.0f);
void BeginNavBar(float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING);
void EndNavBar();
void NavTitle(const char* title, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);

View File

@ -240,7 +240,8 @@ void ImGuiManager::DrawPerformanceOverlay()
}
}
}
else if (g_settings.display_show_status_indicators && state == System::State::Paused)
else if (g_settings.display_show_status_indicators && state == System::State::Paused &&
!FullscreenUI::HasActiveWindow())
{
text.Assign(ICON_FA_PAUSE);
DRAW_LINE(standard_font, text, IM_COL32(255, 255, 255, 255));

View File

@ -1390,6 +1390,19 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
// Source Management
// ------------------------------------------------------------------------
bool InputManager::ReloadDevices()
{
bool changed = false;
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
changed |= s_input_sources[i]->ReloadDevices();
}
return changed;
}
void InputManager::CloseSources()
{
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)

View File

@ -220,6 +220,10 @@ void ReloadBindings(SettingsInterface& si, SettingsInterface& binding_si);
/// Re-parses the sources part of the config and initializes any backends.
void ReloadSources(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock);
/// Called when a device change is triggered by the system (DBT_DEVNODES_CHANGED on Windows).
/// Returns true if any device changes are detected.
bool ReloadDevices();
/// Shuts down any enabled input sources.
void CloseSources();

View File

@ -20,6 +20,7 @@ public:
virtual bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) = 0;
virtual void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) = 0;
virtual bool ReloadDevices() = 0;
virtual void Shutdown() = 0;
virtual void PollEvents() = 0;

View File

@ -130,6 +130,13 @@ void SDLInputSource::LoadSettings(SettingsInterface& si)
m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
}
bool SDLInputSource::ReloadDevices()
{
// We'll get a GC added/removed event here.
PollEvents();
return false;
}
void SDLInputSource::SetHints()
{
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, m_controller_enhanced_mode ? "1" : "0");

View File

@ -16,6 +16,7 @@ public:
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;

View File

@ -548,7 +548,14 @@ void VulkanHostDisplay::DestroyResources()
bool VulkanHostDisplay::CreateImGuiContext()
{
return ImGui_ImplVulkan_Init(m_swap_chain->GetClearRenderPass());
const VkRenderPass render_pass =
m_swap_chain ? m_swap_chain->GetClearRenderPass() :
g_vulkan_context->GetRenderPass(VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_UNDEFINED, VK_SAMPLE_COUNT_1_BIT,
VK_ATTACHMENT_LOAD_OP_CLEAR);
if (render_pass == VK_NULL_HANDLE)
return false;
return ImGui_ImplVulkan_Init(render_pass);
}
void VulkanHostDisplay::DestroyImGuiContext()

View File

@ -48,6 +48,11 @@ bool Win32RawInputSource::Initialize(SettingsInterface& si, std::unique_lock<std
void Win32RawInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) {}
bool Win32RawInputSource::ReloadDevices()
{
return false;
}
void Win32RawInputSource::Shutdown()
{
CloseDevices();

View File

@ -16,6 +16,7 @@ public:
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;

View File

@ -126,6 +126,35 @@ bool XInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
void XInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) {}
bool XInputSource::ReloadDevices()
{
bool changed = false;
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
XINPUT_STATE new_state;
DWORD result = m_xinput_get_state(i, &new_state);
if (result == ERROR_SUCCESS)
{
if (m_controllers[i].connected)
continue;
HandleControllerConnection(i);
changed = true;
}
else if (result == ERROR_DEVICE_NOT_CONNECTED)
{
if (!m_controllers[i].connected)
continue;
HandleControllerDisconnection(i);
changed = true;
}
}
return changed;
}
void XInputSource::Shutdown()
{
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
@ -152,6 +181,8 @@ void XInputSource::PollEvents()
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
const bool was_connected = m_controllers[i].connected;
if (!was_connected)
continue;
XINPUT_STATE new_state;
DWORD result = m_xinput_get_state(i, &new_state);

View File

@ -17,6 +17,7 @@ public:
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;