From 221d9c71acf630747a046435aa20d3ae8cc27848 Mon Sep 17 00:00:00 2001 From: crueter Date: Wed, 8 Oct 2025 16:07:02 -0400 Subject: [PATCH 1/3] [desktop] Initial data manager prototype Right now, all this adds is a small dialog to the Widgets frontend showing the user how much space is taken up by their saves, shaders, NAND, and mods. It also gives them the choice to clear these directories (with 2x confirmation), OR open the directory in their system file manager. In the future, a lot more can be done with this concept. Notably, a common import/export (a la android) could be added, the UI can obviously be polished, etc. We could also add this to Android, but I don't think common import/export is needed *for* Android, and should probably be left in qt_common. Signed-off-by: crueter --- src/frontend_common/CMakeLists.txt | 1 + src/frontend_common/data_manager.cpp | 72 ++++++++++++ src/frontend_common/data_manager.h | 21 ++++ src/qt_common/CMakeLists.txt | 5 +- src/qt_common/externals/CMakeLists.txt | 2 +- src/qt_common/qt_content_util.cpp | 25 ++++ src/qt_common/qt_content_util.h | 3 + src/qt_common/qt_string_lookup.h | 35 ++++++ src/yuzu/CMakeLists.txt | 2 + src/yuzu/data_dialog.cpp | 104 +++++++++++++++++ src/yuzu/data_dialog.h | 43 +++++++ src/yuzu/data_dialog.ui | 152 +++++++++++++++++++++++++ src/yuzu/main.cpp | 7 ++ src/yuzu/main.h | 1 + src/yuzu/main.ui | 12 +- 15 files changed, 478 insertions(+), 7 deletions(-) create mode 100644 src/frontend_common/data_manager.cpp create mode 100644 src/frontend_common/data_manager.h create mode 100644 src/qt_common/qt_string_lookup.h create mode 100644 src/yuzu/data_dialog.cpp create mode 100644 src/yuzu/data_dialog.h create mode 100644 src/yuzu/data_dialog.ui diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt index 70e142bb0c..83638476bf 100644 --- a/src/frontend_common/CMakeLists.txt +++ b/src/frontend_common/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(frontend_common STATIC content_manager.h firmware_manager.h firmware_manager.cpp + data_manager.h data_manager.cpp ) create_target_directory_groups(frontend_common) diff --git a/src/frontend_common/data_manager.cpp b/src/frontend_common/data_manager.cpp new file mode 100644 index 0000000000..21e2422a42 --- /dev/null +++ b/src/frontend_common/data_manager.cpp @@ -0,0 +1,72 @@ +#include "data_manager.h" +#include "common/assert.h" +#include "common/fs/path_util.h" +#include +#include + +namespace FrontendCommon::DataManager { + +namespace fs = std::filesystem; + +const std::string GetDataDir(DataDir dir) +{ + const fs::path nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir); + + switch (dir) { + case DataDir::Saves: + return (nand_dir / "user" / "save" / "0000000000000000").string(); + case DataDir::UserNand: + return (nand_dir / "user" / "Contents" / "registered").string(); + case DataDir::SysNand: + return (nand_dir / "system").string(); + case DataDir::Mods: + return Common::FS::GetEdenPathString(Common::FS::EdenPath::LoadDir); + case DataDir::Shaders: + return Common::FS::GetEdenPathString(Common::FS::EdenPath::ShaderDir); + default: + UNIMPLEMENTED(); + } + + return ""; +} + +u64 ClearDir(DataDir dir) +{ + fs::path data_dir = GetDataDir(dir); + u64 result = fs::remove_all(data_dir); + + // mkpath at the end just so it actually exists + fs::create_directories(data_dir); + return result; +} + +const std::string ReadableBytesSize(u64 size) +{ + static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"}; + if (size == 0) { + return "0 B"; + } + + const int digit_groups = (std::min) (static_cast(std::log10(size) / std::log10(1024)), + static_cast(units.size())); + return fmt::format("{:.1f} {}", size / std::pow(1024, digit_groups), units[digit_groups]); +} + +u64 DataDirSize(DataDir dir) +{ + fs::path data_dir = GetDataDir(dir); + u64 size = 0; + + if (!fs::exists(data_dir)) + return 0; + + for (const auto& entry : fs::recursive_directory_iterator(data_dir)) { + if (!entry.is_directory()) { + size += entry.file_size(); + } + } + + return size; +} + +} diff --git a/src/frontend_common/data_manager.h b/src/frontend_common/data_manager.h new file mode 100644 index 0000000000..70ac2ca47e --- /dev/null +++ b/src/frontend_common/data_manager.h @@ -0,0 +1,21 @@ +#ifndef DATA_MANAGER_H +#define DATA_MANAGER_H + +#include "common/common_types.h" +#include + +namespace FrontendCommon::DataManager { + +enum class DataDir { Saves, UserNand, SysNand, Mods, Shaders }; + +const std::string GetDataDir(DataDir dir); + +u64 ClearDir(DataDir dir); + +const std::string ReadableBytesSize(u64 size); + +u64 DataDirSize(DataDir dir); + +}; // namespace FrontendCommon::DataManager + +#endif // DATA_MANAGER_H diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index aa931f113e..fe728e0377 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -4,9 +4,6 @@ # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -find_package(Qt6 REQUIRED COMPONENTS Core) -find_package(Qt6 REQUIRED COMPONENTS Core) - add_library(qt_common STATIC qt_common.h qt_common.cpp @@ -27,6 +24,7 @@ add_library(qt_common STATIC qt_rom_util.h qt_rom_util.cpp qt_applet_util.h qt_applet_util.cpp qt_progress_dialog.h qt_progress_dialog.cpp + qt_string_lookup.h ) @@ -40,6 +38,7 @@ endif() add_subdirectory(externals) target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip) +target_link_libraries(qt_common PUBLIC frozen::frozen) if (NOT APPLE AND ENABLE_OPENGL) target_compile_definitions(qt_common PUBLIC HAS_OPENGL) diff --git a/src/qt_common/externals/CMakeLists.txt b/src/qt_common/externals/CMakeLists.txt index e7b2e7b3e6..189a52c0a6 100644 --- a/src/qt_common/externals/CMakeLists.txt +++ b/src/qt_common/externals/CMakeLists.txt @@ -17,4 +17,4 @@ AddJsonPackage(quazip) # frozen # TODO(crueter): Qt String Lookup -# AddJsonPackage(frozen) +AddJsonPackage(frozen) diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp index 2f659cf1b2..9cbd1caf02 100644 --- a/src/qt_common/qt_content_util.cpp +++ b/src/qt_common/qt_content_util.cpp @@ -348,4 +348,29 @@ void FixProfiles() QtCommon::Game::OpenSaveFolder(); } +void ClearDataDir(FrontendCommon::DataManager::DataDir dir) { + auto result = QtCommon::Frontend::Warning("Really clear data?", + "Important data may be lost!", + QMessageBox::Yes | QMessageBox::No); + + if (result != QMessageBox::Yes) + return; + + result = QtCommon::Frontend::Warning( + "Are you REALLY sure?", + "Once deleted, your data will NOT come back!\n" + "Only do this if you're 100% sure you want to delete this data.", + QMessageBox::Yes | QMessageBox::No); + + if (result != QMessageBox::Yes) + return; + + QtCommon::Frontend::QtProgressDialog dialog(tr("Clearing..."), QString(), 0, 0); + dialog.show(); + + FrontendCommon::DataManager::ClearDir(dir); + + dialog.close(); +} + } // namespace QtCommon::Content diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h index b95e78c0a0..b2443829ab 100644 --- a/src/qt_common/qt_content_util.h +++ b/src/qt_common/qt_content_util.h @@ -6,6 +6,7 @@ #include #include "common/common_types.h" +#include "frontend_common/data_manager.h" namespace QtCommon::Content { @@ -46,6 +47,8 @@ void InstallKeys(); void VerifyGameContents(const std::string &game_path); void VerifyInstalledContents(); +void ClearDataDir(FrontendCommon::DataManager::DataDir dir); + // Profiles // void FixProfiles(); } diff --git a/src/qt_common/qt_string_lookup.h b/src/qt_common/qt_string_lookup.h new file mode 100644 index 0000000000..4898516b72 --- /dev/null +++ b/src/qt_common/qt_string_lookup.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace QtCommon::StringLookup { + +Q_NAMESPACE + +// TODO(crueter): QML interface +enum StringKey { + SavesTooltip, + ShadersTooltip, + UserNandTooltip, + SysNandTooltip, + ModsTooltip, +}; + +static constexpr const frozen::unordered_map strings = { + {SavesTooltip, "DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!"}, + {ShadersTooltip, "Shader pipeline caches. Generally safe to remove."}, + {UserNandTooltip, "Contains updates and DLC for games."}, + {SysNandTooltip, "Contains firmware and applet data."}, + {ModsTooltip, "Contains all of your mod data."}, +}; + +static inline const QString Lookup(StringKey key) +{ + return QString::fromStdString(strings.at(key).data()); +} + +} diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index c03f7a3abf..9663d12bf0 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -234,6 +234,8 @@ add_executable(yuzu deps_dialog.cpp deps_dialog.h deps_dialog.ui + + data_dialog.h data_dialog.cpp data_dialog.ui ) set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp new file mode 100644 index 0000000000..af2d858125 --- /dev/null +++ b/src/yuzu/data_dialog.cpp @@ -0,0 +1,104 @@ +#include "data_dialog.h" +#include "frontend_common/data_manager.h" +#include "qt_common/qt_content_util.h" +#include "qt_common/qt_frontend_util.h" +#include "qt_common/qt_progress_dialog.h" +#include "qt_common/qt_string_lookup.h" +#include "ui_data_dialog.h" + +#include +#include +#include +#include +#include +#include + +DataDialog::DataDialog(QWidget *parent) + : QDialog(parent) + , ui(std::make_unique()) +{ + ui->setupUi(this); + + std::size_t row = 0; +#define TABLE_ITEM(label, name, data_dir) \ + QTableWidgetItem *name##Label = new QTableWidgetItem(tr(label)); \ + name##Label->setToolTip( \ + QtCommon::StringLookup::Lookup(QtCommon::StringLookup::data_dir##Tooltip)); \ + ui->sizes->setItem(row, 0, name##Label); \ + DataItem *name##Item = new DataItem(FrontendCommon::DataManager::DataDir::data_dir, this); \ + ui->sizes->setItem(row, 1, name##Item); \ + ++row; + + TABLE_ITEM("Saves", save, Saves) + TABLE_ITEM("Shaders", shaders, Shaders) + TABLE_ITEM("UserNAND", user, UserNand) + TABLE_ITEM("SysNAND", sys, SysNand) + TABLE_ITEM("Mods", mods, Mods) + +#undef TABLE_ITEM + + QObject::connect(ui->sizes, &QTableWidget::customContextMenuRequested, this, [this]() { + auto items = ui->sizes->selectedItems(); + if (items.empty()) + return; + + QTableWidgetItem *selected = items.at(0); + DataItem *item = (DataItem *) ui->sizes->item(selected->row(), 1); + + QMenu *menu = new QMenu(this); + QAction *open = menu->addAction(tr("Open")); + QObject::connect(open, &QAction::triggered, this, [item]() { + auto data_dir + = item->data(DataItem::DATA_DIR).value(); + + QDesktopServices::openUrl(QUrl::fromLocalFile( + QString::fromStdString(FrontendCommon::DataManager::GetDataDir(data_dir)))); + }); + + QAction *clear = menu->addAction(tr("Clear")); + QObject::connect(clear, &QAction::triggered, this, [item]() { + auto data_dir + = item->data(DataItem::DATA_DIR).value(); + + QtCommon::Content::ClearDataDir(data_dir); + + item->scan(); + }); + + menu->exec(QCursor::pos()); + }); +} + +DataDialog::~DataDialog() = default; + +DataItem::DataItem(FrontendCommon::DataManager::DataDir data_dir, QWidget *parent) + : QTableWidgetItem(QObject::tr("Calculating")) + , m_parent(parent) + , m_dir(data_dir) +{ + setData(DataItem::DATA_DIR, QVariant::fromValue(m_dir)); + scan(); +} + +bool DataItem::operator<(const QTableWidgetItem &other) const +{ + return this->data(DataRole::SIZE).toULongLong() < other.data(DataRole::SIZE).toULongLong(); +} + +void DataItem::reset() { + setText(QStringLiteral("0 B")); + setData(DataItem::SIZE, QVariant::fromValue(0ULL)); +} + +void DataItem::scan() { + m_watcher = new QFutureWatcher(m_parent); + + m_parent->connect(m_watcher, &QFutureWatcher::finished, m_parent, [=, this]() { + u64 size = m_watcher->result(); + setText(QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size))); + setData(DataItem::SIZE, QVariant::fromValue(size)); + }); + + m_watcher->setFuture( + QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); })); +} diff --git a/src/yuzu/data_dialog.h b/src/yuzu/data_dialog.h new file mode 100644 index 0000000000..51bd0a4353 --- /dev/null +++ b/src/yuzu/data_dialog.h @@ -0,0 +1,43 @@ +#ifndef DATA_DIALOG_H +#define DATA_DIALOG_H + +#include +#include +#include +#include +#include "frontend_common/data_manager.h" +#include + +namespace Ui { +class DataDialog; +} + +class DataDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DataDialog(QWidget *parent = nullptr); + ~DataDialog(); + +private: + std::unique_ptr ui; +}; + +class DataItem : public QTableWidgetItem +{ +public: + DataItem(FrontendCommon::DataManager::DataDir data_dir, QWidget *parent); + enum DataRole { SIZE = Qt::UserRole + 1, DATA_DIR }; + + bool operator<(const QTableWidgetItem &other) const; + void reset(); + void scan(); + +private: + QWidget *m_parent; + QFutureWatcher *m_watcher = nullptr; + FrontendCommon::DataManager::DataDir m_dir; +}; + +#endif // DATA_DIALOG_H diff --git a/src/yuzu/data_dialog.ui b/src/yuzu/data_dialog.ui new file mode 100644 index 0000000000..d757a48bd6 --- /dev/null +++ b/src/yuzu/data_dialog.ui @@ -0,0 +1,152 @@ + + + DataDialog + + + + 0 + 0 + 300 + 350 + + + + + 0 + 0 + + + + + 300 + 320 + + + + Data Manager + + + + + + Right-click on an item to either open it or clear it. Hold your mouse over an item to see more information about it. + + + true + + + + + + + Qt::ContextMenuPolicy::CustomContextMenu + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + true + + + QAbstractItemView::SelectionMode::SingleSelection + + + true + + + false + + + 80 + + + true + + + false + + + + New Row + + + + + 0 + + + + + 1 + + + + + 2 + + + + + 4 + + + + + Directory + + + + + Size + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Ok + + + + + + + + + buttonBox + accepted() + DataDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DataDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 44ed29f141..b6dded447c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -156,6 +156,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/debugger/console.h" #include "yuzu/debugger/controller.h" #include "yuzu/debugger/wait_tree.h" +#include "yuzu/data_dialog.h" #include "yuzu/deps_dialog.h" #include "yuzu/discord.h" #include "yuzu/game_list.h" @@ -1705,6 +1706,7 @@ void GMainWindow::ConnectMenuEvents() { connect_menu(ui->action_Install_Keys, &GMainWindow::OnInstallDecryptionKeys); connect_menu(ui->action_About, &GMainWindow::OnAbout); connect_menu(ui->action_Eden_Dependencies, &GMainWindow::OnEdenDependencies); + connect_menu(ui->action_Data_Manager, &GMainWindow::OnDataDialog); } void GMainWindow::UpdateMenuState() { @@ -3934,6 +3936,11 @@ void GMainWindow::OnEdenDependencies() { depsDialog.exec(); } +void GMainWindow::OnDataDialog() { + DataDialog dataDialog(this); + dataDialog.exec(); +} + void GMainWindow::OnToggleFilterBar() { game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked()); if (ui->action_Show_Filter_Bar->isChecked()) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e3922759b0..a3e99c05fe 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -387,6 +387,7 @@ private slots: void OnInstallDecryptionKeys(); void OnAbout(); void OnEdenDependencies(); + void OnDataDialog(); void OnToggleFilterBar(); void OnToggleStatusBar(); void OnGameListRefresh(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 5f56c9e6d1..b7f9d3b1e3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -158,7 +158,7 @@ - &Amiibo + Am&iibo @@ -184,7 +184,7 @@ - Install Firmware + Install &Firmware @@ -192,6 +192,7 @@ + @@ -497,7 +498,7 @@ - Install Decryption Keys + Install Decryption &Keys @@ -593,6 +594,11 @@ &Eden Dependencies + + + &Data Manager + + From e8149b77ae5126e61cbc22163d6f379d7202979f Mon Sep 17 00:00:00 2001 From: crueter Date: Wed, 8 Oct 2025 16:12:11 -0400 Subject: [PATCH 2/3] headers Signed-off-by: crueter --- src/frontend_common/CMakeLists.txt | 3 +++ src/frontend_common/data_manager.cpp | 3 +++ src/frontend_common/data_manager.h | 3 +++ src/qt_common/qt_string_lookup.h | 3 +++ src/yuzu/data_dialog.cpp | 3 +++ src/yuzu/data_dialog.h | 3 +++ 6 files changed, 18 insertions(+) diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt index 83638476bf..aa44740596 100644 --- a/src/frontend_common/CMakeLists.txt +++ b/src/frontend_common/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/frontend_common/data_manager.cpp b/src/frontend_common/data_manager.cpp index 21e2422a42..4a63ed9e28 100644 --- a/src/frontend_common/data_manager.cpp +++ b/src/frontend_common/data_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #include "data_manager.h" #include "common/assert.h" #include "common/fs/path_util.h" diff --git a/src/frontend_common/data_manager.h b/src/frontend_common/data_manager.h index 70ac2ca47e..f6ae836c14 100644 --- a/src/frontend_common/data_manager.h +++ b/src/frontend_common/data_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #ifndef DATA_MANAGER_H #define DATA_MANAGER_H diff --git a/src/qt_common/qt_string_lookup.h b/src/qt_common/qt_string_lookup.h index 4898516b72..028d301f37 100644 --- a/src/qt_common/qt_string_lookup.h +++ b/src/qt_common/qt_string_lookup.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #include diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp index af2d858125..4e61a452bf 100644 --- a/src/yuzu/data_dialog.cpp +++ b/src/yuzu/data_dialog.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #include "data_dialog.h" #include "frontend_common/data_manager.h" #include "qt_common/qt_content_util.h" diff --git a/src/yuzu/data_dialog.h b/src/yuzu/data_dialog.h index 51bd0a4353..eab1def7d0 100644 --- a/src/yuzu/data_dialog.h +++ b/src/yuzu/data_dialog.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #ifndef DATA_DIALOG_H #define DATA_DIALOG_H From 1dec879ec859f3d6ad3d6e9db34fc9730ae42a14 Mon Sep 17 00:00:00 2001 From: crueter Date: Wed, 8 Oct 2025 22:45:33 -0400 Subject: [PATCH 3/3] data dialog rewrite Signed-off-by: crueter --- dist/qt_themes/colorful/icons/48x48/trash.png | Bin 0 -> 744 bytes dist/qt_themes/colorful/style.qrc | 1 + .../colorful_midnight_blue/style.qrc | 1 + dist/qt_themes/default/default.qrc | 1 + dist/qt_themes/default/icons/48x48/trash.png | Bin 0 -> 598 bytes dist/qt_themes/default_dark/style.qrc | 1 + .../qdarkstyle/icons/48x48/trash.png | Bin 0 -> 612 bytes dist/qt_themes/qdarkstyle/style.qrc | 1 + src/qt_common/qt_string_lookup.h | 6 +- src/yuzu/CMakeLists.txt | 1 + src/yuzu/data_dialog.cpp | 112 +++++------- src/yuzu/data_dialog.h | 23 +-- src/yuzu/data_dialog.ui | 162 +++++++++--------- src/yuzu/data_widget.ui | 147 ++++++++++++++++ src/yuzu/main.ui | 18 +- 15 files changed, 304 insertions(+), 170 deletions(-) create mode 100644 dist/qt_themes/colorful/icons/48x48/trash.png create mode 100644 dist/qt_themes/default/icons/48x48/trash.png create mode 100644 dist/qt_themes/qdarkstyle/icons/48x48/trash.png create mode 100644 src/yuzu/data_widget.ui diff --git a/dist/qt_themes/colorful/icons/48x48/trash.png b/dist/qt_themes/colorful/icons/48x48/trash.png new file mode 100644 index 0000000000000000000000000000000000000000..e60dce2e32474e1d2c1329c1b23e88ab72a230b0 GIT binary patch literal 744 zcmVP)K4zwta=qNYLJnvL~=zZzeul9h8^N!-lo&o%(p#V^@2K4E? zgLor65e4bjYYos%fom+lAXaFDZZGeCe(p7mhJ^P*77G6b=1pwC4%Tb86}>sHFw-DN zb7`mh`uKHX#?f0KkBSbJlP%!4K&*sR%|YRDa$=EK#+ZN@4PHQ0vjr?Rcma+6B49KM zUO*CM9upYbN$>(Cwb=rtqcgz^9F5P$GN$%I`|GtKaJtA)IT)SW=~n){;97~l78J&Z zRE4+%LN22z1a#2_kU1-*1v6h1s7;)-kfeTmK-vC=kqI}quOP>yt7t<^8 z3!MPpi%66QMGk*ui;;CM6kHjJV+w_Af$;W2;?A@%J%D2shD8#{Q$iZxhR(H(fD8dK z;cX|9$0^4wOb=w88oGOc@Xkn8#d+A^6qp!3&NfbdB1_A4-6?Rs*=ZsAcD9jnpuDNR z?>K+d%ztPikqw|TvryC1j>@u;^=76(wb|`4tgH!a3ABLGOyap20Uv;e3@i2OyUt$L a2Y&+S`sZ*slElaW0000icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png + icons/48x48/trash.png icons/48x48/list-add.png icons/48x48/no_avatar.png icons/48x48/sd_card.png diff --git a/dist/qt_themes/colorful_midnight_blue/style.qrc b/dist/qt_themes/colorful_midnight_blue/style.qrc index b9821c6722..2aee8ea1e1 100644 --- a/dist/qt_themes/colorful_midnight_blue/style.qrc +++ b/dist/qt_themes/colorful_midnight_blue/style.qrc @@ -11,6 +11,7 @@ SPDX-License-Identifier: GPL-2.0-or-later ../colorful/icons/48x48/bad_folder.png ../colorful/icons/48x48/chip.png ../colorful/icons/48x48/folder.png + ../colorful/icons/48x48/trash.png ../colorful/icons/48x48/list-add.png ../colorful/icons/48x48/sd_card.png ../colorful/icons/256x256/plus_folder.png diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc index 0d8af20fc1..45a91ef2dc 100644 --- a/dist/qt_themes/default/default.qrc +++ b/dist/qt_themes/default/default.qrc @@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-2.0-or-later icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png + icons/48x48/trash.png icons/48x48/list-add.png icons/48x48/sd_card.png icons/48x48/star.png diff --git a/dist/qt_themes/default/icons/48x48/trash.png b/dist/qt_themes/default/icons/48x48/trash.png new file mode 100644 index 0000000000000000000000000000000000000000..547b821de9a1e41539301d294df6a3c5e086f08c GIT binary patch literal 598 zcmV-c0;&CpP)L4(R?sUe{w*)6D6 zgrk^|Fz(LBoonxid6HAy*>h&zclMt7xPz)<6F?16M^UrYA)%N|h=>5!z;NbbGdIAn zs=j9}?65rG0`9~E3;^>sg|-G(EbE-_Q&bfZnF1z&&J;D>zy=^T2aG|({l28geFIKa z^@y;@RWr6b)&*z){i)AWv1oJvx4u>6;09m_I0TybpAu=K2^;`}R`-D-VS5=Z45b0z zRK5;Gqza5jaxPT$pnZ}u>WMsIZu7I_w{4#k^F|sye|`#;27HstfCOxb$YU6r;meMb z{8Z#=VqnRTzvwXk*PA$ciG$$1!c~4Ec@3EU{Pb8NC4PIQ= z!f>9ShJWA0-iaD`Hsp-9Lr4q84BMW*4x&A9WhkhM$UblaygCw=L>Dj#%ou*V{5Fa# zB=rFI`2PmhD(-;pZ<-COVO4^U&A!D(;{}-hp|{17-W>2)0MVcE0L&V-#;i;B0jt0< kQ1>9Z9v*uY=xwF*1F&%4fCiBSb^rhX07*qoM6N<$f`QBlCjbBd literal 0 HcmV?d00001 diff --git a/dist/qt_themes/default_dark/style.qrc b/dist/qt_themes/default_dark/style.qrc index 7de4737c2c..ac6c9fe4e9 100644 --- a/dist/qt_themes/default_dark/style.qrc +++ b/dist/qt_themes/default_dark/style.qrc @@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later ../colorful/icons/48x48/bad_folder.png ../colorful/icons/48x48/chip.png ../colorful/icons/48x48/folder.png + ../colorful/icons/48x48/trash.png ../qdarkstyle/icons/48x48/no_avatar.png ../colorful/icons/48x48/list-add.png ../colorful/icons/48x48/sd_card.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/trash.png b/dist/qt_themes/qdarkstyle/icons/48x48/trash.png new file mode 100644 index 0000000000000000000000000000000000000000..e59fe85c033c483b0c0aefc2fd950d17ebb9f56a GIT binary patch literal 612 zcmV-q0-ODbP)gSZtKA?qxQ!n@r^P3scPOe?eNUMeT`)qqvY#G1b7$#Col_`KJq=!x`+UQD;9^6y2yz!m!<~ z8nHj|63=@fgcU5eEM`_L_d*B*9LlXEkwG6HbE{~;MjwlKs$Sol7q;*OS5w~ub3^Jh zIyZC%ann|y$WEyhP_ zY9QdwFij&q%B~;8*(n?NS`l+{7D9I5R7KgB(M)*^ysN0NfX8@&AMNOhvJYo*zT(;2 z(MR_9$ke1Yug!}kb74#cDexCc#v__Z!0000icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png + icons/48x48/trash.png icons/48x48/no_avatar.png icons/48x48/list-add.png icons/48x48/sd_card.png diff --git a/src/qt_common/qt_string_lookup.h b/src/qt_common/qt_string_lookup.h index 028d301f37..de6acac8a1 100644 --- a/src/qt_common/qt_string_lookup.h +++ b/src/qt_common/qt_string_lookup.h @@ -23,11 +23,11 @@ enum StringKey { }; static constexpr const frozen::unordered_map strings = { - {SavesTooltip, "DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!"}, - {ShadersTooltip, "Shader pipeline caches. Generally safe to remove."}, + {SavesTooltip, "Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!"}, + {ShadersTooltip, "Contains Vulkan and OpenGL pipeline caches. Generally safe to remove."}, {UserNandTooltip, "Contains updates and DLC for games."}, {SysNandTooltip, "Contains firmware and applet data."}, - {ModsTooltip, "Contains all of your mod data."}, + {ModsTooltip, "Contains game mods, patches, and cheats."}, }; static inline const QString Lookup(StringKey key) diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 9663d12bf0..f4669d0914 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -236,6 +236,7 @@ add_executable(yuzu deps_dialog.ui data_dialog.h data_dialog.cpp data_dialog.ui + data_widget.ui ) set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp index 4e61a452bf..87d81e4f43 100644 --- a/src/yuzu/data_dialog.cpp +++ b/src/yuzu/data_dialog.cpp @@ -4,17 +4,12 @@ #include "data_dialog.h" #include "frontend_common/data_manager.h" #include "qt_common/qt_content_util.h" -#include "qt_common/qt_frontend_util.h" -#include "qt_common/qt_progress_dialog.h" #include "qt_common/qt_string_lookup.h" #include "ui_data_dialog.h" #include #include -#include -#include -#include -#include +#include DataDialog::DataDialog(QWidget *parent) : QDialog(parent) @@ -22,86 +17,69 @@ DataDialog::DataDialog(QWidget *parent) { ui->setupUi(this); - std::size_t row = 0; -#define TABLE_ITEM(label, name, data_dir) \ - QTableWidgetItem *name##Label = new QTableWidgetItem(tr(label)); \ - name##Label->setToolTip( \ - QtCommon::StringLookup::Lookup(QtCommon::StringLookup::data_dir##Tooltip)); \ - ui->sizes->setItem(row, 0, name##Label); \ - DataItem *name##Item = new DataItem(FrontendCommon::DataManager::DataDir::data_dir, this); \ - ui->sizes->setItem(row, 1, name##Item); \ - ++row; + // TODO: Should we make this a single widget that pulls data from a model? +#define WIDGET(name) \ + ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \ + QtCommon::StringLookup::name##Tooltip, \ + this)); - TABLE_ITEM("Saves", save, Saves) - TABLE_ITEM("Shaders", shaders, Shaders) - TABLE_ITEM("UserNAND", user, UserNand) - TABLE_ITEM("SysNAND", sys, SysNand) - TABLE_ITEM("Mods", mods, Mods) + WIDGET(Saves) + WIDGET(Shaders) + WIDGET(UserNand) + WIDGET(SysNand) + WIDGET(Mods) -#undef TABLE_ITEM +#undef WIDGET - QObject::connect(ui->sizes, &QTableWidget::customContextMenuRequested, this, [this]() { - auto items = ui->sizes->selectedItems(); - if (items.empty()) - return; - - QTableWidgetItem *selected = items.at(0); - DataItem *item = (DataItem *) ui->sizes->item(selected->row(), 1); - - QMenu *menu = new QMenu(this); - QAction *open = menu->addAction(tr("Open")); - QObject::connect(open, &QAction::triggered, this, [item]() { - auto data_dir - = item->data(DataItem::DATA_DIR).value(); - - QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(FrontendCommon::DataManager::GetDataDir(data_dir)))); - }); - - QAction *clear = menu->addAction(tr("Clear")); - QObject::connect(clear, &QAction::triggered, this, [item]() { - auto data_dir - = item->data(DataItem::DATA_DIR).value(); - - QtCommon::Content::ClearDataDir(data_dir); - - item->scan(); - }); - - menu->exec(QCursor::pos()); + connect(ui->labels, &QListWidget::itemSelectionChanged, this, [this]() { + ui->page->setCurrentIndex(ui->labels->currentRow()); }); } DataDialog::~DataDialog() = default; -DataItem::DataItem(FrontendCommon::DataManager::DataDir data_dir, QWidget *parent) - : QTableWidgetItem(QObject::tr("Calculating")) - , m_parent(parent) +DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, + QtCommon::StringLookup::StringKey tooltip, + QWidget *parent) + : QWidget(parent) + , ui(std::make_unique()) , m_dir(data_dir) { - setData(DataItem::DATA_DIR, QVariant::fromValue(m_dir)); + ui->setupUi(this); + + ui->tooltip->setText(QtCommon::StringLookup::Lookup(tooltip)); + + ui->clear->setIcon(QIcon::fromTheme(QStringLiteral("trash"))); + ui->open->setIcon(QIcon::fromTheme(QStringLiteral("folder"))); + + connect(ui->clear, &QPushButton::clicked, this, &DataWidget::clear); + connect(ui->open, &QPushButton::clicked, this, &DataWidget::open); + scan(); } -bool DataItem::operator<(const QTableWidgetItem &other) const -{ - return this->data(DataRole::SIZE).toULongLong() < other.data(DataRole::SIZE).toULongLong(); +void DataWidget::clear() { + QtCommon::Content::ClearDataDir(m_dir); + scan(); } -void DataItem::reset() { - setText(QStringLiteral("0 B")); - setData(DataItem::SIZE, QVariant::fromValue(0ULL)); +void DataWidget::open() { + QDesktopServices::openUrl(QUrl::fromLocalFile( + QString::fromStdString(FrontendCommon::DataManager::GetDataDir(m_dir)))); } -void DataItem::scan() { - m_watcher = new QFutureWatcher(m_parent); +void DataWidget::scan() { + ui->size->setText(tr("Calculating...")); - m_parent->connect(m_watcher, &QFutureWatcher::finished, m_parent, [=, this]() { - u64 size = m_watcher->result(); - setText(QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size))); - setData(DataItem::SIZE, QVariant::fromValue(size)); + QFutureWatcher *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcher::finished, this, [=, this]() { + u64 size = watcher->result(); + ui->size->setText( + QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size))); + watcher->deleteLater(); }); - m_watcher->setFuture( + watcher->setFuture( QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); })); } diff --git a/src/yuzu/data_dialog.h b/src/yuzu/data_dialog.h index eab1def7d0..9f367d6049 100644 --- a/src/yuzu/data_dialog.h +++ b/src/yuzu/data_dialog.h @@ -5,11 +5,10 @@ #define DATA_DIALOG_H #include -#include -#include -#include #include "frontend_common/data_manager.h" -#include +#include "qt_common/qt_string_lookup.h" + +#include "ui_data_widget.h" namespace Ui { class DataDialog; @@ -27,19 +26,21 @@ private: std::unique_ptr ui; }; -class DataItem : public QTableWidgetItem +class DataWidget : public QWidget { + Q_OBJECT public: - DataItem(FrontendCommon::DataManager::DataDir data_dir, QWidget *parent); - enum DataRole { SIZE = Qt::UserRole + 1, DATA_DIR }; + explicit DataWidget(FrontendCommon::DataManager::DataDir data_dir, + QtCommon::StringLookup::StringKey tooltip, + QWidget *parent = nullptr); - bool operator<(const QTableWidgetItem &other) const; - void reset(); +public slots: + void clear(); + void open(); void scan(); private: - QWidget *m_parent; - QFutureWatcher *m_watcher = nullptr; + std::unique_ptr ui; FrontendCommon::DataManager::DataDir m_dir; }; diff --git a/src/yuzu/data_dialog.ui b/src/yuzu/data_dialog.ui index d757a48bd6..06751e2fb1 100644 --- a/src/yuzu/data_dialog.ui +++ b/src/yuzu/data_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 300 - 350 + 480 + 320 @@ -27,90 +27,86 @@ - - - Right-click on an item to either open it or clear it. Hold your mouse over an item to see more information about it. - - - true - - + + + + + + 0 + 0 + + + + + Saves + + + + + Shaders + + + + + UserNAND + + + + + SysNAND + + + + + Mods + + + + + + + + + 0 + 0 + + + + + 275 + 200 + + + + -1 + + + + - - - Qt::ContextMenuPolicy::CustomContextMenu + + + 10 - - QAbstractItemView::EditTrigger::NoEditTriggers - - - true - - - QAbstractItemView::SelectionMode::SingleSelection - - - true - - - false - - - 80 - - - true - - - false - - - - New Row - - - - - 0 - - - - - 1 - - - - - 2 - - - - - 4 - - - - - Directory - - - - - Size - - - - - - - - Qt::Orientation::Horizontal - - - QDialogButtonBox::StandardButton::Ok - - + + + + Deleting ANY data is IRREVERSABLE! + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Ok + + + + diff --git a/src/yuzu/data_widget.ui b/src/yuzu/data_widget.ui new file mode 100644 index 0000000000..ed67078fa1 --- /dev/null +++ b/src/yuzu/data_widget.ui @@ -0,0 +1,147 @@ + + + DataWidget + + + + 0 + 0 + 275 + 200 + + + + Form + + + + + + + + Tooltip + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + + 10 + true + + + + Size + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + + + QLayout::SizeConstraint::SetFixedSize + + + 25 + + + + + + 1 + 1 + + + + + 52 + 42 + + + + Open with your system file manager + + + QPushButton { + border-style: solid; + border-width:1px; + border-radius:25px; + border-color: transparent; + max-width:50px; + max-height:40px; + min-width:50px; + min-height:40px; +} + + + + + + + 24 + 24 + + + + + + + + + 1 + 1 + + + + + 52 + 42 + + + + Delete all data in this directory. THIS IS 100% IRREVERSABLE! + + + QPushButton { + border-style: solid; + border-width:1px; + border-radius:25px; + border-color: transparent; + max-width:50px; + max-height:40px; + min-width:50px; + min-height:40px; +} + + + + + + + 24 + 24 + + + + + + + + + + + diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index b7f9d3b1e3..12ff4efdf1 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -165,6 +165,16 @@ + + + &Applets + + + + + + + &TAS @@ -195,11 +205,7 @@ - - - - - + @@ -503,7 +509,7 @@ - Open Home Menu + Open &Home Menu