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.
This commit is contained in:
Dentomologist 2025-05-18 16:27:54 -07:00
parent f14ac36ac8
commit 9a3825c622
4 changed files with 124 additions and 5 deletions

View File

@ -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<u64>(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<u64>(capped_new_time.count()));
m_ini.Save(m_ini_path);
}
UpdateEvent::Trigger(filtered_game_id, static_cast<std::chrono::milliseconds>(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<u64>(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

View File

@ -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();

View File

@ -3,17 +3,25 @@
#include "DolphinQt/Config/InfoWidget.h"
#include <chrono>
#include <QComboBox>
#include <QCryptographicHash>
#include <QDir>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QString>
#include <QTextEdit>
#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();

View File

@ -8,6 +8,8 @@
#include <QWidget>
#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;
};