mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-11 01:24:11 +02:00
FullscreenUI: Add reset option to directory selector
And improve styling.
This commit is contained in:
@@ -52,3 +52,8 @@
|
||||
#define ICON_EMOJI_CHART_UPWARDS_TREND "\xf0\x9f\x93\x88"
|
||||
#define ICON_EMOJI_DIRECT_HIT "\xf0\x9f\x8e\xaf"
|
||||
#define ICON_EMOJI_RED_FLAG "\xf0\x9f\x9a\xa9"
|
||||
#define ICON_EMOJI_ARROW_UP "\xe2\xac\x86"
|
||||
#define ICON_EMOJI_ARROW_LEFT "\xe2\xac\x85"
|
||||
#define ICON_EMOJI_ARROW_DOWN "\xe2\xac\x87"
|
||||
#define ICON_EMOJI_ARROW_RIGHT "\xe2\x9e\xa1"
|
||||
#define ICON_EMOJI_ARROW_RIGHT_CURVE_UP "\xe2\xa4\xb4"
|
||||
|
||||
+30
-33
@@ -741,13 +741,11 @@ void FullscreenUI::DoResume()
|
||||
|
||||
void FullscreenUI::DoStartFile()
|
||||
{
|
||||
auto callback = [](const std::string& path) {
|
||||
if (!path.empty())
|
||||
DoStartPath(path);
|
||||
};
|
||||
|
||||
OpenFileSelector(FSUI_ICONVSTR(ICON_EMOJI_OPTICAL_DISK, "Select Disc Image"), false, std::move(callback),
|
||||
GetDiscImageFilters());
|
||||
OpenFileSelector(FSUI_ICONVSTR(ICON_EMOJI_OPTICAL_DISK, "Select Disc Image"), GetDiscImageFilters(), {},
|
||||
[](const std::string& path) {
|
||||
if (!path.empty())
|
||||
DoStartPath(path);
|
||||
});
|
||||
}
|
||||
|
||||
void FullscreenUI::DoStartBIOS()
|
||||
@@ -877,35 +875,34 @@ void FullscreenUI::DoToggleFastForward()
|
||||
|
||||
void FullscreenUI::StartChangeDiscFromFile(bool return_to_game)
|
||||
{
|
||||
auto callback = [return_to_game](const std::string& path) {
|
||||
if (path.empty())
|
||||
{
|
||||
if (return_to_game)
|
||||
UnpauseForMenuClose();
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmWithSafetyCheck(FSUI_STR("change disc"), false, [path, return_to_game](bool result) {
|
||||
if (result)
|
||||
OpenFileSelector(
|
||||
FSUI_ICONVSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), GetDiscImageFilters(),
|
||||
std::string(Path::GetDirectory(VideoThread::GetGamePath())), [return_to_game](const std::string& path) {
|
||||
if (path.empty())
|
||||
{
|
||||
if (!GameList::IsScannableFilename(path))
|
||||
{
|
||||
OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, FSUI_STR("Error"),
|
||||
fmt::format(FSUI_FSTR("{} is not a valid disc image."), Path::GetFileName(path)));
|
||||
if (return_to_game)
|
||||
UnpauseForMenuClose();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::RunOnCoreThread([path]() { System::InsertMedia(path.c_str()); });
|
||||
ClosePauseMenuImmediately();
|
||||
}
|
||||
if (return_to_game)
|
||||
UnpauseForMenuClose();
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
OpenFileSelector(FSUI_ICONVSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), false, std::move(callback),
|
||||
GetDiscImageFilters(), std::string(Path::GetDirectory(VideoThread::GetGamePath())));
|
||||
ConfirmWithSafetyCheck(FSUI_STR("change disc"), false, [path, return_to_game](bool result) {
|
||||
if (result)
|
||||
{
|
||||
if (!GameList::IsScannableFilename(path))
|
||||
{
|
||||
OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, FSUI_STR("Error"),
|
||||
fmt::format(FSUI_FSTR("{} is not a valid disc image."), Path::GetFileName(path)));
|
||||
if (return_to_game)
|
||||
UnpauseForMenuClose();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::RunOnCoreThread([path]() { System::InsertMedia(path.c_str()); });
|
||||
ClosePauseMenuImmediately();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// This can come from the core thread without the menu, so need to to trigger run idle.
|
||||
UpdateRunIdleState();
|
||||
|
||||
@@ -86,38 +86,36 @@ void FullscreenUI::ClearGameListState()
|
||||
|
||||
void FullscreenUI::DoSetCoverImage(std::string entry_path)
|
||||
{
|
||||
OpenFileSelector(
|
||||
FSUI_ICONVSTR(ICON_FA_IMAGE, "Set Cover Image"), false,
|
||||
[entry_path = std::move(entry_path)](std::string path) {
|
||||
if (path.empty())
|
||||
return;
|
||||
OpenFileSelector(FSUI_ICONVSTR(ICON_FA_IMAGE, "Set Cover Image"), GetImageFilters(), EmuFolders::Covers,
|
||||
[entry_path = std::move(entry_path)](std::string path) {
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
const auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(entry_path);
|
||||
if (!entry)
|
||||
return;
|
||||
const auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(entry_path);
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
std::string existing_path = GameList::GetCoverImagePathForEntry(entry);
|
||||
std::string new_path = GameList::GetNewCoverImagePathForEntry(entry, path.c_str(), false);
|
||||
if (!existing_path.empty())
|
||||
{
|
||||
OpenConfirmMessageDialog(
|
||||
ICON_EMOJI_WARNING, FSUI_ICONVSTR(ICON_FA_IMAGE, "Set Cover Image"),
|
||||
FSUI_STR("A cover already exists for this game. Are you sure that you want to overwrite it?"),
|
||||
[path = std::move(path), existing_path = std::move(existing_path),
|
||||
new_path = std::move(new_path)](bool result) {
|
||||
if (!result)
|
||||
return;
|
||||
std::string existing_path = GameList::GetCoverImagePathForEntry(entry);
|
||||
std::string new_path = GameList::GetNewCoverImagePathForEntry(entry, path.c_str(), false);
|
||||
if (!existing_path.empty())
|
||||
{
|
||||
OpenConfirmMessageDialog(
|
||||
ICON_EMOJI_WARNING, FSUI_ICONVSTR(ICON_FA_IMAGE, "Set Cover Image"),
|
||||
FSUI_STR("A cover already exists for this game. Are you sure that you want to overwrite it?"),
|
||||
[path = std::move(path), existing_path = std::move(existing_path),
|
||||
new_path = std::move(new_path)](bool result) {
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
DoSetCoverImage(std::move(path), std::move(existing_path), std::move(new_path));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DoSetCoverImage(std::move(path), std::move(existing_path), std::move(new_path));
|
||||
}
|
||||
},
|
||||
GetImageFilters(), EmuFolders::Covers);
|
||||
DoSetCoverImage(std::move(path), std::move(existing_path), std::move(new_path));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DoSetCoverImage(std::move(path), std::move(existing_path), std::move(new_path));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FullscreenUI::DoSetCoverImage(std::string source_path, std::string existing_path, std::string new_path)
|
||||
|
||||
@@ -1611,22 +1611,22 @@ void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, std::string_view ti
|
||||
{
|
||||
if (MenuButton(title, runtime_var))
|
||||
{
|
||||
OpenFileSelector(title, true,
|
||||
[game_settings = IsEditingGameSettings(bsi), section = TinyString(section),
|
||||
key = TinyString(key)](const std::string& dir) {
|
||||
if (dir.empty())
|
||||
return;
|
||||
OpenDirectorySelector(title, runtime_var, EmuFolders::GetDefaultPath(&runtime_var),
|
||||
[game_settings = IsEditingGameSettings(bsi), section = TinyString(section),
|
||||
key = TinyString(key)](const std::string& dir) {
|
||||
if (dir.empty())
|
||||
return;
|
||||
|
||||
const auto lock = Core::GetSettingsLock();
|
||||
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
||||
std::string relative_path(Path::MakeRelative(dir, EmuFolders::DataRoot));
|
||||
bsi->SetStringValue(section.c_str(), key.c_str(), relative_path.c_str());
|
||||
SetSettingsChanged(bsi);
|
||||
const auto lock = Core::GetSettingsLock();
|
||||
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
|
||||
std::string relative_path(Path::MakeRelative(dir, EmuFolders::DataRoot));
|
||||
bsi->SetStringValue(section.c_str(), key.c_str(), relative_path.c_str());
|
||||
SetSettingsChanged(bsi);
|
||||
|
||||
Host::RunOnCoreThread(&EmuFolders::Update);
|
||||
if (key == "Covers")
|
||||
RemoveCoverCacheEntry({});
|
||||
});
|
||||
Host::RunOnCoreThread(&EmuFolders::Update);
|
||||
if (key == "Covers")
|
||||
RemoveCoverCacheEntry({});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2630,19 +2630,20 @@ void FullscreenUI::DrawGameListSettingsPage()
|
||||
if (MenuButton(FSUI_ICONVSTR(ICON_FA_FOLDER_PLUS, "Add Search Directory"),
|
||||
FSUI_VSTR("Adds a new directory to the game search list.")))
|
||||
{
|
||||
OpenFileSelector(FSUI_ICONVSTR(ICON_FA_FOLDER_PLUS, "Add Search Directory"), true, [](const std::string& dir) {
|
||||
if (!dir.empty())
|
||||
{
|
||||
const auto lock = Core::GetSettingsLock();
|
||||
SettingsInterface* bsi = Core::GetBaseSettingsLayer();
|
||||
OpenDirectorySelector(FSUI_ICONVSTR(ICON_FA_FOLDER_PLUS, "Add Search Directory"), {}, {},
|
||||
[](const std::string& dir) {
|
||||
if (!dir.empty())
|
||||
{
|
||||
const auto lock = Core::GetSettingsLock();
|
||||
SettingsInterface* bsi = Core::GetBaseSettingsLayer();
|
||||
|
||||
bsi->AddToStringList("GameList", "RecursivePaths", dir.c_str());
|
||||
bsi->RemoveFromStringList("GameList", "Paths", dir.c_str());
|
||||
SetSettingsChanged(bsi);
|
||||
PopulateGameListDirectoryCache(*bsi);
|
||||
Host::RefreshGameListAsync(false);
|
||||
}
|
||||
});
|
||||
bsi->AddToStringList("GameList", "RecursivePaths", dir.c_str());
|
||||
bsi->RemoveFromStringList("GameList", "Paths", dir.c_str());
|
||||
SetSettingsChanged(bsi);
|
||||
PopulateGameListDirectoryCache(*bsi);
|
||||
Host::RefreshGameListAsync(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const auto& it : s_settings_locals.game_list_directories_cache)
|
||||
@@ -3839,7 +3840,6 @@ void FullscreenUI::DrawMemoryCardSettingsPage()
|
||||
static constexpr const std::array path_keys = {"Card1Path", "Card2Path"};
|
||||
|
||||
SettingsInterface* bsi = GetEditingSettingsInterface();
|
||||
const bool game_settings = IsEditingGameSettings(bsi);
|
||||
|
||||
BeginMenuButtons();
|
||||
ResetFocusHere();
|
||||
@@ -3915,7 +3915,7 @@ void FullscreenUI::DrawMemoryCardSettingsPage()
|
||||
}
|
||||
}
|
||||
|
||||
MenuHeading(FSUI_VSTR("Settings and Operations"));
|
||||
MenuHeading(FSUI_VSTR("Save Locations"));
|
||||
|
||||
DrawFolderSetting(bsi, FSUI_ICONVSTR(ICON_FA_FOLDER_OPEN, "Memory Card Directory"), "MemoryCards", "Directory",
|
||||
EmuFolders::MemoryCards);
|
||||
@@ -3923,12 +3923,7 @@ void FullscreenUI::DrawMemoryCardSettingsPage()
|
||||
DrawFolderSetting(bsi, FSUI_ICONVSTR(ICON_FA_FOLDER_OPEN, "Save State Directory"), "Folders", "SaveStates",
|
||||
EmuFolders::SaveStates);
|
||||
|
||||
if (!game_settings && MenuButton(FSUI_ICONVSTR(ICON_FA_ARROW_ROTATE_LEFT, "Reset Memory Card Directory"),
|
||||
FSUI_VSTR("Resets memory card directory to default (user directory).")))
|
||||
{
|
||||
bsi->SetStringValue("MemoryCards", "Directory", "memcards");
|
||||
SetSettingsChanged(bsi);
|
||||
}
|
||||
MenuHeading(FSUI_VSTR("Settings"));
|
||||
|
||||
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_SHARE_NODES, "Use Single Card For Multi-Disc Games"),
|
||||
FSUI_VSTR("When playing a multi-disc game and using per-game (title) memory cards, "
|
||||
@@ -4894,18 +4889,16 @@ void FullscreenUI::DrawPostProcessingSettingsPage()
|
||||
if (MenuButton(FSUI_ICONVSTR(ICON_FA_IMAGE, "Image Path"),
|
||||
GetEffectiveTinyStringSetting(bsi, "BorderOverlay", "ImagePath", "")))
|
||||
{
|
||||
OpenFileSelector(
|
||||
FSUI_ICONVSTR(ICON_FA_IMAGE, "Image Path"), false,
|
||||
[game_settings = IsEditingGameSettings(bsi)](const std::string& path) {
|
||||
if (path.empty())
|
||||
return;
|
||||
OpenFileSelector(FSUI_ICONVSTR(ICON_FA_IMAGE, "Image Path"), GetImageFilters(), {},
|
||||
[game_settings = IsEditingGameSettings(bsi)](const std::string& path) {
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
SettingsInterface* const bsi = GetEditingSettingsInterface(game_settings);
|
||||
bsi->SetStringValue("BorderOverlay", "ImagePath", path.c_str());
|
||||
SetSettingsChanged(bsi);
|
||||
queue_reload();
|
||||
},
|
||||
GetImageFilters());
|
||||
SettingsInterface* const bsi = GetEditingSettingsInterface(game_settings);
|
||||
bsi->SetStringValue("BorderOverlay", "ImagePath", path.c_str());
|
||||
SetSettingsChanged(bsi);
|
||||
queue_reload();
|
||||
});
|
||||
}
|
||||
|
||||
reload_pending |= DrawIntRectSetting(
|
||||
|
||||
@@ -86,8 +86,6 @@ TRANSLATE_NOOP("FullscreenUI", "9 Frames");
|
||||
TRANSLATE_NOOP("FullscreenUI", "90% [54 FPS (NTSC) / 45 FPS (PAL)]");
|
||||
TRANSLATE_NOOP("FullscreenUI", "900% [540 FPS (NTSC) / 450 FPS (PAL)]");
|
||||
TRANSLATE_NOOP("FullscreenUI", "9x (for 4K)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "<Parent Directory>");
|
||||
TRANSLATE_NOOP("FullscreenUI", "<Use This Directory>");
|
||||
TRANSLATE_NOOP("FullscreenUI", "A cover already exists for this game. Are you sure that you want to overwrite it?");
|
||||
TRANSLATE_NOOP("FullscreenUI", "A resume save state was found for this game, but it is corrupted and cannot be loaded:\n\n{}\n\nDo you want to delete the save state and boot the game anyway?");
|
||||
TRANSLATE_NOOP("FullscreenUI", "AMOLED");
|
||||
@@ -634,12 +632,11 @@ TRANSLATE_NOOP("FullscreenUI", "Rendering");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Replaces these settings with a previously saved controller preset.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Rescan All Games");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Reset Controller Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Reset Memory Card Directory");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Reset Play Time");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Reset Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Reset To Default");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Resets all configuration to defaults (including bindings).");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Resets all settings to the defaults.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Resets memory card directory to default (user directory).");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Restart Game");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Restore Defaults");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Resume Game");
|
||||
@@ -665,6 +662,7 @@ TRANSLATE_NOOP("FullscreenUI", "SDL DualSense Player LED");
|
||||
TRANSLATE_NOOP("FullscreenUI", "SDL DualShock 4 / DualSense Enhanced Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Safe Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Save Controller Preset");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Save Locations");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Save Preset");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Save Screenshot");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Save State");
|
||||
@@ -735,7 +733,6 @@ TRANSLATE_NOOP("FullscreenUI", "Sets the verbosity of messages logged. Higher le
|
||||
TRANSLATE_NOOP("FullscreenUI", "Sets which sort of memory card image will be used for slot {}.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Setting {} binding {}.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Settings and Operations");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Settings reset to default.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shader {} added as stage {}.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shared Card Name");
|
||||
@@ -865,6 +862,7 @@ TRANSLATE_NOOP("FullscreenUI", "Use Separate Disc Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use Single Card For Multi-Disc Games");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer (Low VRAM Mode)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer For Readbacks");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use This Directory");
|
||||
TRANSLATE_NOOP("FullscreenUI", "User Name");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Uses OpenGL ES even when desktop OpenGL is supported. May improve performance on some SBC drivers.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Uses PGXP for all instructions, not just memory operations.");
|
||||
|
||||
@@ -271,7 +271,7 @@ public:
|
||||
~FileSelectorDialog();
|
||||
|
||||
void Open(std::string_view title, FileSelectorCallback callback, FileSelectorFilters filters,
|
||||
std::string initial_directory, bool select_directory);
|
||||
std::string initial_directory);
|
||||
void ClearState();
|
||||
|
||||
void Draw();
|
||||
@@ -298,10 +298,50 @@ private:
|
||||
|
||||
std::string m_current_directory;
|
||||
std::vector<Item> m_items;
|
||||
std::vector<std::string> m_filters;
|
||||
FileSelectorFilters m_filters;
|
||||
FileSelectorCallback m_callback;
|
||||
|
||||
bool m_directory_changed = false;
|
||||
bool m_first_item_is_parent_directory = false;
|
||||
};
|
||||
|
||||
class DirectorySelectorDialog : public PopupDialog
|
||||
{
|
||||
public:
|
||||
DirectorySelectorDialog();
|
||||
~DirectorySelectorDialog();
|
||||
|
||||
void Open(std::string_view title, DirectorySelectorCallback callback, std::string initial_directory,
|
||||
std::string default_directory);
|
||||
void ClearState();
|
||||
|
||||
void Draw();
|
||||
|
||||
private:
|
||||
struct Item
|
||||
{
|
||||
Item() = default;
|
||||
Item(std::string display_name_, std::string full_path_, bool is_file_);
|
||||
Item(const Item&) = default;
|
||||
Item(Item&&) = default;
|
||||
~Item() = default;
|
||||
|
||||
Item& operator=(const Item&) = default;
|
||||
Item& operator=(Item&&) = default;
|
||||
|
||||
std::string display_name;
|
||||
std::string full_path;
|
||||
bool is_file;
|
||||
};
|
||||
|
||||
void PopulateItems();
|
||||
void SetDirectory(std::string dir);
|
||||
|
||||
std::string m_current_directory;
|
||||
std::vector<Item> m_items;
|
||||
std::string m_default_directory;
|
||||
FileSelectorCallback m_callback;
|
||||
|
||||
bool m_is_directory = false;
|
||||
bool m_directory_changed = false;
|
||||
bool m_first_item_is_parent_directory = false;
|
||||
};
|
||||
@@ -435,9 +475,11 @@ struct WidgetsState
|
||||
ImAnimatedVec2 menu_button_frame_min_animated;
|
||||
ImAnimatedVec2 menu_button_frame_max_animated;
|
||||
|
||||
// TODO: Make these dynamic rather than global state.
|
||||
ChoiceDialog choice_dialog;
|
||||
DropdownDialog dropdown_dialog;
|
||||
FileSelectorDialog file_selector_dialog;
|
||||
DirectorySelectorDialog directory_selector_dialog;
|
||||
InputStringDialog input_string_dialog;
|
||||
FixedPopupDialog fixed_popup_dialog;
|
||||
ProgressDialog progress_dialog;
|
||||
@@ -1648,6 +1690,7 @@ void FullscreenUI::EndLayout()
|
||||
s_state.choice_dialog.Draw();
|
||||
s_state.dropdown_dialog.Draw();
|
||||
s_state.file_selector_dialog.Draw();
|
||||
s_state.directory_selector_dialog.Draw();
|
||||
s_state.input_string_dialog.Draw();
|
||||
s_state.progress_dialog.Draw();
|
||||
s_state.message_dialog.Draw();
|
||||
@@ -3218,6 +3261,24 @@ void FullscreenUI::MenuHeading(std::string_view title, bool draw_line /*= true*/
|
||||
ImGui::Dummy(ImVec2(0.0f, total_height));
|
||||
}
|
||||
|
||||
void FullscreenUI::MenuSeparator()
|
||||
{
|
||||
const float line_thickness = LayoutScale(1.0f);
|
||||
const float line_padding = LayoutScale(5.0f);
|
||||
const float avail_width = MenuButtonBounds::CalcAvailWidth();
|
||||
|
||||
const ImGuiWindow* const window = ImGui::GetCurrentWindowRead();
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
const ImVec2 pos = ImVec2(window->DC.CursorPos.x + style.FramePadding.x, window->DC.CursorPos.y);
|
||||
const ImVec2 line_start = ImVec2(pos.x, pos.y + line_padding);
|
||||
const ImVec2 line_end = ImVec2(line_start.x + avail_width, line_start.y);
|
||||
|
||||
window->DrawList->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness);
|
||||
|
||||
const float total_height = line_thickness + style.FramePadding.y;
|
||||
ImGui::Dummy(ImVec2(0.0f, total_height));
|
||||
}
|
||||
|
||||
bool FullscreenUI::MenuHeadingButton(std::string_view title, std::string_view value /*= {}*/,
|
||||
float font_size /*= UIStyle.LargeFontSize */, bool enabled /*= true*/,
|
||||
bool draw_line /*= true*/)
|
||||
@@ -4802,8 +4863,7 @@ FullscreenUI::FileSelectorDialog::FileSelectorDialog() = default;
|
||||
FullscreenUI::FileSelectorDialog::~FileSelectorDialog() = default;
|
||||
|
||||
void FullscreenUI::FileSelectorDialog::Open(std::string_view title, FileSelectorCallback callback,
|
||||
FileSelectorFilters filters, std::string initial_directory,
|
||||
bool select_directory)
|
||||
FileSelectorFilters filters, std::string initial_directory)
|
||||
{
|
||||
if (initial_directory.empty() || !FileSystem::DirectoryExists(initial_directory.c_str()))
|
||||
initial_directory = FileSystem::GetWorkingDirectory();
|
||||
@@ -4811,7 +4871,6 @@ void FullscreenUI::FileSelectorDialog::Open(std::string_view title, FileSelector
|
||||
SetTitleAndOpen(fmt::format("{}##file_selector_dialog", title));
|
||||
m_callback = std::move(callback);
|
||||
m_filters = std::move(filters);
|
||||
m_is_directory = select_directory;
|
||||
SetDirectory(std::move(initial_directory));
|
||||
}
|
||||
|
||||
@@ -4820,7 +4879,6 @@ void FullscreenUI::FileSelectorDialog::ClearState()
|
||||
PopupDialog::ClearState();
|
||||
m_callback = {};
|
||||
m_filters = {};
|
||||
m_is_directory = false;
|
||||
m_directory_changed = false;
|
||||
}
|
||||
|
||||
@@ -4846,9 +4904,8 @@ void FullscreenUI::FileSelectorDialog::PopulateItems()
|
||||
{
|
||||
FileSystem::FindResultsArray results;
|
||||
FileSystem::FindFiles(m_current_directory.c_str(), "*",
|
||||
(m_is_directory ? 0 : FILESYSTEM_FIND_FILES) | FILESYSTEM_FIND_FOLDERS |
|
||||
FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS |
|
||||
FILESYSTEM_FIND_SORT_BY_NAME,
|
||||
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES |
|
||||
FILESYSTEM_FIND_RELATIVE_PATHS | FILESYSTEM_FIND_SORT_BY_NAME,
|
||||
&results);
|
||||
|
||||
// Ensure we only go back to the root list once we've gone up from the root of that drive.
|
||||
@@ -4863,8 +4920,8 @@ void FullscreenUI::FileSelectorDialog::PopulateItems()
|
||||
parent_path.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
|
||||
}
|
||||
|
||||
m_items.emplace_back(fmt::format(ICON_EMOJI_FILE_FOLDER_OPEN " {}", FSUI_VSTR("<Parent Directory>")),
|
||||
std::move(parent_path), false);
|
||||
m_items.emplace_back(fmt::format(ICON_EMOJI_ARROW_UP " {}", FSUI_VSTR("Parent Directory")), std::move(parent_path),
|
||||
false);
|
||||
m_first_item_is_parent_directory = true;
|
||||
|
||||
for (const FILESYSTEM_FIND_DATA& fd : results)
|
||||
@@ -4931,20 +4988,10 @@ void FullscreenUI::FileSelectorDialog::Draw()
|
||||
BeginMenuButtons();
|
||||
|
||||
Item* selected = nullptr;
|
||||
bool directory_selected = false;
|
||||
|
||||
if (!m_current_directory.empty())
|
||||
MenuButtonWithoutSummary(SmallString::from_format(ICON_FA_FOLDER_OPEN " {}", m_current_directory), false);
|
||||
|
||||
if (m_is_directory && !m_current_directory.empty())
|
||||
{
|
||||
if (MenuButtonWithoutSummary(
|
||||
SmallString::from_format(ICON_EMOJI_FILE_FOLDER_OPEN " {}", FSUI_VSTR("<Use This Directory>"))))
|
||||
{
|
||||
directory_selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (Item& item : m_items)
|
||||
{
|
||||
if (MenuButtonWithoutSummary(item.display_name))
|
||||
@@ -4985,13 +5032,6 @@ void FullscreenUI::FileSelectorDialog::Draw()
|
||||
[this, dir = std::move(selected->full_path)]() mutable { SetDirectory(std::move(dir)); });
|
||||
}
|
||||
}
|
||||
else if (directory_selected)
|
||||
{
|
||||
std::string path = std::exchange(m_current_directory, std::string());
|
||||
const FileSelectorCallback callback = std::exchange(m_callback, FileSelectorCallback());
|
||||
StartClose();
|
||||
callback(std::move(path));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Backspace, false) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadContextMenu, false))
|
||||
@@ -5010,11 +5050,10 @@ bool FullscreenUI::IsFileSelectorOpen()
|
||||
return s_state.file_selector_dialog.IsOpen();
|
||||
}
|
||||
|
||||
void FullscreenUI::OpenFileSelector(std::string_view title, bool select_directory, FileSelectorCallback callback,
|
||||
FileSelectorFilters filters, std::string initial_directory)
|
||||
void FullscreenUI::OpenFileSelector(std::string_view title, FileSelectorFilters filters, std::string initial_directory,
|
||||
FileSelectorCallback callback)
|
||||
{
|
||||
s_state.file_selector_dialog.Open(title, std::move(callback), std::move(filters), std::move(initial_directory),
|
||||
select_directory);
|
||||
s_state.file_selector_dialog.Open(title, std::move(callback), std::move(filters), std::move(initial_directory));
|
||||
}
|
||||
|
||||
void FullscreenUI::CloseFileSelector()
|
||||
@@ -5022,6 +5061,211 @@ void FullscreenUI::CloseFileSelector()
|
||||
s_state.file_selector_dialog.StartClose();
|
||||
}
|
||||
|
||||
FullscreenUI::DirectorySelectorDialog::DirectorySelectorDialog() = default;
|
||||
|
||||
FullscreenUI::DirectorySelectorDialog::~DirectorySelectorDialog() = default;
|
||||
|
||||
void FullscreenUI::DirectorySelectorDialog::Open(std::string_view title, DirectorySelectorCallback callback,
|
||||
std::string initial_directory, std::string default_directory)
|
||||
{
|
||||
if (initial_directory.empty() || !FileSystem::DirectoryExists(initial_directory.c_str()))
|
||||
initial_directory = FileSystem::GetWorkingDirectory();
|
||||
|
||||
SetTitleAndOpen(fmt::format("{}##file_selector_dialog", title));
|
||||
m_default_directory = std::move(default_directory);
|
||||
m_callback = std::move(callback);
|
||||
SetDirectory(std::move(initial_directory));
|
||||
}
|
||||
|
||||
void FullscreenUI::DirectorySelectorDialog::ClearState()
|
||||
{
|
||||
PopupDialog::ClearState();
|
||||
m_callback = {};
|
||||
m_default_directory = {};
|
||||
m_directory_changed = false;
|
||||
}
|
||||
|
||||
FullscreenUI::DirectorySelectorDialog::Item::Item(std::string display_name_, std::string full_path_, bool is_file_)
|
||||
: display_name(std::move(display_name_)), full_path(std::move(full_path_)), is_file(is_file_)
|
||||
{
|
||||
}
|
||||
|
||||
void FullscreenUI::DirectorySelectorDialog::PopulateItems()
|
||||
{
|
||||
m_items.clear();
|
||||
m_first_item_is_parent_directory = false;
|
||||
|
||||
if (m_current_directory.empty())
|
||||
{
|
||||
for (std::string& root_path : FileSystem::GetRootDirectoryList())
|
||||
{
|
||||
std::string label = fmt::format(ICON_EMOJI_FILE_FOLDER " {}", root_path);
|
||||
m_items.emplace_back(std::move(label), std::move(root_path), false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FileSystem::FindResultsArray results;
|
||||
FileSystem::FindFiles(m_current_directory.c_str(), "*",
|
||||
FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS |
|
||||
FILESYSTEM_FIND_SORT_BY_NAME,
|
||||
&results);
|
||||
|
||||
// Ensure we only go back to the root list once we've gone up from the root of that drive.
|
||||
std::string parent_path;
|
||||
std::string::size_type sep_pos = m_current_directory.rfind(FS_OSPATH_SEPARATOR_CHARACTER);
|
||||
if (sep_pos != std::string::npos && sep_pos != (m_current_directory.size() - 1))
|
||||
{
|
||||
parent_path = Path::Canonicalize(m_current_directory.substr(0, sep_pos));
|
||||
|
||||
// Ensure that the root directory has a trailing backslash.
|
||||
if (parent_path.find(FS_OSPATH_SEPARATOR_CHARACTER) == std::string::npos)
|
||||
parent_path.push_back(FS_OSPATH_SEPARATOR_CHARACTER);
|
||||
}
|
||||
|
||||
m_items.emplace_back(fmt::format(ICON_EMOJI_ARROW_UP " {}", FSUI_VSTR("Parent Directory")), std::move(parent_path),
|
||||
false);
|
||||
m_first_item_is_parent_directory = true;
|
||||
|
||||
for (const FILESYSTEM_FIND_DATA& fd : results)
|
||||
{
|
||||
std::string full_path = Path::Combine(m_current_directory, fd.FileName);
|
||||
std::string title = fmt::format(ICON_EMOJI_FILE_FOLDER " {}", fd.FileName);
|
||||
m_items.emplace_back(std::move(title), std::move(full_path), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FullscreenUI::DirectorySelectorDialog::SetDirectory(std::string dir)
|
||||
{
|
||||
// Ensure at least one slash always exists.
|
||||
while (!dir.empty() && dir.back() == FS_OSPATH_SEPARATOR_CHARACTER &&
|
||||
dir.find(FS_OSPATH_SEPARATOR_CHARACTER) != (dir.size() - 1))
|
||||
{
|
||||
dir.pop_back();
|
||||
}
|
||||
|
||||
m_current_directory = std::move(dir);
|
||||
m_directory_changed = true;
|
||||
PopulateItems();
|
||||
}
|
||||
|
||||
void FullscreenUI::DirectorySelectorDialog::Draw()
|
||||
{
|
||||
if (!IsOpen())
|
||||
return;
|
||||
|
||||
if (!BeginRender(LayoutScale(10.0f), LayoutScale(20.0f), LayoutScale(1000.0f, 650.0f)))
|
||||
{
|
||||
const DirectorySelectorCallback callback = std::move(m_callback);
|
||||
ClearState();
|
||||
if (callback)
|
||||
callback(std::string());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_directory_changed)
|
||||
{
|
||||
m_directory_changed = false;
|
||||
ImGui::SetScrollY(0.0f);
|
||||
QueueResetFocus(FocusResetType::Other);
|
||||
}
|
||||
|
||||
ResetFocusHere();
|
||||
BeginMenuButtons();
|
||||
|
||||
Item* selected = nullptr;
|
||||
std::string selected_directory;
|
||||
|
||||
if (!m_current_directory.empty())
|
||||
{
|
||||
MenuButtonWithoutSummary(SmallString::from_format(ICON_FA_FOLDER_OPEN " {}", m_current_directory), false);
|
||||
|
||||
if (MenuButtonWithoutSummary(FSUI_ICONVSTR(ICON_EMOJI_CHECKMARK_BUTTON, "Use This Directory")))
|
||||
selected_directory = m_current_directory;
|
||||
}
|
||||
|
||||
if (!m_default_directory.empty())
|
||||
{
|
||||
if (MenuButtonWithoutSummary(FSUI_ICONVSTR(ICON_EMOJI_REFRESH, "Reset To Default")))
|
||||
selected_directory = m_default_directory;
|
||||
}
|
||||
|
||||
bool needs_separator =
|
||||
(!m_current_directory.empty() || !m_default_directory.empty() || m_first_item_is_parent_directory);
|
||||
for (size_t i = 0; i < m_items.size(); i++)
|
||||
{
|
||||
if (needs_separator && (i > 0 || !m_first_item_is_parent_directory))
|
||||
{
|
||||
needs_separator = false;
|
||||
MenuSeparator();
|
||||
}
|
||||
|
||||
Item& item = m_items[i];
|
||||
if (MenuButtonWithoutSummary(item.display_name))
|
||||
selected = &item;
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
|
||||
if (IsGamepadInputSource())
|
||||
{
|
||||
SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")),
|
||||
std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Parent Directory")),
|
||||
std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")),
|
||||
std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Cancel"))});
|
||||
}
|
||||
else
|
||||
{
|
||||
SetFullscreenFooterText(
|
||||
std::array{std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Change Selection")),
|
||||
std::make_pair(ICON_PF_BACKSPACE, FSUI_VSTR("Parent Directory")),
|
||||
std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), std::make_pair(ICON_PF_ESC, FSUI_VSTR("Cancel"))});
|
||||
}
|
||||
|
||||
EndRender();
|
||||
|
||||
if (selected)
|
||||
{
|
||||
BeginTransition(DEFAULT_TRANSITION_TIME,
|
||||
[this, dir = std::move(selected->full_path)]() mutable { SetDirectory(std::move(dir)); });
|
||||
}
|
||||
else if (!selected_directory.empty())
|
||||
{
|
||||
const DirectorySelectorCallback callback = std::exchange(m_callback, DirectorySelectorCallback());
|
||||
StartClose();
|
||||
callback(std::move(selected_directory));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Backspace, false) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadContextMenu, false))
|
||||
{
|
||||
if (!m_items.empty() && m_first_item_is_parent_directory)
|
||||
{
|
||||
BeginTransition(DEFAULT_TRANSITION_TIME,
|
||||
[this, dir = std::move(m_items.front().full_path)]() mutable { SetDirectory(std::move(dir)); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FullscreenUI::IsDirectorySelectorOpen()
|
||||
{
|
||||
return s_state.directory_selector_dialog.IsOpen();
|
||||
}
|
||||
|
||||
void FullscreenUI::OpenDirectorySelector(std::string_view title, std::string initial_directory,
|
||||
std::string default_directory, DirectorySelectorCallback callback)
|
||||
{
|
||||
s_state.directory_selector_dialog.Open(title, std::move(callback), std::move(initial_directory),
|
||||
std::move(default_directory));
|
||||
}
|
||||
|
||||
void FullscreenUI::CloseDirectorySelector()
|
||||
{
|
||||
s_state.directory_selector_dialog.StartClose();
|
||||
}
|
||||
|
||||
FullscreenUI::ChoiceDialog::ChoiceDialog() = default;
|
||||
|
||||
FullscreenUI::ChoiceDialog::~ChoiceDialog() = default;
|
||||
|
||||
@@ -424,6 +424,7 @@ void RenderAutoLabelText(ImDrawList* draw_list, ImFont* font, float font_size, f
|
||||
void TextAlignedMultiLine(float align_x, const char* text, const char* text_end = nullptr, float wrap_width = -1.0f);
|
||||
void TextUnformatted(std::string_view text);
|
||||
void MenuHeading(std::string_view title, bool draw_line = true);
|
||||
void MenuSeparator();
|
||||
bool MenuHeadingButton(std::string_view title, std::string_view value = {}, float font_size = UIStyle.LargeFontSize,
|
||||
bool enabled = true, bool draw_line = true);
|
||||
bool MenuButton(std::string_view title, std::string_view summary, bool enabled = true,
|
||||
@@ -540,11 +541,16 @@ bool AreAnyWidgetsDialogInteractable();
|
||||
using FileSelectorCallback = std::function<void(std::string path)>;
|
||||
using FileSelectorFilters = std::vector<std::string>;
|
||||
bool IsFileSelectorOpen();
|
||||
void OpenFileSelector(std::string_view title, bool select_directory, FileSelectorCallback callback,
|
||||
FileSelectorFilters filters = FileSelectorFilters(),
|
||||
std::string initial_directory = std::string());
|
||||
void OpenFileSelector(std::string_view title, FileSelectorFilters filters, std::string initial_directory,
|
||||
FileSelectorCallback callback);
|
||||
void CloseFileSelector();
|
||||
|
||||
using DirectorySelectorCallback = std::function<void(std::string path)>;
|
||||
bool IsDirectorySelectorOpen();
|
||||
void OpenDirectorySelector(std::string_view title, std::string initial_directory, std::string default_directory,
|
||||
DirectorySelectorCallback callback);
|
||||
void CloseDirectorySelector();
|
||||
|
||||
using ChoiceDialogCallback = std::function<void(s32 index, const std::string& title, bool checked)>;
|
||||
using ChoiceDialogOptions = std::vector<std::pair<std::string, bool>>;
|
||||
bool IsChoiceDialogOpen();
|
||||
|
||||
+30
-3
@@ -2775,6 +2775,10 @@ const char* Settings::GetPIODeviceTypeModeDisplayName(PIODeviceType type)
|
||||
|
||||
namespace EmuFolders {
|
||||
|
||||
static void EnsureFolderExists(const std::string& path);
|
||||
static std::string LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* section,
|
||||
const char* name, const char* def);
|
||||
|
||||
std::string AppRoot;
|
||||
std::string DataRoot;
|
||||
std::string Bios;
|
||||
@@ -2795,10 +2799,33 @@ std::string Textures;
|
||||
std::string UserResources;
|
||||
std::string Videos;
|
||||
|
||||
static void EnsureFolderExists(const std::string& path);
|
||||
|
||||
} // namespace EmuFolders
|
||||
|
||||
std::string EmuFolders::GetDefaultPath(const std::string* ref_folder)
|
||||
{
|
||||
// clang-format off
|
||||
std::string_view subdir;
|
||||
if (ref_folder == &Bios) subdir = "bios";
|
||||
else if (ref_folder == &Cache) subdir = "cache";
|
||||
else if (ref_folder == &Cheats) subdir = "cheats";
|
||||
else if (ref_folder == &Covers) subdir = "covers";
|
||||
else if (ref_folder == &GameIcons) subdir = "gameicons";
|
||||
else if (ref_folder == &GameSettings) subdir = "gamesettings";
|
||||
else if (ref_folder == &InputProfiles) subdir = "inputprofiles";
|
||||
else if (ref_folder == &MemoryCards) subdir = "memcards";
|
||||
else if (ref_folder == &Patches) subdir = "patches";
|
||||
else if (ref_folder == &SaveStates) subdir = "savestates";
|
||||
else if (ref_folder == &Screenshots) subdir = "screenshots";
|
||||
else if (ref_folder == &Shaders) subdir = "shaders";
|
||||
else if (ref_folder == &Subchannels) subdir = "subchannels";
|
||||
else if (ref_folder == &Textures) subdir = "textures";
|
||||
else if (ref_folder == &UserResources) subdir = "resources";
|
||||
else if (ref_folder == &Videos) subdir = "videos";
|
||||
// clang-format on
|
||||
|
||||
return Path::Combine(DataRoot, subdir);
|
||||
}
|
||||
|
||||
void EmuFolders::SetDefaults()
|
||||
{
|
||||
Bios = Path::Combine(DataRoot, "bios");
|
||||
@@ -2819,7 +2846,7 @@ void EmuFolders::SetDefaults()
|
||||
Videos = Path::Combine(DataRoot, "videos");
|
||||
}
|
||||
|
||||
static std::string LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* section,
|
||||
std::string EmuFolders::LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* section,
|
||||
const char* name, const char* def)
|
||||
{
|
||||
std::string value = si.GetStringValue(section, name, def);
|
||||
|
||||
@@ -713,6 +713,9 @@ void EnsureFoldersExist();
|
||||
void LoadConfig(SettingsInterface& si);
|
||||
void Save(SettingsInterface& si);
|
||||
|
||||
// Returns the default path for the given settings key.
|
||||
std::string GetDefaultPath(const std::string* ref_folder);
|
||||
|
||||
/// Updates the variables in the EmuFolders namespace, reloading subsystems if needed.
|
||||
void Update();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user