2018-07-06 03:07:05 +00:00
|
|
|
import QtQuick 2.9
|
|
|
|
import QtQuick.Controls 2.2
|
|
|
|
|
|
|
|
import AppModel 1.0
|
2018-07-06 05:08:55 +00:00
|
|
|
import ComputerManager 1.0
|
2019-05-19 18:08:23 +00:00
|
|
|
import SdlGamepadKeyNavigation 1.0
|
2018-07-06 05:08:55 +00:00
|
|
|
|
2019-04-06 18:48:58 +00:00
|
|
|
CenteredGridView {
|
2018-07-06 03:07:05 +00:00
|
|
|
property int computerIndex
|
2018-07-07 23:30:26 +00:00
|
|
|
property AppModel appModel : createModel()
|
2019-03-27 08:28:46 +00:00
|
|
|
property bool activated
|
2020-08-02 04:06:01 +00:00
|
|
|
property bool showHiddenGames
|
2020-11-24 03:38:22 +00:00
|
|
|
property bool showGames
|
2018-07-06 03:07:05 +00:00
|
|
|
|
2018-07-09 06:05:36 +00:00
|
|
|
id: appGrid
|
2018-09-23 22:16:27 +00:00
|
|
|
focus: true
|
|
|
|
activeFocusOnTab: true
|
2019-03-27 08:28:46 +00:00
|
|
|
topMargin: 20
|
|
|
|
bottomMargin: 5
|
2019-04-02 02:49:33 +00:00
|
|
|
cellWidth: 230; cellHeight: 297;
|
2018-07-06 03:07:05 +00:00
|
|
|
|
2018-08-10 01:48:40 +00:00
|
|
|
function computerLost()
|
|
|
|
{
|
|
|
|
// Go back to the PC view on PC loss
|
|
|
|
stackView.pop()
|
|
|
|
}
|
|
|
|
|
2019-02-23 06:44:09 +00:00
|
|
|
Component.onCompleted: {
|
|
|
|
// Don't show any highlighted item until interacting with them.
|
|
|
|
// We do this here instead of onActivated to avoid losing the user's
|
|
|
|
// selection when backing out of a different page of the app.
|
|
|
|
currentIndex = -1
|
|
|
|
}
|
|
|
|
|
2019-02-23 06:14:06 +00:00
|
|
|
StackView.onActivated: {
|
|
|
|
appModel.computerLost.connect(computerLost)
|
2019-03-27 08:28:46 +00:00
|
|
|
activated = true
|
2019-05-19 18:08:23 +00:00
|
|
|
|
|
|
|
// Highlight the first item if a gamepad is connected
|
|
|
|
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
|
|
|
currentIndex = 0
|
|
|
|
}
|
2020-11-24 03:38:22 +00:00
|
|
|
|
|
|
|
if (!showGames && !showHiddenGames) {
|
|
|
|
// Check if there's a direct launch app
|
|
|
|
var directLaunchAppIndex = model.getDirectLaunchAppIndex();
|
|
|
|
if (directLaunchAppIndex >= 0) {
|
|
|
|
// Start the direct launch app if nothing else is running
|
|
|
|
currentIndex = directLaunchAppIndex
|
|
|
|
currentItem.launchOrResumeSelectedApp(false)
|
|
|
|
|
|
|
|
// Set showGames so we will not loop when the stream ends
|
|
|
|
showGames = true
|
|
|
|
}
|
|
|
|
}
|
2019-02-23 06:14:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
StackView.onDeactivating: {
|
|
|
|
appModel.computerLost.disconnect(computerLost)
|
2019-03-27 08:28:46 +00:00
|
|
|
activated = false
|
2018-07-06 07:34:16 +00:00
|
|
|
}
|
|
|
|
|
2018-07-06 03:07:05 +00:00
|
|
|
function createModel()
|
|
|
|
{
|
2018-07-06 05:08:55 +00:00
|
|
|
var model = Qt.createQmlObject('import AppModel 1.0; AppModel {}', parent, '')
|
2020-08-02 04:06:01 +00:00
|
|
|
model.initialize(ComputerManager, computerIndex, showHiddenGames)
|
2018-07-06 03:07:05 +00:00
|
|
|
return model
|
|
|
|
}
|
|
|
|
|
2018-07-07 23:30:26 +00:00
|
|
|
model: appModel
|
2018-07-06 03:07:05 +00:00
|
|
|
|
2018-09-23 22:16:27 +00:00
|
|
|
delegate: NavigableItemDelegate {
|
2019-04-02 02:49:33 +00:00
|
|
|
width: 220; height: 287;
|
2018-09-24 02:06:26 +00:00
|
|
|
grid: appGrid
|
2018-07-06 03:07:05 +00:00
|
|
|
|
2021-03-03 00:32:56 +00:00
|
|
|
property alias appContextMenu: appContextMenuLoader.item
|
2021-03-03 00:35:50 +00:00
|
|
|
property alias appNameText: appNameTextLoader.item
|
2021-03-03 00:32:56 +00:00
|
|
|
|
2020-08-02 04:06:01 +00:00
|
|
|
// Dim the app if it's hidden
|
|
|
|
opacity: model.hidden ? 0.4 : 1.0
|
|
|
|
|
2018-07-06 03:07:05 +00:00
|
|
|
Image {
|
2019-04-02 02:49:33 +00:00
|
|
|
property bool isPlaceholder: false
|
|
|
|
|
2018-07-06 03:07:05 +00:00
|
|
|
id: appIcon
|
2019-01-27 07:57:02 +00:00
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
2019-04-02 02:49:33 +00:00
|
|
|
y: 10
|
2018-07-06 03:07:05 +00:00
|
|
|
source: model.boxart
|
2019-04-02 02:49:33 +00:00
|
|
|
|
|
|
|
onSourceSizeChanged: {
|
2021-05-02 15:39:25 +00:00
|
|
|
// Nearly all of Nvidia's official box art does not match the dimensions of placeholder
|
|
|
|
// images, however the one known exeception is Overcooked. Therefore, we only execute
|
|
|
|
// the image size checks if this is not an app collector game. We know the officially
|
|
|
|
// supported games all have box art, so this check is not required.
|
|
|
|
if (!model.isAppCollectorGame &&
|
|
|
|
((sourceSize.width == 130 && sourceSize.height == 180) || // GFE 2.0 placeholder image
|
|
|
|
(sourceSize.width == 628 && sourceSize.height == 888) || // GFE 3.0 placeholder image
|
|
|
|
(sourceSize.width == 200 && sourceSize.height == 266))) // Our no_app_image.png
|
2019-04-02 02:49:33 +00:00
|
|
|
{
|
|
|
|
isPlaceholder = true
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
isPlaceholder = false
|
|
|
|
}
|
|
|
|
|
|
|
|
width = 200
|
|
|
|
height = 267
|
2018-07-06 03:07:05 +00:00
|
|
|
}
|
2019-04-02 02:49:33 +00:00
|
|
|
|
|
|
|
// Display a tooltip with the full name if it's truncated
|
|
|
|
ToolTip.text: model.name
|
|
|
|
ToolTip.delay: 1000
|
|
|
|
ToolTip.timeout: 5000
|
2021-03-03 00:35:50 +00:00
|
|
|
ToolTip.visible: (parent.hovered || parent.highlighted) && (!appNameText || appNameText.truncated)
|
2018-07-06 03:07:05 +00:00
|
|
|
}
|
|
|
|
|
2021-03-03 00:32:56 +00:00
|
|
|
Loader {
|
|
|
|
active: model.running
|
|
|
|
asynchronous: true
|
|
|
|
anchors.fill: appIcon
|
|
|
|
|
|
|
|
sourceComponent: Item {
|
|
|
|
ToolButton {
|
|
|
|
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
|
|
|
|
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
|
|
|
|
anchors.centerIn: parent
|
|
|
|
implicitWidth: 125
|
|
|
|
implicitHeight: 125
|
|
|
|
|
|
|
|
Image {
|
|
|
|
source: "qrc:/res/baseline-play_circle_filled_white-48px.svg"
|
|
|
|
anchors.centerIn: parent
|
|
|
|
sourceSize {
|
|
|
|
width: 75
|
|
|
|
height: 75
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
launchOrResumeSelectedApp(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
ToolTip.text: qsTr("Resume Game")
|
|
|
|
ToolTip.delay: 1000
|
|
|
|
ToolTip.timeout: 3000
|
|
|
|
ToolTip.visible: hovered
|
2019-01-27 07:57:02 +00:00
|
|
|
}
|
2019-02-13 03:07:48 +00:00
|
|
|
|
2021-03-03 00:32:56 +00:00
|
|
|
ToolButton {
|
|
|
|
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
|
|
|
|
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
|
|
|
|
anchors.centerIn: parent
|
|
|
|
implicitWidth: 125
|
|
|
|
implicitHeight: 125
|
|
|
|
|
|
|
|
Image {
|
|
|
|
source: "qrc:/res/baseline-cancel-24px.svg"
|
|
|
|
anchors.centerIn: parent
|
|
|
|
sourceSize {
|
|
|
|
width: 75
|
|
|
|
height: 75
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
doQuitGame()
|
|
|
|
}
|
|
|
|
|
|
|
|
ToolTip.text: qsTr("Quit Game")
|
|
|
|
ToolTip.delay: 1000
|
|
|
|
ToolTip.timeout: 3000
|
|
|
|
ToolTip.visible: hovered
|
2019-01-27 07:57:02 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-05 19:19:54 +00:00
|
|
|
}
|
2018-08-02 05:32:21 +00:00
|
|
|
|
2021-03-03 00:32:56 +00:00
|
|
|
Loader {
|
2021-03-03 00:35:50 +00:00
|
|
|
id: appNameTextLoader
|
2021-03-03 00:32:56 +00:00
|
|
|
active: appIcon.isPlaceholder
|
2021-03-03 01:03:09 +00:00
|
|
|
|
|
|
|
// This loader is not asynchronous to avoid noticeable differences
|
|
|
|
// in the time in which the text loads for each game.
|
2021-03-03 00:32:56 +00:00
|
|
|
|
2019-04-02 02:49:33 +00:00
|
|
|
width: appIcon.width
|
2019-04-02 03:55:11 +00:00
|
|
|
height: model.running ? 175 : appIcon.height
|
2021-03-03 00:32:56 +00:00
|
|
|
|
2019-04-02 02:49:33 +00:00
|
|
|
anchors.left: appIcon.left
|
|
|
|
anchors.right: appIcon.right
|
|
|
|
anchors.bottom: appIcon.bottom
|
2021-03-03 00:32:56 +00:00
|
|
|
|
|
|
|
sourceComponent: Label {
|
|
|
|
id: appNameText
|
|
|
|
text: model.name
|
|
|
|
font.pointSize: 22
|
|
|
|
leftPadding: 20
|
|
|
|
rightPadding: 20
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
wrapMode: Text.Wrap
|
|
|
|
elide: Text.ElideRight
|
|
|
|
}
|
2018-07-06 03:07:05 +00:00
|
|
|
}
|
|
|
|
|
2020-11-24 03:38:22 +00:00
|
|
|
function launchOrResumeSelectedApp(quitExistingApp)
|
2018-08-02 05:32:21 +00:00
|
|
|
{
|
2020-08-02 04:06:01 +00:00
|
|
|
var runningId = appModel.getRunningAppId()
|
|
|
|
if (runningId !== 0 && runningId !== model.appid) {
|
2020-11-24 03:38:22 +00:00
|
|
|
if (quitExistingApp) {
|
|
|
|
quitAppDialog.appName = appModel.getRunningAppName()
|
|
|
|
quitAppDialog.segueToStream = true
|
|
|
|
quitAppDialog.nextAppName = model.name
|
|
|
|
quitAppDialog.nextAppIndex = index
|
|
|
|
quitAppDialog.open()
|
|
|
|
}
|
|
|
|
|
2018-08-02 05:32:21 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var component = Qt.createComponent("StreamSegue.qml")
|
2021-01-01 03:33:41 +00:00
|
|
|
var segue = component.createObject(stackView, {
|
|
|
|
"appName": model.name,
|
|
|
|
"session": appModel.createSessionForApp(index),
|
|
|
|
"isResume": runningId === model.appid
|
|
|
|
})
|
2018-08-02 05:32:21 +00:00
|
|
|
stackView.push(segue)
|
|
|
|
}
|
|
|
|
|
2019-04-04 04:13:12 +00:00
|
|
|
onClicked: {
|
|
|
|
// Only allow clicking on the box art for non-running games.
|
|
|
|
// For running games, buttons will appear to resume or quit which
|
|
|
|
// will handle starting the game and clicks on the box art will
|
|
|
|
// be ignored.
|
|
|
|
if (!model.running) {
|
2020-11-24 03:38:22 +00:00
|
|
|
launchOrResumeSelectedApp(true)
|
2019-04-04 04:13:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-02 04:06:01 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-04 03:27:00 +00:00
|
|
|
Keys.onReturnPressed: {
|
2019-04-04 04:13:12 +00:00
|
|
|
// Open the app context menu if activated via the gamepad or keyboard
|
|
|
|
// for running games. If the game isn't running, the above onClicked
|
|
|
|
// method will handle the launch.
|
2019-01-27 07:57:02 +00:00
|
|
|
if (model.running) {
|
2019-04-04 03:27:00 +00:00
|
|
|
// This will be keyboard/gamepad driven so use
|
2019-01-27 07:57:02 +00:00
|
|
|
// open() instead of popup()
|
|
|
|
appContextMenu.open()
|
|
|
|
}
|
2018-08-02 05:32:21 +00:00
|
|
|
}
|
2018-07-08 04:52:20 +00:00
|
|
|
|
2021-02-27 23:01:22 +00:00
|
|
|
Keys.onEnterPressed: {
|
|
|
|
// Open the app context menu if activated via the gamepad or keyboard
|
|
|
|
// for running games. If the game isn't running, the above onClicked
|
|
|
|
// method will handle the launch.
|
|
|
|
if (model.running) {
|
|
|
|
// This will be keyboard/gamepad driven so use
|
|
|
|
// open() instead of popup()
|
|
|
|
appContextMenu.open()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-10 03:59:01 +00:00
|
|
|
Keys.onMenuPressed: {
|
2020-11-21 19:28:04 +00:00
|
|
|
// This will be keyboard/gamepad driven so use open() instead of popup()
|
|
|
|
appContextMenu.open()
|
2019-02-10 03:59:01 +00:00
|
|
|
}
|
|
|
|
|
2019-01-27 07:57:02 +00:00
|
|
|
function doQuitGame() {
|
|
|
|
quitAppDialog.appName = appModel.getRunningAppName()
|
|
|
|
quitAppDialog.segueToStream = false
|
|
|
|
quitAppDialog.open()
|
2018-09-30 20:41:32 +00:00
|
|
|
}
|
|
|
|
|
2021-03-03 00:32:56 +00:00
|
|
|
Loader {
|
|
|
|
id: appContextMenuLoader
|
|
|
|
asynchronous: true
|
|
|
|
sourceComponent: NavigableMenu {
|
|
|
|
id: appContextMenu
|
|
|
|
NavigableMenuItem {
|
|
|
|
parentMenu: appContextMenu
|
|
|
|
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
|
|
|
|
onTriggered: launchOrResumeSelectedApp(true)
|
|
|
|
}
|
|
|
|
NavigableMenuItem {
|
|
|
|
parentMenu: appContextMenu
|
|
|
|
text: qsTr("Quit Game")
|
|
|
|
onTriggered: doQuitGame()
|
|
|
|
visible: model.running
|
|
|
|
}
|
|
|
|
NavigableMenuItem {
|
|
|
|
parentMenu: appContextMenu
|
|
|
|
checkable: true
|
|
|
|
checked: model.directLaunch
|
|
|
|
text: qsTr("Direct Launch")
|
|
|
|
onTriggered: appModel.setAppDirectLaunch(model.index, !model.directLaunch)
|
|
|
|
enabled: !model.hidden
|
|
|
|
|
|
|
|
ToolTip.text: qsTr("Launch this app immediately when the host is selected, bypassing the app selection grid.")
|
|
|
|
ToolTip.delay: 1000
|
|
|
|
ToolTip.timeout: 3000
|
|
|
|
ToolTip.visible: hovered
|
|
|
|
}
|
|
|
|
NavigableMenuItem {
|
|
|
|
parentMenu: appContextMenu
|
|
|
|
checkable: true
|
|
|
|
checked: model.hidden
|
|
|
|
text: qsTr("Hide Game")
|
|
|
|
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
|
|
|
|
enabled: model.hidden || (!model.running && !model.directLaunch)
|
|
|
|
|
|
|
|
ToolTip.text: qsTr("Hide this game from the app grid. To access hidden games, right-click on the host and choose %1.").arg(qsTr("View All Apps"))
|
|
|
|
ToolTip.delay: 1000
|
|
|
|
ToolTip.timeout: 5000
|
|
|
|
ToolTip.visible: hovered
|
|
|
|
}
|
2020-11-28 23:32:39 +00:00
|
|
|
}
|
2018-07-06 03:07:05 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-09 06:05:36 +00:00
|
|
|
|
2019-03-31 20:57:57 +00:00
|
|
|
NavigableMessageDialog {
|
2018-09-08 00:33:34 +00:00
|
|
|
id: quitAppDialog
|
2018-09-09 17:08:23 +00:00
|
|
|
property string appName : ""
|
2018-09-08 00:33:34 +00:00
|
|
|
property bool segueToStream : false
|
2018-09-09 17:08:23 +00:00
|
|
|
property string nextAppName: ""
|
|
|
|
property int nextAppIndex: 0
|
2020-11-21 19:15:54 +00:00
|
|
|
text:qsTr("Are you sure you want to quit %1? Any unsaved progress will be lost.").arg(appName)
|
2019-04-01 00:24:25 +00:00
|
|
|
standardButtons: Dialog.Yes | Dialog.No
|
2018-10-01 05:20:19 +00:00
|
|
|
|
|
|
|
function quitApp() {
|
2018-09-08 00:33:34 +00:00
|
|
|
var component = Qt.createComponent("QuitSegue.qml")
|
|
|
|
var params = {"appName": appName}
|
|
|
|
if (segueToStream) {
|
|
|
|
// Store the session and app name if we're going to stream after
|
|
|
|
// successfully quitting the old app.
|
2018-09-09 17:08:23 +00:00
|
|
|
params.nextAppName = nextAppName
|
|
|
|
params.nextSession = appModel.createSessionForApp(nextAppIndex)
|
2018-09-08 00:33:34 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
params.nextAppName = null
|
|
|
|
params.nextSession = null
|
|
|
|
}
|
|
|
|
|
|
|
|
stackView.push(component.createObject(stackView, params))
|
|
|
|
|
|
|
|
// Trigger the quit after pushing the quit segue on screen
|
|
|
|
appModel.quitRunningApp()
|
|
|
|
}
|
2018-10-01 05:20:19 +00:00
|
|
|
|
|
|
|
onAccepted: quitApp()
|
2018-09-08 00:33:34 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 19:53:27 +00:00
|
|
|
ScrollBar.vertical: ScrollBar {}
|
2018-07-06 03:07:05 +00:00
|
|
|
}
|