Add support for hiding games

Fixes #255
This commit is contained in:
Cameron Gutman 2020-08-01 21:06:01 -07:00
parent 9385d62c89
commit 539bf0cb30
10 changed files with 239 additions and 47 deletions

View File

@ -68,12 +68,7 @@ private:
}
QWriteLocker lock(&m_Computer->lock);
if (m_Computer->appList != appList) {
m_Computer->appList = appList;
m_Computer->sortAppList();
changed = true;
}
changed = m_Computer->updateAppList(appList);
return true;
}
@ -433,6 +428,15 @@ void ComputerManager::renameHost(NvComputer* computer, QString name)
handleComputerStateChanged(computer);
}
void ComputerManager::clientSideAttributeUpdated(NvComputer* computer)
{
// Persist the change
saveHosts();
// Notify the UI of the state change
handleComputerStateChanged(computer);
}
void ComputerManager::handleAboutToQuit()
{
QWriteLocker lock(&m_Lock);

View File

@ -181,6 +181,8 @@ public:
void renameHost(NvComputer* computer, QString name);
void clientSideAttributeUpdated(NvComputer* computer);
signals:
void computerStateChanged(NvComputer* computer);

View File

@ -4,6 +4,7 @@
#define SER_APPID "id"
#define SER_APPHDR "hdr"
#define SER_APPCOLLECTOR "appcollector"
#define SER_HIDDEN "hidden"
NvApp::NvApp(QSettings& settings)
{
@ -11,6 +12,7 @@ NvApp::NvApp(QSettings& settings)
id = settings.value(SER_APPID).toInt();
hdrSupported = settings.value(SER_APPHDR).toBool();
isAppCollectorGame = settings.value(SER_APPCOLLECTOR).toBool();
hidden = settings.value(SER_HIDDEN).toBool();
}
void NvApp::serialize(QSettings& settings) const
@ -19,4 +21,5 @@ void NvApp::serialize(QSettings& settings) const
settings.setValue(SER_APPID, id);
settings.setValue(SER_APPHDR, hdrSupported);
settings.setValue(SER_APPCOLLECTOR, isAppCollectorGame);
settings.setValue(SER_HIDDEN, hidden);
}

View File

@ -10,7 +10,16 @@ public:
bool operator==(const NvApp& other) const
{
return id == other.id;
return id == other.id &&
name == other.name &&
hdrSupported == other.hdrSupported &&
isAppCollectorGame == other.isAppCollectorGame &&
hidden == other.hidden;
}
bool operator!=(const NvApp& other) const
{
return !operator==(other);
}
bool isInitialized()
@ -25,6 +34,7 @@ public:
QString name;
bool hdrSupported = false;
bool isAppCollectorGame = false;
bool hidden = false;
};
Q_DECLARE_METATYPE(NvApp)

View File

@ -306,6 +306,25 @@ bool NvComputer::isReachableOverVpn()
}
}
bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
if (appList == newAppList) {
return false;
}
// Propagate client-side attributes to the new app list
for (const NvApp& existingApp : appList) {
for (NvApp& newApp : newAppList) {
if (existingApp.id == newApp.id) {
newApp.hidden = existingApp.hidden;
}
}
}
appList = newAppList;
sortAppList();
return true;
}
QVector<QString> NvComputer::uniqueAddresses() const
{
QVector<QString> uniqueAddressList;
@ -389,7 +408,12 @@ bool NvComputer::update(NvComputer& that)
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
ASSIGN_IF_CHANGED(gpuModel);
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
if (!that.appList.isEmpty()) {
// updateAppList() handles merging client-side attributes
updateAppList(that.appList);
}
return changed;
}

View File

@ -16,6 +16,8 @@ class NvComputer
private:
void sortAppList();
bool updateAppList(QVector<NvApp> newAppList);
bool pendingQuit;
public:

View File

@ -9,6 +9,7 @@ CenteredGridView {
property int computerIndex
property AppModel appModel : createModel()
property bool activated
property bool showHiddenGames
id: appGrid
focus: true
@ -48,7 +49,7 @@ CenteredGridView {
function createModel()
{
var model = Qt.createQmlObject('import AppModel 1.0; AppModel {}', parent, '')
model.initialize(ComputerManager, computerIndex)
model.initialize(ComputerManager, computerIndex, showHiddenGames)
return model
}
@ -58,6 +59,9 @@ CenteredGridView {
width: 220; height: 287;
grid: appGrid
// Dim the app if it's hidden
opacity: model.hidden ? 0.4 : 1.0
Image {
property bool isPlaceholder: false
@ -165,8 +169,8 @@ CenteredGridView {
function launchOrResumeSelectedApp()
{
var runningIndex = appModel.getRunningAppIndex()
if (runningIndex >= 0 && runningIndex !== index) {
var runningId = appModel.getRunningAppId()
if (runningId !== 0 && runningId !== model.appid) {
quitAppDialog.appName = appModel.getRunningAppName()
quitAppDialog.segueToStream = true
quitAppDialog.nextAppName = model.name
@ -190,6 +194,25 @@ CenteredGridView {
}
}
onPressAndHold: {
// popup() ensures the menu appears under the mouse cursor
if (appContextMenu.popup) {
appContextMenu.popup()
}
else {
// Qt 5.9 doesn't have popup()
appContextMenu.open()
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton;
onClicked: {
parent.onPressAndHold()
}
}
Keys.onReturnPressed: {
// Open the app context menu if activated via the gamepad or keyboard
// for running games. If the game isn't running, the above onClicked
@ -228,6 +251,14 @@ CenteredGridView {
onTriggered: doQuitGame()
visible: model.running
}
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.hidden
text: "Hide Game"
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
visible: !model.running || model.hidden
}
}
}

View File

@ -151,6 +151,26 @@ CenteredGridView {
NavigableMenu {
id: pcContextMenu
NavigableMenuItem {
parentMenu: pcContextMenu
text: "View Apps"
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
stackView.push(appView)
}
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: "View Hidden Apps"
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
stackView.push(appView)
}
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: "Delete PC"

View File

@ -7,7 +7,7 @@ AppModel::AppModel(QObject *parent)
this, &AppModel::handleBoxArtLoaded);
}
void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
void AppModel::initialize(ComputerManager* computerManager, int computerIndex, bool showHiddenGames)
{
m_ComputerManager = computerManager;
connect(m_ComputerManager, &ComputerManager::computerStateChanged,
@ -15,29 +15,23 @@ void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
Q_ASSERT(computerIndex < m_ComputerManager->getComputers().count());
m_Computer = m_ComputerManager->getComputers().at(computerIndex);
m_Apps = m_Computer->appList;
m_CurrentGameId = m_Computer->currentGameId;
m_ShowHiddenGames = showHiddenGames;
updateAppList(m_Computer->appList);
}
int AppModel::getRunningAppIndex()
int AppModel::getRunningAppId()
{
if (m_CurrentGameId != 0) {
for (int i = 0; i < m_Apps.count(); i++) {
if (m_Apps[i].id == m_CurrentGameId) {
return i;
}
}
}
return -1;
return m_CurrentGameId;
}
QString AppModel::getRunningAppName()
{
if (m_CurrentGameId != 0) {
for (int i = 0; i < m_Apps.count(); i++) {
if (m_Apps[i].id == m_CurrentGameId) {
return m_Apps[i].name;
for (int i = 0; i < m_AllApps.count(); i++) {
if (m_AllApps[i].id == m_CurrentGameId) {
return m_AllApps[i].name;
}
}
}
@ -47,8 +41,8 @@ QString AppModel::getRunningAppName()
Session* AppModel::createSessionForApp(int appIndex)
{
Q_ASSERT(appIndex < m_Apps.count());
NvApp app = m_Apps.at(appIndex);
Q_ASSERT(appIndex < m_VisibleApps.count());
NvApp app = m_VisibleApps.at(appIndex);
return new Session(m_Computer, app);
}
@ -60,7 +54,7 @@ int AppModel::rowCount(const QModelIndex &parent) const
if (parent.isValid())
return 0;
return m_Apps.count();
return m_VisibleApps.count();
}
QVariant AppModel::data(const QModelIndex &index, int role) const
@ -68,8 +62,8 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
if (!index.isValid())
return QVariant();
Q_ASSERT(index.row() < m_Apps.count());
NvApp app = m_Apps.at(index.row());
Q_ASSERT(index.row() < m_VisibleApps.count());
NvApp app = m_VisibleApps.at(index.row());
switch (role)
{
@ -80,6 +74,10 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
case BoxArtRole:
// FIXME: const-correctness
return const_cast<BoxArtManager&>(m_BoxArtManager).loadBoxArt(m_Computer, app);
case HiddenRole:
return app.hidden;
case AppIdRole:
return app.id;
default:
return QVariant();
}
@ -92,6 +90,8 @@ QHash<int, QByteArray> AppModel::roleNames() const
names[NameRole] = "name";
names[RunningRole] = "running";
names[BoxArtRole] = "boxart";
names[HiddenRole] = "hidden";
names[AppIdRole] = "appid";
return names;
}
@ -101,6 +101,98 @@ void AppModel::quitRunningApp()
m_ComputerManager->quitRunningApp(m_Computer);
}
QVector<NvApp> AppModel::getVisibleApps(const QVector<NvApp>& appList)
{
QVector<NvApp> visibleApps;
for (const NvApp& app : appList) {
if (m_ShowHiddenGames || !app.hidden) {
visibleApps.append(app);
}
}
return visibleApps;
}
void AppModel::updateAppList(QVector<NvApp> newList)
{
m_AllApps = newList;
QVector<NvApp> newVisibleList = getVisibleApps(newList);
// Process removals and updates first
for (int i = 0; i < m_VisibleApps.count(); i++) {
const NvApp& existingApp = m_VisibleApps.at(i);
bool found = false;
for (const NvApp& newApp : newVisibleList) {
if (existingApp.id == newApp.id) {
// If the data changed, update it in our list
if (existingApp != newApp) {
m_VisibleApps.replace(i, newApp);
emit dataChanged(createIndex(i, 0), createIndex(i, 0));
}
found = true;
break;
}
}
if (!found) {
beginRemoveRows(QModelIndex(), i, i);
m_VisibleApps.removeAt(i);
endRemoveRows();
i--;
}
}
// Process additions now
for (const NvApp& newApp : newVisibleList) {
int insertionIndex = m_VisibleApps.size();
bool found = false;
for (int i = 0; i < m_VisibleApps.count(); i++) {
const NvApp& existingApp = m_VisibleApps.at(i);
if (existingApp.id == newApp.id) {
found = true;
break;
}
else if (existingApp.name.toLower() > newApp.name.toLower()) {
insertionIndex = i;
break;
}
}
if (!found) {
beginInsertRows(QModelIndex(), insertionIndex, insertionIndex);
m_VisibleApps.insert(insertionIndex, newApp);
endInsertRows();
}
}
Q_ASSERT(newVisibleList == m_VisibleApps);
}
void AppModel::setAppHidden(int appIndex, bool hidden)
{
Q_ASSERT(appIndex < m_VisibleApps.count());
int appId = m_VisibleApps.at(appIndex).id;
{
QWriteLocker lock(&m_Computer->lock);
for (NvApp& app : m_Computer->appList) {
if (app.id == appId) {
app.hidden = hidden;
break;
}
}
}
m_ComputerManager->clientSideAttributeUpdated(m_Computer);
}
void AppModel::handleComputerStateChanged(NvComputer* computer)
{
// Ignore updates for computers that aren't ours
@ -119,20 +211,15 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
// First, process additions/removals from the app list. This
// is required because the new game may now be running, so
// we can't check that first.
if (computer->appList != m_Apps) {
// Just reset the whole thing if the list changes
beginResetModel();
m_Apps = computer->appList;
m_CurrentGameId = computer->currentGameId;
endResetModel();
return;
if (computer->appList != m_AllApps) {
updateAppList(computer->appList);
}
// Finally, process changes to the active app
if (computer->currentGameId != m_CurrentGameId) {
// First, invalidate the running state of newly running game
for (int i = 0; i < m_Apps.count(); i++) {
if (m_Apps[i].id == computer->currentGameId) {
for (int i = 0; i < m_VisibleApps.count(); i++) {
if (m_VisibleApps[i].id == computer->currentGameId) {
emit dataChanged(createIndex(i, 0),
createIndex(i, 0),
QVector<int>() << RunningRole);
@ -142,8 +229,8 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
// Next, invalidate the running state of the old game (if it exists)
if (m_CurrentGameId != 0) {
for (int i = 0; i < m_Apps.count(); i++) {
if (m_Apps[i].id == m_CurrentGameId) {
for (int i = 0; i < m_VisibleApps.count(); i++) {
if (m_VisibleApps[i].id == m_CurrentGameId) {
emit dataChanged(createIndex(i, 0),
createIndex(i, 0),
QVector<int>() << RunningRole);
@ -161,7 +248,7 @@ void AppModel::handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl /* image
{
Q_ASSERT(computer == m_Computer);
int index = m_Apps.indexOf(app);
int index = m_VisibleApps.indexOf(app);
// Make sure we're not delivering a callback to an app that's already been removed
if (index >= 0) {

View File

@ -14,23 +14,27 @@ class AppModel : public QAbstractListModel
{
NameRole = Qt::UserRole,
RunningRole,
BoxArtRole
BoxArtRole,
HiddenRole,
AppIdRole,
};
public:
explicit AppModel(QObject *parent = nullptr);
// Must be called before any QAbstractListModel functions
Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex);
Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex, bool showHiddenGames);
Q_INVOKABLE Session* createSessionForApp(int appIndex);
Q_INVOKABLE int getRunningAppIndex();
Q_INVOKABLE int getRunningAppId();
Q_INVOKABLE QString getRunningAppName();
Q_INVOKABLE void quitRunningApp();
Q_INVOKABLE void setAppHidden(int appIndex, bool hidden);
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override;
@ -46,9 +50,14 @@ signals:
void computerLost();
private:
void updateAppList(QVector<NvApp> newList);
QVector<NvApp> getVisibleApps(const QVector<NvApp>& appList);
NvComputer* m_Computer;
BoxArtManager m_BoxArtManager;
ComputerManager* m_ComputerManager;
QVector<NvApp> m_Apps;
QVector<NvApp> m_VisibleApps, m_AllApps;
int m_CurrentGameId;
bool m_ShowHiddenGames;
};