From 9a3825c62257c8f3d44b0994cb33c6d1944b886e Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Sun, 18 May 2025 16:27:54 -0700 Subject: [PATCH] InfoWidget: Add Time Played display and editing Add a section showing the game's time played and allowing the user to edit the values via the QSpinBoxes displaying them. --- Source/Core/Core/TimePlayed.cpp | 27 ++++++- Source/Core/Core/TimePlayed.h | 4 + Source/Core/DolphinQt/Config/InfoWidget.cpp | 90 ++++++++++++++++++++- Source/Core/DolphinQt/Config/InfoWidget.h | 8 ++ 4 files changed, 124 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/TimePlayed.cpp b/Source/Core/Core/TimePlayed.cpp index 223b9390c9..4906f4f35f 100644 --- a/Source/Core/Core/TimePlayed.cpp +++ b/Source/Core/Core/TimePlayed.cpp @@ -13,6 +13,9 @@ #include "Common/IniFile.h" #include "Common/NandPaths.h" +static constexpr std::chrono::milliseconds MAX_TIME_PLAYED = + std::chrono::hours(TimePlayedManager::MAX_HOURS) + std::chrono::minutes(59); + TimePlayedManager::TimePlayedManager() : m_ini_path(File::GetUserPath(D_CONFIG_IDX) + "TimePlayed.ini") { @@ -33,18 +36,34 @@ void TimePlayedManager::AddTime(const std::string& game_id, { const std::string filtered_game_id = Common::EscapeFileName(game_id); u64 previous_time; - u64 new_time; + std::chrono::milliseconds capped_new_time; { std::lock_guard guard(m_mutex); m_time_list->Get(filtered_game_id, &previous_time); - new_time = previous_time + static_cast(time_emulated.count()); - m_time_list->Set(filtered_game_id, new_time); + const auto new_time = std::chrono::milliseconds(previous_time) + time_emulated; + capped_new_time = std::min(MAX_TIME_PLAYED, new_time); + m_time_list->Set(filtered_game_id, static_cast(capped_new_time.count())); m_ini.Save(m_ini_path); } - UpdateEvent::Trigger(filtered_game_id, static_cast(new_time)); + UpdateEvent::Trigger(filtered_game_id, capped_new_time); +} + +void TimePlayedManager::SetTimePlayed(const std::string& game_id, + const std::chrono::milliseconds time_played) +{ + const std::string filtered_game_id = Common::EscapeFileName(game_id); + const std::chrono::milliseconds capped_time_played = std::min(MAX_TIME_PLAYED, time_played); + + { + std::lock_guard guard(m_mutex); + m_time_list->Set(filtered_game_id, static_cast(capped_time_played.count())); + m_ini.Save(m_ini_path); + } + + UpdateEvent::Trigger(filtered_game_id, capped_time_played); } std::chrono::milliseconds TimePlayedManager::GetTimePlayed(const std::string& game_id) const diff --git a/Source/Core/Core/TimePlayed.h b/Source/Core/Core/TimePlayed.h index 2c629fc31b..8ae9300330 100644 --- a/Source/Core/Core/TimePlayed.h +++ b/Source/Core/Core/TimePlayed.h @@ -25,11 +25,15 @@ public: void AddTime(const std::string& game_id, std::chrono::milliseconds time_emulated); + void SetTimePlayed(const std::string& game_id, std::chrono::milliseconds time_played); + std::chrono::milliseconds GetTimePlayed(const std::string& game_id) const; using UpdateEvent = Common::HookableEvent<"Time Played Update", const std::string&, std::chrono::milliseconds>; + static constexpr int MAX_HOURS = 999'999; + private: TimePlayedManager(); diff --git a/Source/Core/DolphinQt/Config/InfoWidget.cpp b/Source/Core/DolphinQt/Config/InfoWidget.cpp index 81371f0caf..0ed0b52b44 100644 --- a/Source/Core/DolphinQt/Config/InfoWidget.cpp +++ b/Source/Core/DolphinQt/Config/InfoWidget.cpp @@ -3,17 +3,25 @@ #include "DolphinQt/Config/InfoWidget.h" +#include + #include #include #include #include #include +#include #include #include #include +#include +#include #include +#include "Common/HookableEvent.h" + #include "Core/ConfigManager.h" +#include "Core/TimePlayed.h" #include "DiscIO/Blob.h" #include "DiscIO/Enums.h" @@ -22,6 +30,7 @@ #include "DolphinQt/QtUtils/DolphinFileDialog.h" #include "DolphinQt/QtUtils/ImageConverter.h" +#include "DolphinQt/QtUtils/QueueOnObject.h" #include "UICommon/UICommon.h" @@ -37,10 +46,17 @@ InfoWidget::InfoWidget(const UICommon::GameFile& game) : m_game(game) if (!game.GetLanguages().empty()) layout->addWidget(CreateBannerDetails()); + layout->addWidget(CreateTimePlayedDetails()); + + layout->addStretch(1); + setLayout(layout); } -InfoWidget::~InfoWidget() = default; +InfoWidget::~InfoWidget() +{ + m_time_played_update_event.reset(); +} QGroupBox* InfoWidget::CreateFileDetails() { @@ -181,6 +197,78 @@ QGroupBox* InfoWidget::CreateBannerDetails() return group; } +QGroupBox* InfoWidget::CreateTimePlayedDetails() +{ + const auto set_time_played = [this]() { + const int hours = m_hours_played->text().toInt(); + const int minutes = m_minutes_played->text().toInt(); + const std::chrono::milliseconds time_played = + std::chrono::hours(hours) + std::chrono::minutes(minutes); + TimePlayedManager::GetInstance().SetTimePlayed(m_game.GetGameID(), time_played); + }; + + auto* const time_played_label = new QLabel(tr("Time Played:")); + + m_hours_played = new QSpinBox; + m_hours_played->setRange(0, TimePlayedManager::MAX_HOURS); + connect(m_hours_played, &QSpinBox::valueChanged, this, set_time_played); + + m_minutes_played = new QSpinBox; + m_minutes_played->setRange(0, 59); + connect(m_minutes_played, &QSpinBox::valueChanged, this, set_time_played); + + auto* const hours_label = new QLabel(tr("Hours")); + hours_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + auto* const minutes_label = new QLabel(tr("Minutes")); + minutes_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + auto* const time_played_layout = new QHBoxLayout; + time_played_layout->addWidget(m_hours_played); + time_played_layout->addWidget(hours_label); + time_played_layout->addWidget(m_minutes_played); + time_played_layout->addWidget(minutes_label); + + auto* const layout = new QFormLayout; + layout->addRow(time_played_label, time_played_layout); + + const auto update_time_played = [this](const std::string& game_id, + const std::chrono::milliseconds time_played) { + if (game_id != m_game.GetGameID()) + return; + + using namespace std::chrono_literals; + + const auto hours_played = time_played / 1h; + const auto minutes_played = (time_played % 60min) / 1min; + + QSignalBlocker hours_blocker(m_hours_played); + QSignalBlocker minutes_blocker(m_minutes_played); + + m_hours_played->setValue(hours_played); + m_minutes_played->setValue(minutes_played); + }; + + const std::chrono::milliseconds time_played = + TimePlayedManager::GetInstance().GetTimePlayed(m_game.GetGameID()); + update_time_played(m_game.GetGameID(), time_played); + + const auto update_time_played_on_host_thread = + [this, update_time_played](const std::string& game_id, + const std::chrono::milliseconds time_played) { + QueueOnObject(this, [update_time_played, game_id, time_played]() { + update_time_played(game_id, time_played); + }); + }; + m_time_played_update_event = + TimePlayedManager::UpdateEvent::Register(update_time_played_on_host_thread, "InfoWidget"); + + auto* const group_box = new QGroupBox(tr("Time Played Details")); + group_box->setLayout(layout); + layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + return group_box; +} + QWidget* InfoWidget::CreateBannerGraphic(const QPixmap& image) { QWidget* widget = new QWidget(); diff --git a/Source/Core/DolphinQt/Config/InfoWidget.h b/Source/Core/DolphinQt/Config/InfoWidget.h index c9493f1276..1905703d8e 100644 --- a/Source/Core/DolphinQt/Config/InfoWidget.h +++ b/Source/Core/DolphinQt/Config/InfoWidget.h @@ -8,6 +8,8 @@ #include +#include "Common/HookableEvent.h" + #include "UICommon/GameFile.h" namespace DiscIO @@ -19,6 +21,7 @@ class QComboBox; class QGroupBox; class QLineEdit; class QPixmap; +class QSpinBox; class QTextEdit; class InfoWidget final : public QWidget @@ -35,6 +38,7 @@ private: QGroupBox* CreateFileDetails(); QGroupBox* CreateGameDetails(); QGroupBox* CreateBannerDetails(); + QGroupBox* CreateTimePlayedDetails(); QLineEdit* CreateValueDisplay(const QString& value); QLineEdit* CreateValueDisplay(const std::string& value = ""); void CreateLanguageSelector(); @@ -46,4 +50,8 @@ private: QLineEdit* m_name = {}; QLineEdit* m_maker = {}; QTextEdit* m_description = {}; + QSpinBox* m_hours_played = {}; + QSpinBox* m_minutes_played = {}; + + Common::EventHook m_time_played_update_event; };