mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 22:05:38 +00:00
Misc: Backports from PCSX2 UI
This commit is contained in:
parent
8438506206
commit
72dfbaf6cc
|
@ -1922,14 +1922,22 @@ FileSystem::POSIXLock::POSIXLock(int fd)
|
|||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrintf("lockf() failed: %d", fd);
|
||||
Log_ErrorPrintf("lockf() failed: %d", errno);
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
FileSystem::POSIXLock::POSIXLock(std::FILE* fp)
|
||||
{
|
||||
POSIXLock(fileno(fp));
|
||||
m_fd = fileno(fp);
|
||||
if (m_fd >= 0)
|
||||
{
|
||||
if (lockf(m_fd, F_LOCK, 0) != 0)
|
||||
{
|
||||
Log_ErrorPrintf("lockf() failed: %d", errno);
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileSystem::POSIXLock::~POSIXLock()
|
||||
|
|
|
@ -18,6 +18,17 @@ static void* GetProcAddressCallback(const char* name)
|
|||
return ::GetProcAddress(GetModuleHandleA("opengl32.dll"), name);
|
||||
}
|
||||
|
||||
static bool ReloadWGL(HDC dc)
|
||||
{
|
||||
if (!gladLoadWGLLoader([](const char* name) -> void* { return wglGetProcAddress(name); }, dc))
|
||||
{
|
||||
Log_ErrorPrint("Loading GLAD WGL functions failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace GL {
|
||||
ContextWGL::ContextWGL(const WindowInfo& wi) : Context(wi) {}
|
||||
|
||||
|
@ -51,7 +62,7 @@ bool ContextWGL::Initialize(const Version* versions_to_try, size_t num_versions_
|
|||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrint("ContextWGL must always start with a valid surface.");
|
||||
if (!CreatePBuffer())
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -149,7 +160,7 @@ std::unique_ptr<Context> ContextWGL::CreateSharedContext(const WindowInfo& wi)
|
|||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrint("PBuffer not implemented");
|
||||
if (!context->CreatePBuffer())
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -305,11 +316,37 @@ bool ContextWGL::CreatePBuffer()
|
|||
|
||||
static constexpr const int pb_attribs[] = {0, 0};
|
||||
|
||||
HGLRC temp_rc = nullptr;
|
||||
ScopedGuard temp_rc_guard([&temp_rc, hdc]() {
|
||||
if (temp_rc)
|
||||
{
|
||||
wglMakeCurrent(hdc, nullptr);
|
||||
wglDeleteContext(temp_rc);
|
||||
}
|
||||
});
|
||||
|
||||
if (!GLAD_WGL_ARB_pbuffer)
|
||||
{
|
||||
// we're probably running completely surfaceless... need a temporary context.
|
||||
temp_rc = wglCreateContext(hdc);
|
||||
if (!temp_rc || !wglMakeCurrent(hdc, temp_rc))
|
||||
{
|
||||
Log_ErrorPrint("Failed to create temporary context to load WGL for pbuffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReloadWGL(hdc) || !GLAD_WGL_ARB_pbuffer)
|
||||
{
|
||||
Log_ErrorPrint("Missing WGL_ARB_pbuffer");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AssertMsg(m_pixel_format.has_value(), "Has pixel format for pbuffer");
|
||||
HPBUFFERARB pbuffer = wglCreatePbufferARB(hdc, m_pixel_format.value(), 1, 1, pb_attribs);
|
||||
if (!pbuffer)
|
||||
{
|
||||
Log_ErrorPrint("(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed");
|
||||
Log_ErrorPrintf("(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -318,7 +355,7 @@ bool ContextWGL::CreatePBuffer()
|
|||
m_dc = wglGetPbufferDCARB(pbuffer);
|
||||
if (!m_dc)
|
||||
{
|
||||
Log_ErrorPrint("(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed");
|
||||
Log_ErrorPrintf("(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -326,6 +363,7 @@ bool ContextWGL::CreatePBuffer()
|
|||
m_dummy_dc = hdc;
|
||||
m_pbuffer = pbuffer;
|
||||
|
||||
temp_rc_guard.Run();
|
||||
pbuffer_guard.Cancel();
|
||||
hdc_guard.Cancel();
|
||||
hwnd_guard.Cancel();
|
||||
|
@ -401,7 +439,7 @@ bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_contex
|
|||
if ((version.major_version >= 2 && !GLAD_WGL_EXT_create_context_es2_profile) ||
|
||||
(version.major_version < 2 && !GLAD_WGL_EXT_create_context_es_profile))
|
||||
{
|
||||
Log_ErrorPrint("WGL_EXT_create_context_es_profile not supported");
|
||||
Log_ErrorPrintf("WGL_EXT_create_context_es_profile not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -437,11 +475,8 @@ bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_contex
|
|||
}
|
||||
|
||||
// re-init glad-wgl
|
||||
if (make_current && !gladLoadWGLLoader([](const char* name) -> void* { return wglGetProcAddress(name); }, m_dc))
|
||||
{
|
||||
Log_ErrorPrint("Loading GLAD WGL functions failed");
|
||||
if (make_current && !ReloadWGL(m_dc))
|
||||
return false;
|
||||
}
|
||||
|
||||
wglDeleteContext(m_rc);
|
||||
}
|
||||
|
|
|
@ -12,16 +12,14 @@ class ScopedGuard final
|
|||
public:
|
||||
ALWAYS_INLINE ScopedGuard(T&& func) : m_func(std::forward<T>(func)) {}
|
||||
ALWAYS_INLINE ScopedGuard(ScopedGuard&& other) : m_func(std::move(other.m_func)) { other.m_func = nullptr; }
|
||||
ALWAYS_INLINE ~ScopedGuard() { Invoke(); }
|
||||
|
||||
ALWAYS_INLINE ~ScopedGuard() { Run(); }
|
||||
|
||||
ScopedGuard(const ScopedGuard&) = delete;
|
||||
void operator=(const ScopedGuard&) = delete;
|
||||
|
||||
/// Prevents the function from being invoked when we go out of scope.
|
||||
ALWAYS_INLINE void Cancel() { m_func.reset(); }
|
||||
|
||||
/// Explicitly fires the function.
|
||||
ALWAYS_INLINE void Invoke()
|
||||
/// Runs the destructor function now instead of when we go out of scope.
|
||||
ALWAYS_INLINE void Run()
|
||||
{
|
||||
if (!m_func.has_value())
|
||||
return;
|
||||
|
@ -30,6 +28,9 @@ public:
|
|||
m_func.reset();
|
||||
}
|
||||
|
||||
/// Prevents the function from being invoked when we go out of scope.
|
||||
ALWAYS_INLINE void Cancel() { m_func.reset(); }
|
||||
|
||||
private:
|
||||
std::optional<T> m_func;
|
||||
};
|
||||
|
|
|
@ -241,6 +241,12 @@ public:
|
|||
return m_pStringData->pBuffer;
|
||||
}
|
||||
|
||||
// returns a string view for this string
|
||||
std::string_view GetStringView() const
|
||||
{
|
||||
return IsEmpty() ? std::string_view() : std::string_view(GetCharArray(), GetLength());
|
||||
}
|
||||
|
||||
// creates a new string from the specified format
|
||||
static String FromFormat(const char* FormatString, ...) printflike(1, 2);
|
||||
|
||||
|
@ -250,10 +256,7 @@ public:
|
|||
// m_pStringData->pBuffer[i]; }
|
||||
operator const char*() const { return GetCharArray(); }
|
||||
operator char*() { return GetWriteableCharArray(); }
|
||||
operator std::string_view() const
|
||||
{
|
||||
return IsEmpty() ? std::string_view() : std::string_view(GetCharArray(), GetLength());
|
||||
}
|
||||
operator std::string_view() const { return GetStringView(); }
|
||||
|
||||
// Will use the string data provided.
|
||||
String& operator=(const String& copyString)
|
||||
|
|
|
@ -549,7 +549,7 @@ bool Vulkan::Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_
|
|||
Log_ErrorPrintf("Vulkan: Failed to find an acceptable graphics queue.");
|
||||
return false;
|
||||
}
|
||||
if (surface && m_present_queue_family_index == queue_family_count)
|
||||
if (surface != VK_NULL_HANDLE && m_present_queue_family_index == queue_family_count)
|
||||
{
|
||||
Log_ErrorPrintf("Vulkan: Failed to find an acceptable present queue.");
|
||||
return false;
|
||||
|
@ -583,7 +583,7 @@ bool Vulkan::Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_
|
|||
}};
|
||||
|
||||
device_info.queueCreateInfoCount = 1;
|
||||
if (m_graphics_queue_family_index != m_present_queue_family_index)
|
||||
if (surface != VK_NULL_HANDLE && m_graphics_queue_family_index != m_present_queue_family_index)
|
||||
{
|
||||
device_info.queueCreateInfoCount = 2;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class Context
|
|||
public:
|
||||
enum : u32
|
||||
{
|
||||
NUM_COMMAND_BUFFERS = 2
|
||||
NUM_COMMAND_BUFFERS = 3
|
||||
};
|
||||
|
||||
struct OptionalExtensions
|
||||
|
|
|
@ -1067,7 +1067,7 @@ bool GPU_HW_Vulkan::CompilePipelines()
|
|||
}
|
||||
}
|
||||
|
||||
batch_shader_guard.Invoke();
|
||||
batch_shader_guard.Run();
|
||||
|
||||
VkShaderModule fullscreen_quad_vertex_shader =
|
||||
g_vulkan_shader_cache->GetVertexShader(shadergen.GenerateScreenQuadVertexShader());
|
||||
|
|
|
@ -478,6 +478,11 @@ void NoGUIHost::PlatformWindowFocusLost()
|
|||
});
|
||||
}
|
||||
|
||||
void NoGUIHost::PlatformDevicesChanged()
|
||||
{
|
||||
Host::RunOnCPUThread([]() { InputManager::ReloadDevices(); });
|
||||
}
|
||||
|
||||
bool NoGUIHost::GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* height)
|
||||
{
|
||||
auto lock = Host::GetSettingsLock();
|
||||
|
@ -687,6 +692,11 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api)
|
|||
return false;
|
||||
}
|
||||
|
||||
// reload input sources, since it might use the window handle
|
||||
{
|
||||
auto lock = Host::GetSettingsLock();
|
||||
InputManager::ReloadSources(*Host::GetSettingsInterface(), lock);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -708,6 +718,9 @@ void NoGUIHost::ReleaseHostDisplay()
|
|||
if (!g_host_display)
|
||||
return;
|
||||
|
||||
// close input sources, since it might use the window handle
|
||||
InputManager::CloseSources();
|
||||
|
||||
CommonHost::ReleaseHostDisplayResources();
|
||||
ImGuiManager::Shutdown();
|
||||
g_host_display.reset();
|
||||
|
|
|
@ -30,6 +30,7 @@ void ProcessPlatformKeyEvent(s32 key, bool pressed);
|
|||
void ProcessPlatformTextEvent(const char* text);
|
||||
void PlatformWindowFocusGained();
|
||||
void PlatformWindowFocusLost();
|
||||
void PlatformDevicesChanged();
|
||||
bool GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* height);
|
||||
void SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height);
|
||||
} // namespace NoGUIHost
|
|
@ -9,6 +9,7 @@
|
|||
#include "nogui_host.h"
|
||||
#include "resource.h"
|
||||
#include "win32_key_names.h"
|
||||
#include <Dbt.h>
|
||||
#include <shellapi.h>
|
||||
#include <tchar.h>
|
||||
Log_SetChannel(Win32HostInterface);
|
||||
|
@ -122,6 +123,11 @@ bool Win32NoGUIPlatform::CreatePlatformWindow(std::string title)
|
|||
if (m_fullscreen.load(std::memory_order_acquire))
|
||||
SetFullscreen(true);
|
||||
|
||||
// We use these notifications to detect when a controller is connected or disconnected.
|
||||
DEV_BROADCAST_DEVICEINTERFACE_W filter = {sizeof(DEV_BROADCAST_DEVICEINTERFACE_W), DBT_DEVTYP_DEVICEINTERFACE};
|
||||
m_dev_notify_handle =
|
||||
RegisterDeviceNotificationW(hwnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -130,6 +136,12 @@ void Win32NoGUIPlatform::DestroyPlatformWindow()
|
|||
if (!m_hwnd)
|
||||
return;
|
||||
|
||||
if (m_dev_notify_handle)
|
||||
{
|
||||
UnregisterDeviceNotification(m_dev_notify_handle);
|
||||
m_dev_notify_handle = NULL;
|
||||
}
|
||||
|
||||
RECT rc;
|
||||
if (!m_fullscreen.load(std::memory_order_acquire) && GetWindowRect(m_hwnd, &rc))
|
||||
{
|
||||
|
@ -393,6 +405,13 @@ LRESULT CALLBACK Win32NoGUIPlatform::WndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
|||
}
|
||||
break;
|
||||
|
||||
case WM_DEVICECHANGE:
|
||||
{
|
||||
if (wParam == DBT_DEVNODES_CHANGED)
|
||||
NoGUIHost::PlatformDevicesChanged();
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_FUNC:
|
||||
{
|
||||
std::function<void()>* pfunc = reinterpret_cast<std::function<void()>*>(lParam);
|
||||
|
|
|
@ -59,4 +59,6 @@ private:
|
|||
std::atomic_bool m_fullscreen{false};
|
||||
|
||||
DWORD m_last_mouse_buttons = 0;
|
||||
|
||||
HDEVNOTIFY m_dev_notify_handle = NULL;
|
||||
};
|
|
@ -24,10 +24,6 @@
|
|||
#include "settingwidgetbinder.h"
|
||||
#include "util/cd_image.h"
|
||||
|
||||
#ifdef WITH_CHEEVOS
|
||||
#include "frontend-common/achievements.h"
|
||||
#endif
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QFileInfo>
|
||||
|
@ -43,6 +39,15 @@
|
|||
#include <QtWidgets/QStyleFactory>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef WITH_CHEEVOS
|
||||
#include "frontend-common/achievements.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/windows_headers.h"
|
||||
#include <Dbt.h>
|
||||
#endif
|
||||
|
||||
Log_SetChannel(MainWindow);
|
||||
|
||||
static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
|
||||
|
@ -101,6 +106,10 @@ MainWindow::~MainWindow()
|
|||
// we compare here, since recreate destroys the window later
|
||||
if (g_main_window == this)
|
||||
g_main_window = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
unregisterForDeviceNotifications();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::updateApplicationTheme()
|
||||
|
@ -129,6 +138,10 @@ void MainWindow::initialize()
|
|||
if (Achievements::IsUsingRAIntegration())
|
||||
Achievements::RAIntegration::MainWindowChanged((void*)winId());
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
registerForDeviceNotifications();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::reportError(const QString& title, const QString& message)
|
||||
|
@ -143,6 +156,48 @@ bool MainWindow::confirmMessage(const QString& title, const QString& message)
|
|||
return (QMessageBox::question(this, title, message) == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
void MainWindow::registerForDeviceNotifications()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// We use these notifications to detect when a controller is connected or disconnected.
|
||||
DEV_BROADCAST_DEVICEINTERFACE_W filter = {sizeof(DEV_BROADCAST_DEVICEINTERFACE_W), DBT_DEVTYP_DEVICEINTERFACE};
|
||||
m_device_notification_handle = RegisterDeviceNotificationW(
|
||||
(HANDLE)winId(), &filter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::unregisterForDeviceNotifications()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!m_device_notification_handle)
|
||||
return;
|
||||
|
||||
UnregisterDeviceNotification(static_cast<HDEVNOTIFY>(m_device_notification_handle));
|
||||
m_device_notification_handle = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result)
|
||||
{
|
||||
static constexpr const char win_type[] = "windows_generic_MSG";
|
||||
if (eventType == QByteArray(win_type, sizeof(win_type) - 1))
|
||||
{
|
||||
const MSG* msg = static_cast<const MSG*>(message);
|
||||
if (msg->message == WM_DEVICECHANGE && msg->wParam == DBT_DEVNODES_CHANGED)
|
||||
{
|
||||
g_emu_thread->reloadInputDevices();
|
||||
*result = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QMainWindow::nativeEvent(eventType, message, result);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool MainWindow::createDisplay(bool fullscreen, bool render_to_main)
|
||||
{
|
||||
Log_DevPrintf("createDisplay(%u, %u)", static_cast<u32>(fullscreen), static_cast<u32>(render_to_main));
|
||||
|
@ -650,6 +705,9 @@ void MainWindow::recreate()
|
|||
if (s_system_valid)
|
||||
requestShutdown(false, true, true);
|
||||
|
||||
// We need to close input sources, because e.g. DInput uses our window handle.
|
||||
g_emu_thread->closeInputSources();
|
||||
|
||||
close();
|
||||
g_main_window = nullptr;
|
||||
|
||||
|
@ -657,6 +715,9 @@ void MainWindow::recreate()
|
|||
new_main_window->initialize();
|
||||
new_main_window->show();
|
||||
deleteLater();
|
||||
|
||||
// Reload the sources we just closed.
|
||||
g_emu_thread->reloadInputSources();
|
||||
}
|
||||
|
||||
void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu)
|
||||
|
@ -1658,7 +1719,7 @@ void MainWindow::updateWindowState(bool force_visible)
|
|||
return;
|
||||
|
||||
const bool hide_window = !isRenderingToMain() && shouldHideMainWindow();
|
||||
const bool disable_resize = Host::GetBaseBoolSettingValue("Main", "DisableWindowResize", false);
|
||||
const bool disable_resize = Host::GetBoolSettingValue("Main", "DisableWindowResize", false);
|
||||
const bool has_window = s_system_valid || m_display_widget;
|
||||
|
||||
// Need to test both valid and display widget because of startup (vm invalid while window is created).
|
||||
|
@ -1730,8 +1791,8 @@ bool MainWindow::shouldHideMouseCursor() const
|
|||
|
||||
bool MainWindow::shouldHideMainWindow() const
|
||||
{
|
||||
return Host::GetBaseBoolSettingValue("Main", "HideMainWindowWhenRunning", false) || isRenderingFullscreen() ||
|
||||
QtHost::InNoGUIMode();
|
||||
return Host::GetBaseBoolSettingValue("Main", "HideMainWindowWhenRunning", false) ||
|
||||
(g_emu_thread->shouldRenderToMain() && isRenderingFullscreen()) || QtHost::InNoGUIMode();
|
||||
}
|
||||
|
||||
void MainWindow::switchToGameListView()
|
||||
|
@ -2412,7 +2473,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav
|
|||
// reshow the main window during display updates, because otherwise fullscreen transitions and renderer switches
|
||||
// would briefly show and then hide the main window. So instead, we do it on shutdown, here. Except if we're in
|
||||
// batch mode, when we're going to exit anyway.
|
||||
if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode())
|
||||
if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode() && !g_emu_thread->isRunningFullscreenUI())
|
||||
updateWindowState(true);
|
||||
|
||||
// Now we can actually shut down the VM.
|
||||
|
|
|
@ -169,6 +169,10 @@ protected:
|
|||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
|
||||
#ifdef _WIN32
|
||||
bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
static void setStyleFromSettings();
|
||||
static void setIconThemeFromSettings();
|
||||
|
@ -218,6 +222,9 @@ private:
|
|||
void setTheme(const QString& theme);
|
||||
void recreate();
|
||||
|
||||
void registerForDeviceNotifications();
|
||||
void unregisterForDeviceNotifications();
|
||||
|
||||
/// Fills menu with save state info and handlers.
|
||||
void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);
|
||||
|
||||
|
@ -271,6 +278,10 @@ private:
|
|||
bool m_is_closing = false;
|
||||
|
||||
GDBServer* m_gdb_server = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
void* m_device_notification_handle = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern MainWindow* g_main_window;
|
||||
|
|
|
@ -376,7 +376,7 @@ void QtHost::SetDefaultSettings(SettingsInterface& si, bool system, bool control
|
|||
|
||||
bool EmuThread::shouldRenderToMain() const
|
||||
{
|
||||
return !Host::GetBaseBoolSettingValue("Main", "RenderToSeparateWindow", false) && !QtHost::InNoGUIMode();
|
||||
return !Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) && !QtHost::InNoGUIMode();
|
||||
}
|
||||
|
||||
void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height)
|
||||
|
@ -895,6 +895,28 @@ void EmuThread::reloadInputBindings()
|
|||
InputManager::ReloadBindings(*si, *bindings_si);
|
||||
}
|
||||
|
||||
void EmuThread::reloadInputDevices()
|
||||
{
|
||||
if (!isOnThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, &EmuThread::reloadInputDevices, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
InputManager::ReloadDevices();
|
||||
}
|
||||
|
||||
void EmuThread::closeInputSources()
|
||||
{
|
||||
if (!isOnThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, &EmuThread::reloadInputDevices, Qt::BlockingQueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
InputManager::CloseSources();
|
||||
}
|
||||
|
||||
void EmuThread::enumerateInputDevices()
|
||||
{
|
||||
if (!isOnThread())
|
||||
|
|
|
@ -148,6 +148,8 @@ public Q_SLOTS:
|
|||
void updateEmuFolders();
|
||||
void reloadInputSources();
|
||||
void reloadInputBindings();
|
||||
void reloadInputDevices();
|
||||
void closeInputSources();
|
||||
void enumerateInputDevices();
|
||||
void enumerateVibrationMotors();
|
||||
void startFullscreenUI();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
if (!BeginFullscreenColumns())
|
||||
{
|
||||
EndFullscreenColumns();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
{
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopFont();
|
||||
if (valid)
|
||||
ImGui::EndPopup();
|
||||
|
||||
ImGui::PopStyleVar(5);
|
||||
if (!is_open)
|
||||
CloseSaveStateSelector();
|
||||
return;
|
||||
}
|
||||
|
||||
ImVec2 heading_size = ImVec2(
|
||||
io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f + 2.0f));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ModAlpha(UIPrimaryColor, 0.9f));
|
||||
|
||||
if (ImGui::BeginChild("state_titlebar", heading_size, false, ImGuiWindowFlags_NoNav))
|
||||
{
|
||||
BeginNavBar();
|
||||
if (NavButton(ICON_FA_BACKWARD, true, true))
|
||||
CloseSaveStateSelector();
|
||||
|
||||
NavTitle(is_loading ? "Load State" : "Save State");
|
||||
EndNavBar();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ModAlpha(UIBackgroundColor, 0.9f));
|
||||
ImGui::SetCursorPos(ImVec2(0.0f, heading_size.y));
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
for (const SaveStateListEntry& entry : s_save_state_selector_slots)
|
||||
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++)
|
||||
{
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
bool pressed = MenuButtonFrame(entry.title.c_str(), true, button_height, &visible, &hovered, &bb.Min, &bb.Max);
|
||||
if (!visible)
|
||||
if (i == 0)
|
||||
ResetFocusHere();
|
||||
|
||||
const SaveStateListEntry& entry = s_save_state_selector_slots[i];
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
if (window->SkipItems)
|
||||
continue;
|
||||
|
||||
ImVec2 pos(bb.Min);
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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));
|
||||
|
||||
ImRect text_bb(pos, ImVec2(bb.Max.x, pos.y + g_large_font->FontSize));
|
||||
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(text_bb.Min, text_bb.Max, entry.title.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f),
|
||||
&text_bb);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, entry.title.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f),
|
||||
&title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushFont(g_medium_font);
|
||||
|
||||
if (!entry.summary.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.summary.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f),
|
||||
&text_bb);
|
||||
}
|
||||
|
||||
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 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();
|
||||
|
||||
ImGui::GetWindowDrawList()->AddImage(
|
||||
static_cast<ImTextureID>(entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get()),
|
||||
image_bb.Min, image_bb.Max);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (fullscreen)
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
if (!is_open)
|
||||
{
|
||||
EndFullscreenColumnWindow();
|
||||
EndFullscreenColumns();
|
||||
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
|
||||
{
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(start_x + static_cast<float>(grid_x) * (item_width + item_spacing));
|
||||
}
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
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)
|
|||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
|
||||
|
|
|
@ -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,13 +959,15 @@ 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 (!long_format)
|
||||
{
|
||||
if (hours >= 100)
|
||||
ret.Fmt(Host::TranslateString("GameList", "{}h {}m").GetCharArray(), hours, minutes);
|
||||
else if (hours > 0)
|
||||
|
@ -960,6 +978,14 @@ TinyString GameList::FormatTimespan(std::time_t timespan)
|
|||
ret.Fmt(Host::TranslateString("GameList", "{}s").GetCharArray(), seconds);
|
||||
else
|
||||
ret = Host::TranslateString("GameList", "None");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hours > 0)
|
||||
ret = fmt::format(Host::TranslateString("GameList", "{} hours").GetCharArray(), hours);
|
||||
else
|
||||
ret = fmt::format(Host::TranslateString("GameList", "{} minutes").GetCharArray(), minutes);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue