FullscreenUI: Add reset option to directory selector

And improve styling.
This commit is contained in:
Stenzek
2026-06-21 13:43:53 +10:00
parent c712c7b299
commit 8f6b1b6307
9 changed files with 422 additions and 151 deletions
+5
View File
@@ -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
View File
@@ -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();
+27 -29
View File
@@ -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)
+38 -45
View File
@@ -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(
+3 -5
View File
@@ -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.");
+277 -33
View File
@@ -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;
+9 -3
View File
@@ -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
View File
@@ -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);
+3
View File
@@ -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();