mirror of
https://github.com/stenzek/duckstation.git
synced 2024-11-23 05:49:43 +00:00
SaveStateSelectorUI: Decouple current slot from list
This commit is contained in:
parent
cffb383c10
commit
73968ac526
BIN
data/resources/no-save.png
Normal file
BIN
data/resources/no-save.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
@ -321,7 +321,7 @@ void Host::ReleaseGPUDevice()
|
|||||||
if (!g_gpu_device)
|
if (!g_gpu_device)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SaveStateSelectorUI::DestroyTextures();
|
ImGuiManager::DestroyOverlayTextures();
|
||||||
FullscreenUI::Shutdown();
|
FullscreenUI::Shutdown();
|
||||||
ImGuiManager::Shutdown();
|
ImGuiManager::Shutdown();
|
||||||
|
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
#include "achievements.h"
|
#include "achievements.h"
|
||||||
#include "cpu_code_cache.h"
|
#include "cpu_code_cache.h"
|
||||||
#include "cpu_core.h"
|
#include "cpu_core.h"
|
||||||
|
#include "cpu_pgxp.h"
|
||||||
#include "fullscreen_ui.h"
|
#include "fullscreen_ui.h"
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "imgui_overlays.h"
|
#include "imgui_overlays.h"
|
||||||
#include "cpu_pgxp.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "spu.h"
|
#include "spu.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
@ -68,7 +68,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot)
|
|||||||
if (!global && System::GetGameSerial().empty())
|
if (!global && System::GetGameSerial().empty())
|
||||||
{
|
{
|
||||||
Host::AddKeyedOSDMessage("LoadState", TRANSLATE_NOOP("OSDMessage", "Cannot load state for game without serial."),
|
Host::AddKeyedOSDMessage("LoadState", TRANSLATE_NOOP("OSDMessage", "Cannot load state for game without serial."),
|
||||||
5.0f);
|
Host::OSD_ERROR_DURATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,8 @@ static void HotkeyLoadStateSlot(bool global, s32 slot)
|
|||||||
if (!FileSystem::FileExists(path.c_str()))
|
if (!FileSystem::FileExists(path.c_str()))
|
||||||
{
|
{
|
||||||
Host::AddKeyedOSDMessage("LoadState",
|
Host::AddKeyedOSDMessage("LoadState",
|
||||||
fmt::format(TRANSLATE_NOOP("OSDMessage", "No save state found in slot {}."), slot), 5.0f);
|
fmt::format(TRANSLATE_NOOP("OSDMessage", "No save state found in slot {}."), slot),
|
||||||
|
Host::OSD_INFO_DURATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ static void HotkeySaveStateSlot(bool global, s32 slot)
|
|||||||
if (!global && System::GetGameSerial().empty())
|
if (!global && System::GetGameSerial().empty())
|
||||||
{
|
{
|
||||||
Host::AddKeyedOSDMessage("LoadState", TRANSLATE_NOOP("OSDMessage", "Cannot save state for game without serial."),
|
Host::AddKeyedOSDMessage("LoadState", TRANSLATE_NOOP("OSDMessage", "Cannot save state for game without serial."),
|
||||||
5.0f);
|
Host::OSD_ERROR_DURATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,12 +519,20 @@ DEFINE_HOTKEY("SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
|||||||
DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) {
|
||||||
if (!pressed)
|
if (!pressed)
|
||||||
Host::RunOnCPUThread(SaveStateSelectorUI::SelectPreviousSlot);
|
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); });
|
||||||
})
|
})
|
||||||
DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), [](s32 pressed) {
|
TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), [](s32 pressed) {
|
||||||
if (!pressed)
|
if (!pressed)
|
||||||
Host::RunOnCPUThread(SaveStateSelectorUI::SelectNextSlot);
|
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectNextSlot(true); });
|
||||||
|
})
|
||||||
|
DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
|
||||||
|
TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), [](s32 pressed) {
|
||||||
|
if (!pressed && System::IsValid())
|
||||||
|
{
|
||||||
|
SaveStateSelectorUI::SaveCurrentSlot();
|
||||||
|
SaveStateSelectorUI::SelectNextSlot(false);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
DEFINE_HOTKEY("UndoLoadState", TRANSLATE_NOOP("Hotkeys", "Save States"), TRANSLATE_NOOP("Hotkeys", "Undo Load State"),
|
DEFINE_HOTKEY("UndoLoadState", TRANSLATE_NOOP("Hotkeys", "Save States"), TRANSLATE_NOOP("Hotkeys", "Undo Load State"),
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/path.h"
|
#include "common/path.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
#include "common/thirdparty/SmallVector.h"
|
||||||
#include "common/timer.h"
|
#include "common/timer.h"
|
||||||
|
|
||||||
#include "IconsFontAwesome5.h"
|
#include "IconsFontAwesome5.h"
|
||||||
@ -328,14 +329,14 @@ void ImGuiManager::DrawPerformanceOverlay()
|
|||||||
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
||||||
const bool pal = g_gpu->IsInPALMode();
|
const bool pal = g_gpu->IsInPALMode();
|
||||||
text.format("{}x{} {} {}", effective_width, effective_height, pal ? "PAL" : "NTSC",
|
text.format("{}x{} {} {}", effective_width, effective_height, pal ? "PAL" : "NTSC",
|
||||||
interlaced ? "Interlaced" : "Progressive");
|
interlaced ? "Interlaced" : "Progressive");
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_settings.display_show_cpu)
|
if (g_settings.display_show_cpu)
|
||||||
{
|
{
|
||||||
text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", System::GetMinimumFrameTime(), System::GetAverageFrameTime(),
|
text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", System::GetMinimumFrameTime(), System::GetAverageFrameTime(),
|
||||||
System::GetMaximumFrameTime());
|
System::GetMaximumFrameTime());
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||||
|
|
||||||
if (g_settings.cpu_overclock_active ||
|
if (g_settings.cpu_overclock_active ||
|
||||||
@ -496,8 +497,8 @@ void ImGuiManager::DrawEnhancementsOverlay()
|
|||||||
{
|
{
|
||||||
LargeString text;
|
LargeString text;
|
||||||
text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()),
|
text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()),
|
||||||
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()),
|
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()),
|
||||||
g_gpu->IsHardwareRenderer() ? "HW" : "SW");
|
g_gpu->IsHardwareRenderer() ? "HW" : "SW");
|
||||||
|
|
||||||
if (g_settings.rewind_enable)
|
if (g_settings.rewind_enable)
|
||||||
text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
|
text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
|
||||||
@ -655,29 +656,38 @@ namespace SaveStateSelectorUI {
|
|||||||
namespace {
|
namespace {
|
||||||
struct ListEntry
|
struct ListEntry
|
||||||
{
|
{
|
||||||
std::string path;
|
std::string summary;
|
||||||
std::string serial;
|
std::string game_details; // only in global slots
|
||||||
std::string title;
|
std::string filename;
|
||||||
std::string formatted_timestamp;
|
|
||||||
std::unique_ptr<GPUTexture> preview_texture;
|
std::unique_ptr<GPUTexture> preview_texture;
|
||||||
s32 slot;
|
s32 slot;
|
||||||
bool global;
|
bool global;
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
static void InitializePlaceholderListEntry(ListEntry* li, std::string path, s32 slot, bool global);
|
static void InitializePlaceholderListEntry(ListEntry* li, const std::string& path, s32 slot, bool global);
|
||||||
static void InitializeListEntry(ListEntry* li, ExtendedSaveStateInfo* ssi, std::string path, s32 slot, bool global);
|
static void InitializeListEntry(ListEntry* li, ExtendedSaveStateInfo* ssi, const std::string& path, s32 slot,
|
||||||
|
bool global);
|
||||||
|
|
||||||
|
static void DestroyTextures();
|
||||||
static void RefreshHotkeyLegend();
|
static void RefreshHotkeyLegend();
|
||||||
static void Draw();
|
static void Draw();
|
||||||
|
static void ShowSlotOSDMessage();
|
||||||
|
static std::string GetCurrentSlotPath();
|
||||||
|
|
||||||
|
static constexpr const char* DATE_TIME_FORMAT =
|
||||||
|
TRANSLATE_NOOP("SaveStateSelectorUI", "Saved at {0:%H:%M} on {0:%a} {0:%Y/%m/%d}.");
|
||||||
|
|
||||||
|
static std::shared_ptr<GPUTexture> s_placeholder_texture;
|
||||||
|
|
||||||
static std::string s_load_legend;
|
static std::string s_load_legend;
|
||||||
static std::string s_save_legend;
|
static std::string s_save_legend;
|
||||||
static std::string s_prev_legend;
|
static std::string s_prev_legend;
|
||||||
static std::string s_next_legend;
|
static std::string s_next_legend;
|
||||||
|
|
||||||
static std::vector<ListEntry> s_slots;
|
static llvm::SmallVector<ListEntry, System::PER_GAME_SAVE_STATE_SLOTS + System::GLOBAL_SAVE_STATE_SLOTS> s_slots;
|
||||||
static u32 s_current_selection = 0;
|
static s32 s_current_slot = 0;
|
||||||
|
static bool s_current_slot_global = false;
|
||||||
|
|
||||||
static float s_open_time = 0.0f;
|
static float s_open_time = 0.0f;
|
||||||
static float s_close_time = 0.0f;
|
static float s_close_time = 0.0f;
|
||||||
@ -695,31 +705,34 @@ bool SaveStateSelectorUI::IsOpen()
|
|||||||
|
|
||||||
void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
|
void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
|
||||||
{
|
{
|
||||||
|
const std::string& serial = System::GetGameSerial();
|
||||||
|
|
||||||
s_open_time = 0.0f;
|
s_open_time = 0.0f;
|
||||||
s_close_time = open_time;
|
s_close_time = open_time;
|
||||||
|
|
||||||
if (s_open)
|
if (s_open)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!s_placeholder_texture)
|
||||||
|
s_placeholder_texture = ImGuiFullscreen::LoadTexture("no-save.png");
|
||||||
|
|
||||||
s_scroll_animated.Reset(0.0f);
|
s_scroll_animated.Reset(0.0f);
|
||||||
s_background_animated.Reset(0.0f);
|
s_background_animated.Reset(0.0f);
|
||||||
s_open = true;
|
s_open = true;
|
||||||
RefreshList();
|
RefreshList(serial);
|
||||||
RefreshHotkeyLegend();
|
RefreshHotkeyLegend();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::Close(bool reset_slot)
|
void SaveStateSelectorUI::Close()
|
||||||
{
|
{
|
||||||
s_open = false;
|
s_open = false;
|
||||||
s_load_legend = {};
|
s_load_legend = {};
|
||||||
s_save_legend = {};
|
s_save_legend = {};
|
||||||
s_prev_legend = {};
|
s_prev_legend = {};
|
||||||
s_next_legend = {};
|
s_next_legend = {};
|
||||||
if (reset_slot)
|
|
||||||
s_current_selection = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::RefreshList()
|
void SaveStateSelectorUI::RefreshList(const std::string& serial)
|
||||||
{
|
{
|
||||||
for (ListEntry& entry : s_slots)
|
for (ListEntry& entry : s_slots)
|
||||||
{
|
{
|
||||||
@ -731,11 +744,11 @@ void SaveStateSelectorUI::RefreshList()
|
|||||||
if (System::IsShutdown())
|
if (System::IsShutdown())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!System::GetGameSerial().empty())
|
if (!serial.empty())
|
||||||
{
|
{
|
||||||
for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++)
|
for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++)
|
||||||
{
|
{
|
||||||
std::string path(System::GetGameSaveStateFileName(System::GetGameSerial(), i));
|
std::string path(System::GetGameSaveStateFileName(serial, i));
|
||||||
std::optional<ExtendedSaveStateInfo> ssi = System::GetExtendedSaveStateInfo(path.c_str());
|
std::optional<ExtendedSaveStateInfo> ssi = System::GetExtendedSaveStateInfo(path.c_str());
|
||||||
|
|
||||||
ListEntry li;
|
ListEntry li;
|
||||||
@ -761,9 +774,26 @@ void SaveStateSelectorUI::RefreshList()
|
|||||||
|
|
||||||
s_slots.push_back(std::move(li));
|
s_slots.push_back(std::move(li));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (s_slots.empty() || s_current_selection >= s_slots.size())
|
void SaveStateSelectorUI::Clear()
|
||||||
s_current_selection = 0;
|
{
|
||||||
|
// called on CPU thread at shutdown, textures should already be deleted, unless running
|
||||||
|
// big picture UI, in which case we have to delete them here...
|
||||||
|
ClearList();
|
||||||
|
|
||||||
|
s_current_slot = 0;
|
||||||
|
s_current_slot_global = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStateSelectorUI::ClearList()
|
||||||
|
{
|
||||||
|
for (ListEntry& li : s_slots)
|
||||||
|
{
|
||||||
|
if (li.preview_texture)
|
||||||
|
g_gpu_device->RecycleTexture(std::move(li.preview_texture));
|
||||||
|
}
|
||||||
|
s_slots.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::DestroyTextures()
|
void SaveStateSelectorUI::DestroyTextures()
|
||||||
@ -775,6 +805,8 @@ void SaveStateSelectorUI::DestroyTextures()
|
|||||||
if (entry.preview_texture)
|
if (entry.preview_texture)
|
||||||
g_gpu_device->RecycleTexture(std::move(entry.preview_texture));
|
g_gpu_device->RecycleTexture(std::move(entry.preview_texture));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s_placeholder_texture.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::RefreshHotkeyLegend()
|
void SaveStateSelectorUI::RefreshHotkeyLegend()
|
||||||
@ -794,34 +826,69 @@ void SaveStateSelectorUI::RefreshHotkeyLegend()
|
|||||||
TRANSLATE_STR("SaveStateSelectorUI", "Select Next"));
|
TRANSLATE_STR("SaveStateSelectorUI", "Select Next"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::SelectNextSlot()
|
void SaveStateSelectorUI::SelectNextSlot(bool open_selector)
|
||||||
{
|
{
|
||||||
if (!s_open)
|
const s32 total_slots = s_current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
||||||
Open();
|
s_current_slot++;
|
||||||
|
if (s_current_slot >= total_slots)
|
||||||
|
{
|
||||||
|
s_current_slot -= total_slots;
|
||||||
|
s_current_slot_global = !s_current_slot_global;
|
||||||
|
if (System::GetGameSerial().empty() && !s_current_slot_global)
|
||||||
|
{
|
||||||
|
s_current_slot_global = false;
|
||||||
|
s_current_slot = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s_open_time = 0.0f;
|
if (open_selector)
|
||||||
s_current_selection = (s_current_selection == static_cast<u32>(s_slots.size() - 1)) ? 0 : (s_current_selection + 1);
|
{
|
||||||
|
if (!s_open)
|
||||||
|
Open();
|
||||||
|
|
||||||
|
s_open_time = 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowSlotOSDMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::SelectPreviousSlot()
|
void SaveStateSelectorUI::SelectPreviousSlot(bool open_selector)
|
||||||
{
|
{
|
||||||
if (!s_open)
|
s_current_slot--;
|
||||||
Open();
|
if (s_current_slot < 0)
|
||||||
|
{
|
||||||
|
s_current_slot_global = !s_current_slot_global;
|
||||||
|
s_current_slot += s_current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
||||||
|
if (System::GetGameSerial().empty() && !s_current_slot_global)
|
||||||
|
{
|
||||||
|
s_current_slot_global = false;
|
||||||
|
s_current_slot = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s_open_time = 0.0f;
|
if (open_selector)
|
||||||
s_current_selection =
|
{
|
||||||
(s_current_selection == 0) ? (static_cast<u32>(s_slots.size()) - 1u) : (s_current_selection - 1);
|
if (!s_open)
|
||||||
|
Open();
|
||||||
|
|
||||||
|
s_open_time = 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowSlotOSDMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, ExtendedSaveStateInfo* ssi, std::string path, s32 slot,
|
void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, ExtendedSaveStateInfo* ssi, const std::string& path,
|
||||||
bool global)
|
s32 slot, bool global)
|
||||||
{
|
{
|
||||||
li->title = std::move(ssi->title);
|
if (global)
|
||||||
li->serial = std::move(ssi->serial);
|
li->game_details = fmt::format(TRANSLATE_FS("SaveStateSelectorUI", "{} ({})"), ssi->title, ssi->serial);
|
||||||
li->path = std::move(path);
|
|
||||||
li->formatted_timestamp =
|
li->summary = fmt::format(TRANSLATE_FS("SaveStateSelectorUI", DATE_TIME_FORMAT), fmt::localtime(ssi->timestamp));
|
||||||
fmt::format(TRANSLATE_FS("SaveStateSelectorUI", "Saved at {0:%H:%M:%S} on {0:%a} {0:%Y/%m/%d}."),
|
li->filename = Path::GetFileName(path);
|
||||||
fmt::localtime(ssi->timestamp));
|
|
||||||
li->slot = slot;
|
li->slot = slot;
|
||||||
li->global = global;
|
li->global = global;
|
||||||
|
|
||||||
@ -835,38 +902,17 @@ void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, ExtendedSaveStateIn
|
|||||||
li->preview_texture = g_gpu_device->FetchTexture(
|
li->preview_texture = g_gpu_device->FetchTexture(
|
||||||
ssi->screenshot_width, ssi->screenshot_height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
|
ssi->screenshot_width, ssi->screenshot_height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
|
||||||
ssi->screenshot_data.data(), sizeof(u32) * ssi->screenshot_width);
|
ssi->screenshot_data.data(), sizeof(u32) * ssi->screenshot_width);
|
||||||
|
if (!li->preview_texture)
|
||||||
|
Log_ErrorPrintf("Failed to upload save state image to GPU");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
li->preview_texture = g_gpu_device->FetchTexture(
|
|
||||||
Resources::PLACEHOLDER_ICON_WIDTH, Resources::PLACEHOLDER_ICON_HEIGHT, 1, 1, 1, GPUTexture::Type::Texture,
|
|
||||||
GPUTexture::Format::RGBA8, Resources::PLACEHOLDER_ICON_DATA, sizeof(u32) * Resources::PLACEHOLDER_ICON_WIDTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!li->preview_texture)
|
|
||||||
Log_ErrorPrintf("Failed to upload save state image to GPU");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::InitializePlaceholderListEntry(ListEntry* li, std::string path, s32 slot, bool global)
|
void SaveStateSelectorUI::InitializePlaceholderListEntry(ListEntry* li, const std::string& path, s32 slot, bool global)
|
||||||
{
|
{
|
||||||
li->title = TRANSLATE_STR("SaveStateSelectorUI", "No Save State");
|
li->summary = TRANSLATE_STR("SaveStateSelectorUI", "No save present in this slot.");
|
||||||
std::string().swap(li->serial);
|
|
||||||
li->path = std::move(path);
|
|
||||||
std::string().swap(li->formatted_timestamp);
|
|
||||||
li->slot = slot;
|
li->slot = slot;
|
||||||
li->global = global;
|
li->global = global;
|
||||||
|
|
||||||
if (g_gpu_device)
|
|
||||||
{
|
|
||||||
g_gpu_device->RecycleTexture(std::move(li->preview_texture));
|
|
||||||
|
|
||||||
li->preview_texture = g_gpu_device->FetchTexture(
|
|
||||||
Resources::PLACEHOLDER_ICON_WIDTH, Resources::PLACEHOLDER_ICON_HEIGHT, 1, 1, 1, GPUTexture::Type::Texture,
|
|
||||||
GPUTexture::Format::RGBA8, Resources::PLACEHOLDER_ICON_DATA, sizeof(u32) * Resources::PLACEHOLDER_ICON_WIDTH);
|
|
||||||
if (!li->preview_texture)
|
|
||||||
Log_ErrorPrintf("Failed to upload save state image to GPU");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::Draw()
|
void SaveStateSelectorUI::Draw()
|
||||||
@ -899,6 +945,8 @@ void SaveStateSelectorUI::Draw()
|
|||||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
|
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
|
||||||
ImGuiWindowFlags_NoBackground);
|
ImGuiWindowFlags_NoBackground);
|
||||||
{
|
{
|
||||||
|
const s32 current_slot = GetCurrentSlot();
|
||||||
|
const bool current_slot_global = IsCurrentSlotGlobal();
|
||||||
const ImVec2 image_size = ImVec2(128.0f * scale, (128.0f / (4.0f / 3.0f)) * scale);
|
const ImVec2 image_size = ImVec2(128.0f * scale, (128.0f / (4.0f / 3.0f)) * scale);
|
||||||
const float item_width = std::floor(width - (padding_and_rounding * 2.0f) - ImGui::GetStyle().ScrollbarSize);
|
const float item_width = std::floor(width - (padding_and_rounding * 2.0f) - ImGui::GetStyle().ScrollbarSize);
|
||||||
const float item_height = std::floor(image_size.y + padding * 2.0f);
|
const float item_height = std::floor(image_size.y + padding * 2.0f);
|
||||||
@ -909,7 +957,7 @@ void SaveStateSelectorUI::Draw()
|
|||||||
const ListEntry& entry = s_slots[i];
|
const ListEntry& entry = s_slots[i];
|
||||||
const float y_start = item_height * static_cast<float>(i);
|
const float y_start = item_height * static_cast<float>(i);
|
||||||
|
|
||||||
if (i == s_current_selection)
|
if (entry.slot == current_slot && entry.global == current_slot_global)
|
||||||
{
|
{
|
||||||
ImGui::SetCursorPosY(y_start);
|
ImGui::SetCursorPosY(y_start);
|
||||||
|
|
||||||
@ -946,11 +994,12 @@ void SaveStateSelectorUI::Draw()
|
|||||||
ImColor(0.22f, 0.30f, 0.34f, 0.9f), padding_and_rounding);
|
ImColor(0.22f, 0.30f, 0.34f, 0.9f), padding_and_rounding);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.preview_texture)
|
if (GPUTexture* preview_texture =
|
||||||
|
entry.preview_texture ? entry.preview_texture.get() : s_placeholder_texture.get())
|
||||||
{
|
{
|
||||||
ImGui::SetCursorPosY(y_start + padding);
|
ImGui::SetCursorPosY(y_start + padding);
|
||||||
ImGui::SetCursorPosX(padding);
|
ImGui::SetCursorPosX(padding);
|
||||||
ImGui::Image(entry.preview_texture.get(), image_size);
|
ImGui::Image(preview_texture, image_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SetCursorPosY(y_start + padding);
|
ImGui::SetCursorPosY(y_start + padding);
|
||||||
@ -958,24 +1007,15 @@ void SaveStateSelectorUI::Draw()
|
|||||||
ImGui::Indent(text_indent);
|
ImGui::Indent(text_indent);
|
||||||
|
|
||||||
ImGui::TextUnformatted(TinyString::from_format(entry.global ?
|
ImGui::TextUnformatted(TinyString::from_format(entry.global ?
|
||||||
TRANSLATE_FS("SaveStateSelectorUI", "Global Slot {}") :
|
TRANSLATE_FS("SaveStateSelectorUI", "Global Slot {}") :
|
||||||
TRANSLATE_FS("SaveStateSelectorUI", "Game Slot {}"),
|
TRANSLATE_FS("SaveStateSelectorUI", "Game Slot {}"),
|
||||||
entry.slot)
|
entry.slot)
|
||||||
.c_str());
|
.c_str());
|
||||||
if (!entry.formatted_timestamp.empty())
|
if (entry.global)
|
||||||
{
|
ImGui::TextUnformatted(entry.game_details.c_str(), entry.game_details.c_str() + entry.game_details.length());
|
||||||
if (entry.global)
|
ImGui::TextUnformatted(entry.summary.c_str(), entry.summary.c_str() + entry.summary.length());
|
||||||
ImGui::TextUnformatted(entry.title.c_str());
|
|
||||||
ImGui::TextUnformatted(entry.formatted_timestamp.c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui::TextUnformatted(TRANSLATE("SaveStateSelectorUI", "No save present in this slot."));
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string_view filename = Path::GetFileName(entry.path);
|
|
||||||
ImGui::PushFont(ImGuiManager::GetFixedFont());
|
ImGui::PushFont(ImGuiManager::GetFixedFont());
|
||||||
ImGui::TextUnformatted(filename.data(), filename.data() + filename.length());
|
ImGui::TextUnformatted(entry.filename.data(), entry.filename.data() + entry.filename.length());
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
|
|
||||||
ImGui::Unindent(text_indent);
|
ImGui::Unindent(text_indent);
|
||||||
@ -1016,24 +1056,80 @@ void SaveStateSelectorUI::Draw()
|
|||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s32 SaveStateSelectorUI::GetCurrentSlot()
|
||||||
|
{
|
||||||
|
return s_current_slot + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveStateSelectorUI::IsCurrentSlotGlobal()
|
||||||
|
{
|
||||||
|
return s_current_slot_global;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SaveStateSelectorUI::GetCurrentSlotPath()
|
||||||
|
{
|
||||||
|
std::string filename;
|
||||||
|
if (!s_current_slot_global)
|
||||||
|
{
|
||||||
|
if (const std::string& serial = System::GetGameSerial(); !serial.empty())
|
||||||
|
filename = System::GetGameSaveStateFileName(serial, s_current_slot + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filename = System::GetGlobalSaveStateFileName(s_current_slot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::LoadCurrentSlot()
|
void SaveStateSelectorUI::LoadCurrentSlot()
|
||||||
{
|
{
|
||||||
if (s_slots.empty() || s_current_selection >= s_slots.size() || s_slots[s_current_selection].path.empty())
|
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
||||||
return;
|
{
|
||||||
|
if (FileSystem::FileExists(path.c_str()))
|
||||||
|
{
|
||||||
|
System::LoadState(path.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Host::AddIconOSDMessage(
|
||||||
|
"LoadState", ICON_FA_SD_CARD,
|
||||||
|
IsCurrentSlotGlobal() ?
|
||||||
|
fmt::format(TRANSLATE_FS("SaveStateSelectorUI", "No save state found in Global Slot {}."), GetCurrentSlot()) :
|
||||||
|
fmt::format(TRANSLATE_FS("SaveStateSelectorUI", "No save state found in Slot {}."), GetCurrentSlot()),
|
||||||
|
Host::OSD_INFO_DURATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
System::LoadState(s_slots[s_current_selection].path.c_str());
|
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveStateSelectorUI::SaveCurrentSlot()
|
void SaveStateSelectorUI::SaveCurrentSlot()
|
||||||
{
|
{
|
||||||
if (s_slots.empty() || s_current_selection >= s_slots.size() || s_slots[s_current_selection].path.empty())
|
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
||||||
return;
|
System::SaveState(path.c_str(), g_settings.create_save_state_backups);
|
||||||
|
|
||||||
System::SaveState(s_slots[s_current_selection].path.c_str(), g_settings.create_save_state_backups);
|
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SaveStateSelectorUI::ShowSlotOSDMessage()
|
||||||
|
{
|
||||||
|
const std::string path = GetCurrentSlotPath();
|
||||||
|
FILESYSTEM_STAT_DATA sd;
|
||||||
|
std::string date;
|
||||||
|
if (!path.empty() && FileSystem::StatFile(path.c_str(), &sd))
|
||||||
|
date = fmt::format(TRANSLATE_FS("SaveStateSelectorUI", DATE_TIME_FORMAT), fmt::localtime(sd.ModificationTime));
|
||||||
|
else
|
||||||
|
date = TRANSLATE_STR("SaveStateSelectorUI", "no save yet");
|
||||||
|
|
||||||
|
Host::AddIconOSDMessage(
|
||||||
|
"ShowSlotOSDMessage", ICON_FA_SEARCH,
|
||||||
|
IsCurrentSlotGlobal() ?
|
||||||
|
fmt::format(TRANSLATE_FS("SaveStateSelectorUI", "Global Save Slot {0} selected ({1})."), GetCurrentSlot(), date) :
|
||||||
|
fmt::format(TRANSLATE_FS("SaveStateSelectorUI", "Save Slot {0} selected ({1})."), GetCurrentSlot(), date),
|
||||||
|
Host::OSD_QUICK_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
void ImGuiManager::RenderOverlayWindows()
|
void ImGuiManager::RenderOverlayWindows()
|
||||||
{
|
{
|
||||||
const System::State state = System::GetState();
|
const System::State state = System::GetState();
|
||||||
@ -1043,3 +1139,8 @@ void ImGuiManager::RenderOverlayWindows()
|
|||||||
SaveStateSelectorUI::Draw();
|
SaveStateSelectorUI::Draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImGuiManager::DestroyOverlayTextures()
|
||||||
|
{
|
||||||
|
SaveStateSelectorUI::DestroyTextures();
|
||||||
|
}
|
||||||
|
@ -5,25 +5,32 @@
|
|||||||
|
|
||||||
#include "util/imgui_manager.h"
|
#include "util/imgui_manager.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ImGuiManager {
|
namespace ImGuiManager {
|
||||||
void RenderTextOverlays();
|
void RenderTextOverlays();
|
||||||
void RenderDebugWindows();
|
void RenderDebugWindows();
|
||||||
|
|
||||||
void RenderOverlayWindows();
|
void RenderOverlayWindows();
|
||||||
|
void DestroyOverlayTextures();
|
||||||
} // namespace ImGuiManager
|
} // namespace ImGuiManager
|
||||||
|
|
||||||
namespace SaveStateSelectorUI {
|
namespace SaveStateSelectorUI {
|
||||||
|
|
||||||
static constexpr float DEFAULT_OPEN_TIME = 5.0f;
|
static constexpr float DEFAULT_OPEN_TIME = 7.5f;
|
||||||
|
|
||||||
bool IsOpen();
|
bool IsOpen();
|
||||||
void Open(float open_time = DEFAULT_OPEN_TIME);
|
void Open(float open_time = DEFAULT_OPEN_TIME);
|
||||||
void RefreshList();
|
void RefreshList(const std::string& serial);
|
||||||
void DestroyTextures();
|
void Clear();
|
||||||
void Close(bool reset_slot = false);
|
void ClearList();
|
||||||
|
void Close();
|
||||||
|
|
||||||
void SelectNextSlot();
|
void SelectNextSlot(bool open_selector);
|
||||||
void SelectPreviousSlot();
|
void SelectPreviousSlot(bool open_selector);
|
||||||
|
|
||||||
|
s32 GetCurrentSlot();
|
||||||
|
bool IsCurrentSlotGlobal();
|
||||||
void LoadCurrentSlot();
|
void LoadCurrentSlot();
|
||||||
void SaveCurrentSlot();
|
void SaveCurrentSlot();
|
||||||
|
|
||||||
|
@ -1621,7 +1621,7 @@ void System::DestroySystem()
|
|||||||
|
|
||||||
PostProcessing::Shutdown();
|
PostProcessing::Shutdown();
|
||||||
|
|
||||||
SaveStateSelectorUI::Close(true);
|
SaveStateSelectorUI::Clear();
|
||||||
FullscreenUI::OnSystemDestroyed();
|
FullscreenUI::OnSystemDestroyed();
|
||||||
|
|
||||||
InputManager::PauseVibration();
|
InputManager::PauseVibration();
|
||||||
@ -3367,7 +3367,9 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
|
|||||||
UpdateSessionTime(prev_serial);
|
UpdateSessionTime(prev_serial);
|
||||||
|
|
||||||
if (SaveStateSelectorUI::IsOpen())
|
if (SaveStateSelectorUI::IsOpen())
|
||||||
SaveStateSelectorUI::RefreshList();
|
SaveStateSelectorUI::RefreshList(s_running_game_serial);
|
||||||
|
else
|
||||||
|
SaveStateSelectorUI::ClearList();
|
||||||
|
|
||||||
#ifdef ENABLE_DISCORD_PRESENCE
|
#ifdef ENABLE_DISCORD_PRESENCE
|
||||||
UpdateDiscordPresence();
|
UpdateDiscordPresence();
|
||||||
|
Loading…
Reference in New Issue
Block a user