Compare commits
1 Commits
e769d50a68
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a152edfb4 |
+308
-24
@@ -3,6 +3,10 @@ import QtQuick.Window
|
|||||||
import NomadInput 1.0
|
import NomadInput 1.0
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var selectedGame: ({})
|
||||||
|
|
||||||
// this is our top screen, for now, though in the future I would like to have it configurable which screen does which.
|
// this is our top screen, for now, though in the future I would like to have it configurable which screen does which.
|
||||||
// I'll burn that bridge when I get to it
|
// I'll burn that bridge when I get to it
|
||||||
property Window topWindow: Window {
|
property Window topWindow: Window {
|
||||||
@@ -12,35 +16,314 @@ QtObject {
|
|||||||
title: "nomad-top" // this is necesarry for Sway, which auto-assigns the windows
|
title: "nomad-top" // this is necesarry for Sway, which auto-assigns the windows
|
||||||
property string lastInputText: "none" // I've never really integrated NomadInput into this debug section in the top half and now I'm too lazy to. it's just a placeholder anyway.
|
property string lastInputText: "none" // I've never really integrated NomadInput into this debug section in the top half and now I'm too lazy to. it's just a placeholder anyway.
|
||||||
|
|
||||||
Connections {
|
Rectangle {
|
||||||
target: inputManager
|
|
||||||
|
|
||||||
function onActionPressed(action) {
|
|
||||||
topWindow.lastInputText = inputManager.actionName(action) + " pressed"
|
|
||||||
}
|
|
||||||
function onActionReleased(action) {
|
|
||||||
topWindow.lastInputText = inputManager.actionName(action) + " released"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { // just a placeholder for the top screen until we do something better
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#181818"
|
color: "black"
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
anchors.centerIn: parent
|
id: heroArea
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
Text {
|
anchors.fill: parent
|
||||||
text: "nomad top screen"
|
|
||||||
color: "white"
|
property bool heroAActive: true
|
||||||
font.pixelSize: 50
|
property url requestedHero: ""
|
||||||
|
|
||||||
|
property bool logoAActive: true
|
||||||
|
property url requestedLogo: ""
|
||||||
|
|
||||||
|
readonly property int fadeTime: 200
|
||||||
|
|
||||||
|
function showHero(newSource) {
|
||||||
|
requestedHero = newSource
|
||||||
|
heroUpdateTimer.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
function loadRequestedHero() {
|
||||||
text: "last input: " + topWindow.lastInputText
|
transitionToA.stop()
|
||||||
color: "white"
|
transitionToB.stop()
|
||||||
font.pixelSize: 30
|
|
||||||
|
if (heroA.opacity >= heroB.opacity) {
|
||||||
|
heroAActive = true
|
||||||
|
heroA.opacity = 1
|
||||||
|
heroB.opacity = 0
|
||||||
|
|
||||||
|
heroB.source = requestedHero
|
||||||
|
|
||||||
|
// If this source was already loaded, status won't change,
|
||||||
|
// so onStatusChanged won't fire. Start the transition ourselves.
|
||||||
|
if (heroB.status === Image.Ready
|
||||||
|
&& heroB.source === requestedHero) {
|
||||||
|
transitionToB.restart()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
heroAActive = false
|
||||||
|
heroA.opacity = 0
|
||||||
|
heroB.opacity = 1
|
||||||
|
|
||||||
|
heroA.source = requestedHero
|
||||||
|
|
||||||
|
if (heroA.status === Image.Ready
|
||||||
|
&& heroA.source === requestedHero) {
|
||||||
|
transitionToA.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: heroUpdateTimer
|
||||||
|
interval: 75
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
heroArea.loadRequestedHero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
|
||||||
|
function onSelectedGameChanged() {
|
||||||
|
heroArea.showHero(root.selectedGame.heroArtwork)
|
||||||
|
heroArea.showLogo(root.selectedGame.logoArtwork)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: heroA
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Ready && source === heroArea.requestedHero && !heroArea.heroAActive) {
|
||||||
|
transitionToA.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: heroB
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Ready && source === heroArea.requestedHero && heroArea.heroAActive) {
|
||||||
|
transitionToB.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
id: transitionToB
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: heroA
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: heroB
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinished: {
|
||||||
|
if (heroB.source === heroArea.requestedHero) {
|
||||||
|
heroArea.heroAActive = false
|
||||||
|
heroA.opacity = 0
|
||||||
|
heroB.opacity = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
id: transitionToA
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: heroB
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: heroA
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinished: {
|
||||||
|
if (heroA.source === heroArea.requestedHero) {
|
||||||
|
heroArea.heroAActive = true
|
||||||
|
heroA.opacity = 1
|
||||||
|
heroB.opacity = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: preloadPrevious
|
||||||
|
visible: false
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: preloadNext
|
||||||
|
visible: false
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLogo(newSource) {
|
||||||
|
requestedLogo = newSource
|
||||||
|
|
||||||
|
transitionLogoToA.stop()
|
||||||
|
transitionLogoToB.stop()
|
||||||
|
|
||||||
|
if (logoAActive) {
|
||||||
|
logoB.source = requestedLogo
|
||||||
|
|
||||||
|
if (logoB.status === Image.Ready
|
||||||
|
&& logoB.source === requestedLogo) {
|
||||||
|
transitionLogoToB.restart()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logoA.source = requestedLogo
|
||||||
|
|
||||||
|
if (logoA.status === Image.Ready
|
||||||
|
&& logoA.source === requestedLogo) {
|
||||||
|
transitionLogoToA.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: logoA
|
||||||
|
|
||||||
|
z: 10
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: 400
|
||||||
|
height: 240
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (
|
||||||
|
status === Image.Ready
|
||||||
|
&& source === heroArea.requestedLogo
|
||||||
|
&& !heroArea.logoAActive
|
||||||
|
) {
|
||||||
|
transitionLogoToA.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: logoB
|
||||||
|
|
||||||
|
z: 10
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: 400
|
||||||
|
height: 240
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (
|
||||||
|
status === Image.Ready
|
||||||
|
&& source === heroArea.requestedLogo
|
||||||
|
&& heroArea.logoAActive
|
||||||
|
) {
|
||||||
|
transitionLogoToB.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
id: transitionLogoToB
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: logoA
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: logoB
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinished: {
|
||||||
|
if (logoB.source === heroArea.requestedLogo) {
|
||||||
|
heroArea.logoAActive = false
|
||||||
|
logoA.opacity = 0
|
||||||
|
logoB.opacity = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
id: transitionLogoToA
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: logoB
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: logoA
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
duration: heroArea.fadeTime
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinished: {
|
||||||
|
if (logoA.source === heroArea.requestedLogo) {
|
||||||
|
heroArea.logoAActive = true
|
||||||
|
logoA.opacity = 1
|
||||||
|
logoB.opacity = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,6 +535,7 @@ QtObject {
|
|||||||
|
|
||||||
function onNavigationFocused() {
|
function onNavigationFocused() {
|
||||||
gamesList.currentIndex = index
|
gamesList.currentIndex = index
|
||||||
|
root.selectedGame = gameLibraryModel.gameById(model.gameId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ int GameLibraryModel::rowCount(const QModelIndex &parent) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetches artwork based on the game's UUID
|
// fetches artwork based on the game's UUID
|
||||||
QUrl GameLibraryModel::gridArtworkUrl(const QString &gameId) const {
|
QUrl GameLibraryModel::getArtworkUrl(const QString &gameId, const QString &artworkType) const {
|
||||||
QString dataDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
QString dataDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
|
||||||
QDir artworkDirectory(QDir(dataDirectory).filePath("artwork/" + gameId));
|
QDir artworkDirectory(QDir(dataDirectory).filePath("artwork/" + gameId));
|
||||||
@@ -27,11 +27,12 @@ QUrl GameLibraryModel::gridArtworkUrl(const QString &gameId) const {
|
|||||||
"png",
|
"png",
|
||||||
"jpg",
|
"jpg",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"webp"
|
"webp",
|
||||||
|
"gif"
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const QString &extension : extensions) {
|
for (const QString &extension : extensions) {
|
||||||
QString artworkPath = artworkDirectory.filePath("grid." + extension);
|
QString artworkPath = artworkDirectory.filePath(artworkType + "." + extension);
|
||||||
|
|
||||||
if (QFileInfo::exists(artworkPath)) {
|
if (QFileInfo::exists(artworkPath)) {
|
||||||
return QUrl::fromLocalFile(artworkPath);
|
return QUrl::fromLocalFile(artworkPath);
|
||||||
@@ -41,6 +42,25 @@ QUrl GameLibraryModel::gridArtworkUrl(const QString &gameId) const {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantMap GameLibraryModel::gameById(const QString &gameId) const {
|
||||||
|
for (const GameRecord &game : m_games) {
|
||||||
|
if (game.id == gameId) {
|
||||||
|
return {
|
||||||
|
{ "gameId", game.id },
|
||||||
|
{ "title", game.title },
|
||||||
|
{ "platform", game.platform },
|
||||||
|
{ "runner", game.runner },
|
||||||
|
{ "source", game.source },
|
||||||
|
{ "gridArtwork", getArtworkUrl(game.id, "grid") },
|
||||||
|
{ "heroArtwork", getArtworkUrl(game.id, "hero") },
|
||||||
|
{ "logoArtwork", getArtworkUrl(game.id, "logo") }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// returns whatever data is requested
|
// returns whatever data is requested
|
||||||
QVariant GameLibraryModel::data(const QModelIndex &index, int role) const {
|
QVariant GameLibraryModel::data(const QModelIndex &index, int role) const {
|
||||||
if (!index.isValid()) {
|
if (!index.isValid()) {
|
||||||
@@ -70,7 +90,13 @@ QVariant GameLibraryModel::data(const QModelIndex &index, int role) const {
|
|||||||
return game.source;
|
return game.source;
|
||||||
|
|
||||||
case GridArtworkRole:
|
case GridArtworkRole:
|
||||||
return gridArtworkUrl(game.id);
|
return getArtworkUrl(game.id, "grid");
|
||||||
|
|
||||||
|
case HeroArtworkRole:
|
||||||
|
return getArtworkUrl(game.id, "hero");
|
||||||
|
|
||||||
|
case LogoArtworkRole:
|
||||||
|
return getArtworkUrl(game.id, "logo");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
@@ -84,7 +110,9 @@ QHash<int, QByteArray> GameLibraryModel::roleNames() const {
|
|||||||
{ PlatformRole, "platform" },
|
{ PlatformRole, "platform" },
|
||||||
{ RunnerRole, "runner" },
|
{ RunnerRole, "runner" },
|
||||||
{ SourceRole, "source" },
|
{ SourceRole, "source" },
|
||||||
{ GridArtworkRole, "gridArtwork" }
|
{ GridArtworkRole, "gridArtwork" },
|
||||||
|
{ HeroArtworkRole, "heroArtwork" },
|
||||||
|
{ LogoArtworkRole, "logoArtwork" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,16 @@ public:
|
|||||||
PlatformRole,
|
PlatformRole,
|
||||||
RunnerRole,
|
RunnerRole,
|
||||||
SourceRole,
|
SourceRole,
|
||||||
GridArtworkRole
|
GridArtworkRole,
|
||||||
|
HeroArtworkRole,
|
||||||
|
LogoArtworkRole
|
||||||
};
|
};
|
||||||
|
|
||||||
// make sure we get a GameRepository
|
// make sure we get a GameRepository
|
||||||
explicit GameLibraryModel(GameRepository &repository, QObject *parent = nullptr);
|
explicit GameLibraryModel(GameRepository &repository, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_INVOKABLE QVariantMap gameById(const QString &gameId) const;
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
@@ -34,5 +38,5 @@ public:
|
|||||||
private:
|
private:
|
||||||
GameRepository &m_repository;
|
GameRepository &m_repository;
|
||||||
QList<GameRecord> m_games;
|
QList<GameRecord> m_games;
|
||||||
QUrl gridArtworkUrl(const QString &gameId) const;
|
QUrl getArtworkUrl(const QString &gameId, const QString &artworkType) const;
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user