Merge pull request #140 from sudachi-emu/feature-total-times-played
Added total times played support
This commit is contained in:
commit
39f1a62c8d
|
@ -102,7 +102,7 @@ void PushInShowController(Core::System& system, AppletStorageChannel& channel) {
|
|||
// ShowControllerFirmwareUpdateForSystem/ShowControllerKeyRemappingForSystem,
|
||||
// which sets this to the input param
|
||||
.style_set = Core::HID::NpadStyleSet::None,
|
||||
.joy_hold_type = 0,
|
||||
.joy_hold_type = Frontend::NpadJoyDeviceHoldType::Vertical,
|
||||
};
|
||||
std::vector<u8> common_args_data(sizeof(common_args));
|
||||
std::vector<u8> private_args_data(sizeof(private_args));
|
||||
|
|
|
@ -53,6 +53,8 @@ enum class ControllerSupportResult : u32 {
|
|||
Cancel = 2,
|
||||
};
|
||||
|
||||
enum class NpadJoyDeviceHoldType : u32 { Vertical = 0, Horizontal = 1 };
|
||||
|
||||
struct ControllerSupportArgPrivate {
|
||||
u32 arg_private_size{};
|
||||
u32 arg_size{};
|
||||
|
@ -61,7 +63,7 @@ struct ControllerSupportArgPrivate {
|
|||
ControllerSupportMode mode{};
|
||||
ControllerSupportCaller caller{};
|
||||
Core::HID::NpadStyleSet style_set{};
|
||||
u32 joy_hold_type{};
|
||||
NpadJoyDeviceHoldType joy_hold_type{};
|
||||
};
|
||||
static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,
|
||||
"ControllerSupportArgPrivate has incorrect size.");
|
||||
|
|
|
@ -125,6 +125,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
|
|||
connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->show_play_time, &QCheckBox::stateChanged, this,
|
||||
&ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->show_total_times, &QCheckBox::stateChanged, this,
|
||||
&ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
|
@ -170,6 +172,7 @@ void ConfigureUi::ApplyConfiguration() {
|
|||
UISettings::values.show_size = ui->show_size->isChecked();
|
||||
UISettings::values.show_types = ui->show_types->isChecked();
|
||||
UISettings::values.show_play_time = ui->show_play_time->isChecked();
|
||||
UISettings::values.show_total_times = ui->show_total_times->isChecked();
|
||||
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
|
||||
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
|
||||
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
|
||||
|
@ -200,6 +203,7 @@ void ConfigureUi::SetConfiguration() {
|
|||
ui->show_size->setChecked(UISettings::values.show_size.GetValue());
|
||||
ui->show_types->setChecked(UISettings::values.show_types.GetValue());
|
||||
ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
|
||||
ui->show_total_times->setChecked(UISettings::values.show_total_times.GetValue());
|
||||
ui->game_icon_size_combobox->setCurrentIndex(
|
||||
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
|
||||
ui->folder_icon_size_combobox->setCurrentIndex(
|
||||
|
@ -262,7 +266,7 @@ void ConfigureUi::InitializeLanguageComboBox() {
|
|||
#else
|
||||
const QString country = QLocale::countryToString(QLocale(locale).country());
|
||||
ui->language_combobox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// Unlike other configuration changes, interface language changes need to be reflected on the
|
||||
|
|
|
@ -111,6 +111,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="show_total_times">
|
||||
<property name="text">
|
||||
<string>Show Total Times Column</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
|
||||
<item>
|
||||
|
|
|
@ -344,6 +344,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
|
|||
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
|
||||
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
|
||||
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
|
||||
tree_view->setColumnHidden(COLUMN_TOTAL_TIMES, !UISettings::values.show_total_times);
|
||||
item_model->setSortRole(GameListItemPath::SortRole);
|
||||
|
||||
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
|
||||
|
@ -800,6 +801,7 @@ void GameList::RetranslateUI() {
|
|||
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
|
||||
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
|
||||
item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
|
||||
item_model->setHeaderData(COLUMN_TOTAL_TIMES, Qt::Horizontal, tr("Total times"));
|
||||
}
|
||||
|
||||
void GameListSearchField::changeEvent(QEvent* event) {
|
||||
|
@ -828,6 +830,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
|||
tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
|
||||
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
|
||||
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
|
||||
tree_view->setColumnHidden(COLUMN_TOTAL_TIMES, !UISettings::values.show_total_times);
|
||||
|
||||
// Cancel any existing worker.
|
||||
current_worker.reset();
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "uisettings.h"
|
||||
#include "sudachi/compatibility_list.h"
|
||||
#include "sudachi/play_time_manager.h"
|
||||
#include "uisettings.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
|
@ -77,6 +77,7 @@ public:
|
|||
COLUMN_FILE_TYPE,
|
||||
COLUMN_SIZE,
|
||||
COLUMN_PLAY_TIME,
|
||||
COLUMN_TOTAL_TIMES,
|
||||
COLUMN_COUNT, // Number of columns
|
||||
};
|
||||
|
||||
|
|
|
@ -247,6 +247,31 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* GameListItem for Play Time values.
|
||||
* This object stores the play time of a game in seconds, and its readable
|
||||
* representation in minutes/hours
|
||||
*/
|
||||
class GameListItemTotalTimes : public GameListItem {
|
||||
public:
|
||||
static constexpr int TotalTimesRole = SortRole;
|
||||
|
||||
GameListItemTotalTimes() = default;
|
||||
explicit GameListItemTotalTimes(const qulonglong times_count) {
|
||||
setData(times_count, TotalTimesRole);
|
||||
}
|
||||
|
||||
void setData(const QVariant& value, int role) override {
|
||||
qulonglong times_count = value.toULongLong();
|
||||
GameListItem::setData(PlayTime::ReadableTotalTimes(times_count), Qt::DisplayRole);
|
||||
GameListItem::setData(value, TotalTimesRole);
|
||||
}
|
||||
|
||||
bool operator<(const QStandardItem& other) const override {
|
||||
return data(TotalTimesRole).toULongLong() < other.data(TotalTimesRole).toULongLong();
|
||||
}
|
||||
};
|
||||
|
||||
class GameListDir : public GameListItem {
|
||||
public:
|
||||
static constexpr int GameDirRole = Qt::UserRole + 2;
|
||||
|
|
|
@ -215,7 +215,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
|
|||
new GameListItem(file_type_string),
|
||||
new GameListItemSize(size),
|
||||
new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
|
||||
};
|
||||
new GameListItemTotalTimes(play_time_manager.GetTotalTimes(program_id))};
|
||||
|
||||
const auto patch_versions = GetGameListCachedObject(
|
||||
fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
|
||||
|
|
|
@ -18,6 +18,7 @@ void PlayTimeManager::SetProgramId(u64 program_id) {
|
|||
|
||||
inline void PlayTimeManager::UpdateTimestamp() {
|
||||
this->last_timestamp = std::chrono::steady_clock::now();
|
||||
this->last_total_times = GetTotalTimes(this->running_program_id);
|
||||
}
|
||||
|
||||
void PlayTimeManager::Start() {
|
||||
|
@ -50,12 +51,12 @@ void PlayTimeManager::Save() {
|
|||
std::chrono::steady_clock::duration(now - this->last_timestamp))
|
||||
.count());
|
||||
UpdateTimestamp();
|
||||
if (!UpdatePlayTime(running_program_id, duration)) {
|
||||
if (!UpdatePlayTime(running_program_id, duration, 1)) {
|
||||
LOG_ERROR(Common, "Failed to update play time");
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdatePlayTime(u64 program_id, u64 add_play_time) {
|
||||
bool UpdatePlayTime(u64 program_id, u64 add_play_time, u64 add_total_times) {
|
||||
std::vector<PlayTimeElement> play_time_elements;
|
||||
if (!ReadPlayTimeFile(play_time_elements)) {
|
||||
return false;
|
||||
|
@ -63,9 +64,11 @@ bool UpdatePlayTime(u64 program_id, u64 add_play_time) {
|
|||
const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id);
|
||||
|
||||
if (it == play_time_elements.end()) {
|
||||
play_time_elements.push_back({.program_id = program_id, .play_time = add_play_time});
|
||||
play_time_elements.push_back(
|
||||
{.program_id = program_id, .play_time = add_play_time, .total_times = add_total_times});
|
||||
} else {
|
||||
play_time_elements.at(it - play_time_elements.begin()).play_time += add_play_time;
|
||||
play_time_elements.at(it - play_time_elements.begin()).total_times += add_total_times;
|
||||
}
|
||||
if (!WritePlayTimeFile(play_time_elements)) {
|
||||
return false;
|
||||
|
@ -86,6 +89,19 @@ u64 GetPlayTime(u64 program_id) {
|
|||
return play_time_elements.at(it - play_time_elements.begin()).play_time;
|
||||
}
|
||||
|
||||
u64 GetTotalTimes(u64 program_id) {
|
||||
std::vector<PlayTimeElement> play_time_elements;
|
||||
|
||||
if (!ReadPlayTimeFile(play_time_elements)) {
|
||||
return 0;
|
||||
}
|
||||
const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id);
|
||||
if (it == play_time_elements.end()) {
|
||||
return 0;
|
||||
}
|
||||
return play_time_elements.at(it - play_time_elements.begin()).total_times;
|
||||
}
|
||||
|
||||
bool PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
|
||||
std::vector<PlayTimeElement> play_time_elements;
|
||||
|
||||
|
@ -174,4 +190,8 @@ QString ReadablePlayTime(qulonglong time_seconds) {
|
|||
.arg(QString::fromUtf8(units[unit]));
|
||||
}
|
||||
|
||||
QString ReadableTotalTimes(qulonglong times_count) {
|
||||
return QStringLiteral("%L1").arg((double)times_count, 0, 'f', 0);
|
||||
}
|
||||
|
||||
} // namespace PlayTime
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace PlayTime {
|
|||
struct PlayTimeElement {
|
||||
u64 program_id;
|
||||
u64 play_time;
|
||||
u64 total_times;
|
||||
|
||||
inline bool operator==(const PlayTimeElement& other) const {
|
||||
return program_id == other.program_id;
|
||||
|
@ -49,6 +50,7 @@ public:
|
|||
private:
|
||||
u64 running_program_id;
|
||||
std::chrono::steady_clock::time_point last_timestamp;
|
||||
u64 last_total_times;
|
||||
std::jthread play_time_thread;
|
||||
void AutoTimestamp(std::stop_token stop_token);
|
||||
void Save();
|
||||
|
@ -56,13 +58,15 @@ private:
|
|||
|
||||
std::optional<std::filesystem::path> GetCurrentUserPlayTimePath();
|
||||
|
||||
bool UpdatePlayTime(u64 program_id, u64 add_play_time);
|
||||
bool UpdatePlayTime(u64 program_id, u64 add_play_time, u64 add_total_times);
|
||||
|
||||
[[nodiscard]] bool ReadPlayTimeFile(std::vector<PlayTimeElement>& out_play_time_elements);
|
||||
[[nodiscard]] bool WritePlayTimeFile(const std::vector<PlayTimeElement>& play_time_elements);
|
||||
|
||||
u64 GetPlayTime(u64 program_id);
|
||||
u64 GetTotalTimes(u64 program_id);
|
||||
|
||||
QString ReadablePlayTime(qulonglong time_seconds);
|
||||
QString ReadableTotalTimes(qulonglong times_count);
|
||||
|
||||
} // namespace PlayTime
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace {
|
|||
struct PlayTimeElement {
|
||||
ProgramId program_id;
|
||||
PlayTime play_time;
|
||||
TotalTimes total_times;
|
||||
};
|
||||
|
||||
std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(
|
||||
|
@ -57,9 +58,9 @@ std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(
|
|||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [program_id, play_time] : elements) {
|
||||
for (const auto& [program_id, play_time, total_times] : elements) {
|
||||
if (program_id != 0) {
|
||||
out_play_time_db[program_id] = play_time;
|
||||
out_play_time_db[program_id] = {play_time, total_times};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +90,7 @@ std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(
|
|||
|
||||
for (auto& [program_id, play_time] : play_time_db) {
|
||||
if (program_id != 0) {
|
||||
elements.push_back(PlayTimeElement{program_id, play_time});
|
||||
elements.push_back(PlayTimeElement{program_id, play_time.first, play_time.second});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +140,8 @@ void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
|
|||
while (!stop_token.stop_requested()) {
|
||||
Common::StoppableTimedWait(stop_token, 30s);
|
||||
|
||||
database[running_program_id] += GetDuration();
|
||||
database[running_program_id].first += GetDuration();
|
||||
database[running_program_id].second += 1;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +155,16 @@ void PlayTimeManager::Save() {
|
|||
u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
|
||||
auto it = database.find(program_id);
|
||||
if (it != database.end()) {
|
||||
return it->second;
|
||||
return it->second.first;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u64 PlayTimeManager::GetTotalTimes(u64 program_id) const {
|
||||
auto it = database.find(program_id);
|
||||
if (it != database.end()) {
|
||||
return it->second.second;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
@ -179,4 +190,12 @@ QString ReadablePlayTime(qulonglong time_seconds) {
|
|||
.arg(QString::fromUtf8(unit));
|
||||
}
|
||||
|
||||
QString ReadableTotalTimes(qulonglong times_count) {
|
||||
if (times_count == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return QStringLiteral("%L1").arg((double)times_count, 0, 'f', 0);
|
||||
}
|
||||
|
||||
} // namespace PlayTime
|
||||
|
|
|
@ -19,7 +19,8 @@ namespace PlayTime {
|
|||
|
||||
using ProgramId = u64;
|
||||
using PlayTime = u64;
|
||||
using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
|
||||
using TotalTimes = u64;
|
||||
using PlayTimeDatabase = std::map<ProgramId, std::pair<PlayTime, TotalTimes>>;
|
||||
|
||||
class PlayTimeManager {
|
||||
public:
|
||||
|
@ -30,6 +31,7 @@ public:
|
|||
SUDACHI_NON_MOVEABLE(PlayTimeManager);
|
||||
|
||||
u64 GetPlayTime(u64 program_id) const;
|
||||
u64 GetTotalTimes(u64 program_id) const;
|
||||
void ResetProgramPlayTime(u64 program_id);
|
||||
void SetProgramId(u64 program_id);
|
||||
void Start();
|
||||
|
@ -46,5 +48,6 @@ private:
|
|||
};
|
||||
|
||||
QString ReadablePlayTime(qulonglong time_seconds);
|
||||
QString ReadableTotalTimes(qulonglong times_count);
|
||||
|
||||
} // namespace PlayTime
|
||||
|
|
|
@ -212,6 +212,9 @@ struct Values {
|
|||
// Play time
|
||||
Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
|
||||
|
||||
// Total times
|
||||
Setting<bool> show_total_times{linkage, true, "show_total_times", Category::UiGameList};
|
||||
|
||||
bool configuration_applied;
|
||||
bool reset_to_defaults;
|
||||
bool shortcut_already_warned{false};
|
||||
|
|
Loading…
Reference in New Issue