Compare commits
1 Commits
e769d50a68
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a152edfb4 |
+308
-24
@@ -3,6 +3,10 @@ import QtQuick.Window
|
||||
import NomadInput 1.0
|
||||
|
||||
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.
|
||||
// I'll burn that bridge when I get to it
|
||||
property Window topWindow: Window {
|
||||
@@ -12,35 +16,314 @@ QtObject {
|
||||
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.
|
||||
|
||||
Connections {
|
||||
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
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#181818"
|
||||
color: "black"
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 20
|
||||
Item {
|
||||
id: heroArea
|
||||
|
||||
Text {
|
||||
text: "nomad top screen"
|
||||
color: "white"
|
||||
font.pixelSize: 50
|
||||
anchors.fill: parent
|
||||
|
||||
property bool heroAActive: true
|
||||
property url requestedHero: ""
|
||||
|
||||
property bool logoAActive: true
|
||||
property url requestedLogo: ""
|
||||
|
||||
readonly property int fadeTime: 200
|
||||
|
||||
function showHero(newSource) {
|
||||
requestedHero = newSource
|
||||
heroUpdateTimer.restart()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "last input: " + topWindow.lastInputText
|
||||
color: "white"
|
||||
font.pixelSize: 30
|
||||
function loadRequestedHero() {
|
||||
transitionToA.stop()
|
||||
transitionToB.stop()
|
||||
|
||||
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() {
|
||||
gamesList.currentIndex = index
|
||||
root.selectedGame = gameLibraryModel.gameById(model.gameId)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -18,7 +18,7 @@ int GameLibraryModel::rowCount(const QModelIndex &parent) const {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
QDir artworkDirectory(QDir(dataDirectory).filePath("artwork/" + gameId));
|
||||
@@ -27,11 +27,12 @@ QUrl GameLibraryModel::gridArtworkUrl(const QString &gameId) const {
|
||||
"png",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"webp"
|
||||
"webp",
|
||||
"gif"
|
||||
};
|
||||
|
||||
for (const QString &extension : extensions) {
|
||||
QString artworkPath = artworkDirectory.filePath("grid." + extension);
|
||||
QString artworkPath = artworkDirectory.filePath(artworkType + "." + extension);
|
||||
|
||||
if (QFileInfo::exists(artworkPath)) {
|
||||
return QUrl::fromLocalFile(artworkPath);
|
||||
@@ -41,6 +42,25 @@ QUrl GameLibraryModel::gridArtworkUrl(const QString &gameId) const {
|
||||
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
|
||||
QVariant GameLibraryModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid()) {
|
||||
@@ -70,7 +90,13 @@ QVariant GameLibraryModel::data(const QModelIndex &index, int role) const {
|
||||
return game.source;
|
||||
|
||||
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:
|
||||
return {};
|
||||
@@ -84,7 +110,9 @@ QHash<int, QByteArray> GameLibraryModel::roleNames() const {
|
||||
{ PlatformRole, "platform" },
|
||||
{ RunnerRole, "runner" },
|
||||
{ SourceRole, "source" },
|
||||
{ GridArtworkRole, "gridArtwork" }
|
||||
{ GridArtworkRole, "gridArtwork" },
|
||||
{ HeroArtworkRole, "heroArtwork" },
|
||||
{ LogoArtworkRole, "logoArtwork" }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,16 @@ public:
|
||||
PlatformRole,
|
||||
RunnerRole,
|
||||
SourceRole,
|
||||
GridArtworkRole
|
||||
GridArtworkRole,
|
||||
HeroArtworkRole,
|
||||
LogoArtworkRole
|
||||
};
|
||||
|
||||
// make sure we get a GameRepository
|
||||
explicit GameLibraryModel(GameRepository &repository, QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE QVariantMap gameById(const QString &gameId) const;
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
@@ -34,5 +38,5 @@ public:
|
||||
private:
|
||||
GameRepository &m_repository;
|
||||
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