top screen hero and logo art

This commit is contained in:
2026-08-15 03:46:53 -05:00
parent e769d50a68
commit 6a152edfb4
3 changed files with 347 additions and 31 deletions
+308 -24
View File
@@ -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: {