Add Qt Bigscreen (QML/CMake), remove Tauri

Add a native Qt "Bigscreen" shell: CMakeLists, C++ entry (main.cpp, InputRouter), QML module (Theme, ShellWindow, views and components) and a Bigscreen/.gitignore; update top-level .gitignore and README with Qt build/run instructions. Remove the legacy Tauri/web prototype files (package.json, package-lock.json, src-tauri and many web assets) as part of the migration to the Qt/CMake-based shell.
This commit is contained in:
2026-05-23 21:19:24 +12:00
parent 627c52a5b7
commit f8632e40e7
102 changed files with 1123 additions and 17645 deletions
+4
View File
@@ -0,0 +1,4 @@
import QtQuick
ShellWindow {
}
+140
View File
@@ -0,0 +1,140 @@
import QtQuick
import QtQuick.Controls
import Nebula.Bigscreen
ApplicationWindow {
id: window
width: 1280
height: 720
visible: true
title: "Nebula Bigscreen"
color: Theme.backgroundDeep
property string currentView: "home"
property bool powerOverlayVisible: false
property string signedInUser: "Player"
NebulaBackground {
anchors.fill: parent
}
Column {
anchors.fill: parent
spacing: 0
TopBar {
id: topBar
width: parent.width
userName: window.signedInUser
accentFocus: contentLoader.item && contentLoader.item.accentFocus !== undefined
? contentLoader.item.accentFocus()
: 0
}
Loader {
id: contentLoader
width: parent.width
height: parent.height - topBar.height
sourceComponent: viewComponentFor(window.currentView)
}
}
Connections {
target: contentLoader.item
ignoreUnknownSignals: true
function onNavigate(route) {
window.onChildNavigate(route)
}
function onGoBack() {
window.onChildGoBack()
}
}
PowerOverlay {
id: powerOverlay
visible: window.powerOverlayVisible
z: 10
onDismissed: window.powerOverlayVisible = false
onActionChosen: function(actionId) {
console.log("Power action:", actionId)
window.powerOverlayVisible = false
}
}
Connections {
target: InputRouter
function onActionTriggered(action) {
if (window.powerOverlayVisible) {
window.handlePowerInput(action)
return
}
if (action === InputRouter.Menu) {
window.powerOverlayVisible = true
return
}
if (contentLoader.item && contentLoader.item.handleInput) {
contentLoader.item.handleInput(action)
}
}
}
Component {
id: homeComponent
HomeView {}
}
Component {
id: libraryComponent
LibraryView {}
}
Component {
id: settingsComponent
SettingsView {}
}
function viewComponentFor(viewId) {
switch (viewId) {
case "library":
return libraryComponent
case "settings":
return settingsComponent
default:
return homeComponent
}
}
function onChildNavigate(route) {
if (route === "power") {
window.powerOverlayVisible = true
return
}
window.currentView = route
}
function onChildGoBack() {
window.currentView = "home"
}
function handlePowerInput(action) {
switch (action) {
case InputRouter.Up:
powerOverlay.moveFocus(-1)
break
case InputRouter.Down:
powerOverlay.moveFocus(1)
break
case InputRouter.Accept:
powerOverlay.activateFocused()
break
case InputRouter.Back:
window.powerOverlayVisible = false
break
default:
break
}
}
}
+45
View File
@@ -0,0 +1,45 @@
pragma Singleton
import QtQuick
QtObject {
readonly property color backgroundDeep: "#050A17"
readonly property color backgroundMid: "#111321"
readonly property color backgroundPanel: "#181A2E"
readonly property color accentCyan: "#4FD8FF"
readonly property color accentPurple: "#6F5CFF"
readonly property color accentLine: "#30325A"
readonly property color textPrimary: "#F2F7FF"
readonly property color textMuted: "#A8BDD8"
readonly property int tileWidth: 280
readonly property int tileHeight: 160
readonly property int tileGap: 20
readonly property int topBarHeight: 72
readonly property int focusScaleDuration: 180
readonly property real tileFocusScale: 1.06
readonly property real tilePressScale: 0.96
readonly property font brandFont: Qt.font({
family: "Segoe UI",
pixelSize: 28,
weight: Font.DemiBold
})
readonly property font titleFont: Qt.font({
family: "Segoe UI",
pixelSize: 22,
weight: Font.DemiBold
})
readonly property font bodyFont: Qt.font({
family: "Segoe UI",
pixelSize: 16,
weight: Font.Normal
})
readonly property font metaFont: Qt.font({
family: "Segoe UI",
pixelSize: 13,
weight: Font.Normal
})
}
+89
View File
@@ -0,0 +1,89 @@
import QtQuick
import Nebula.Bigscreen
Item {
id: root
property string label: ""
property string meta: ""
property string iconText: ""
property bool focused: false
property bool pressed: false
signal activated()
implicitWidth: Theme.tileWidth
implicitHeight: Theme.tileHeight
scale: pressed ? Theme.tilePressScale : (focused ? Theme.tileFocusScale : 1.0)
Behavior on scale {
NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic }
}
Rectangle {
id: card
anchors.fill: parent
radius: 14
color: Theme.backgroundPanel
border.width: focused ? 2 : 1
border.color: focused ? Theme.accentCyan : Theme.accentLine
Rectangle {
anchors.fill: parent
radius: parent.radius
visible: focused
gradient: Gradient {
GradientStop { position: 0.0; color: "#224FD8FF" }
GradientStop { position: 1.0; color: "transparent" }
}
}
Column {
anchors.fill: parent
anchors.margins: 20
spacing: 10
Text {
text: root.iconText
font.pixelSize: 36
}
Text {
text: root.label
font: Theme.titleFont
color: Theme.textPrimary
}
Text {
text: root.meta
font: Theme.metaFont
color: Theme.textMuted
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 12
height: 3
radius: 2
color: Theme.accentCyan
opacity: focused ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation { duration: Theme.focusScaleDuration }
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: root.activated()
onPressed: root.pressed = true
onReleased: root.pressed = false
onCanceled: root.pressed = false
}
}
@@ -0,0 +1,53 @@
import QtQuick
Item {
id: root
anchors.fill: parent
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#050A17" }
GradientStop { position: 0.55; color: "#0E1630" }
GradientStop { position: 1.0; color: "#1A1342" }
}
}
Repeater {
model: 48
Rectangle {
width: (index % 5) + 1
height: width
radius: width / 2
color: index % 3 === 0 ? "#88FFFFFF" : "#44B8D4FF"
x: (index * 137) % root.width
y: (index * 89) % root.height
opacity: 0.35 + (index % 7) * 0.05
SequentialAnimation on opacity {
loops: Animation.Infinite
running: true
NumberAnimation {
from: 0.2
to: 0.7
duration: 2000 + (index % 5) * 400
}
NumberAnimation {
from: 0.7
to: 0.2
duration: 2000 + (index % 5) * 400
}
}
}
}
Rectangle {
anchors.fill: parent
opacity: 0.35
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: "#CC000000" }
}
}
}
+92
View File
@@ -0,0 +1,92 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
Rectangle {
id: root
property int focusIndex: 0
readonly property var actions: [
{ label: "Sleep", meta: "Suspend system" },
{ label: "Restart", meta: "Reboot NebulaOS" },
{ label: "Shut down", meta: "Power off" },
{ label: "Log out", meta: "Return to login" },
{ label: "Desktop Mode", meta: "Switch session" },
{ label: "Close", meta: "Back to Home" }
]
signal dismissed()
signal actionChosen(string actionId)
anchors.fill: parent
color: "#CC050A17"
Keys.onPressed: function(event) { event.accepted = true }
ColumnLayout {
anchors.centerIn: parent
width: Math.min(parent.width - 80, 520)
spacing: 12
Text {
text: "Power"
font: Theme.brandFont
color: Theme.textPrimary
Layout.alignment: Qt.AlignHCenter
}
Repeater {
model: root.actions
Rectangle {
Layout.fillWidth: true
height: 56
radius: 10
color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel
border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine
border.width: index === root.focusIndex ? 2 : 1
RowLayout {
anchors.fill: parent
anchors.margins: 16
Text {
text: modelData.label
font: Theme.titleFont
color: Theme.textPrimary
}
Item { Layout.fillWidth: true }
Text {
text: modelData.meta
font: Theme.metaFont
color: Theme.textMuted
}
}
MouseArea {
anchors.fill: parent
onClicked: {
root.focusIndex = index
root.activateFocused()
}
}
}
}
}
function moveFocus(delta) {
focusIndex = Math.max(0, Math.min(actions.length - 1, focusIndex + delta))
}
function activateFocused() {
const labels = ["sleep", "restart", "shutdown", "logout", "desktop", "close"]
const id = labels[focusIndex]
if (id === "close") {
dismissed()
} else {
actionChosen(id)
}
}
}
+52
View File
@@ -0,0 +1,52 @@
import QtQuick
import Nebula.Bigscreen
Item {
id: root
property var tiles: []
property int focusIndex: 0
property real accentFocus: 0
signal tileActivated(int index)
implicitHeight: Theme.tileHeight + 24
onFocusIndexChanged: root.accentFocus = focusIndex / Math.max(1, tiles.length - 1)
Row {
id: rail
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 40
spacing: Theme.tileGap
Repeater {
model: root.tiles
AppTile {
label: modelData.label
meta: modelData.meta
iconText: modelData.icon
focused: index === root.focusIndex
onActivated: root.tileActivated(index)
}
}
}
function moveFocus(delta) {
if (tiles.length === 0) {
return
}
const next = Math.max(0, Math.min(tiles.length - 1, focusIndex + delta))
focusIndex = next
}
function activateFocused() {
if (tiles.length === 0) {
return
}
tileActivated(focusIndex)
}
}
+80
View File
@@ -0,0 +1,80 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
Item {
id: root
property string userName: "Player"
property real accentFocus: 0
implicitHeight: Theme.topBarHeight
RowLayout {
anchors.fill: parent
anchors.leftMargin: 40
anchors.rightMargin: 40
spacing: 16
Text {
text: "Nebula OS"
font: Theme.brandFont
color: Theme.textPrimary
style: Text.Outline
styleColor: Theme.accentCyan
Layout.alignment: Qt.AlignVCenter
}
Item { Layout.fillWidth: true }
RowLayout {
spacing: 14
Layout.alignment: Qt.AlignVCenter
Rectangle {
width: 36
height: 36
radius: 18
color: Theme.backgroundPanel
border.color: Theme.accentCyan
border.width: 2
Text {
anchors.centerIn: parent
text: root.userName.length > 0 ? root.userName.charAt(0).toUpperCase() : "?"
font: Theme.titleFont
color: Theme.textPrimary
}
}
Text {
id: clockLabel
font: Theme.bodyFont
color: Theme.textMuted
}
}
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: 40 + root.accentFocus * 320
width: 120
height: 2
radius: 1
color: Theme.accentCyan
opacity: 0.85
Behavior on anchors.leftMargin {
NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic }
}
}
Timer {
interval: 1000
running: true
repeat: true
triggeredOnStart: true
onTriggered: clockLabel.text = Qt.formatTime(new Date(), "hh:mm AP")
}
}
+59
View File
@@ -0,0 +1,59 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
ColumnLayout {
id: root
property var homeTiles: [
{ label: "Library", meta: "Games and apps", icon: "📚", route: "library" },
{ label: "Settings", meta: "System controls", icon: "⚙", route: "settings" },
{ label: "Power", meta: "Sleep and sessions", icon: "⏻", route: "power" }
]
signal navigate(string route)
spacing: 28
Text {
text: "Home"
font: Theme.brandFont
color: Theme.textPrimary
Layout.leftMargin: 40
}
TileRail {
id: rail
Layout.fillWidth: true
tiles: root.homeTiles
onTileActivated: function(index) {
const route = root.homeTiles[index].route
if (route === "power") {
root.navigate("power")
} else {
root.navigate(route)
}
}
}
function handleInput(action) {
switch (action) {
case InputRouter.Left:
rail.moveFocus(-1)
break
case InputRouter.Right:
rail.moveFocus(1)
break
case InputRouter.Accept:
rail.activateFocused()
break
default:
break
}
}
function accentFocus() {
return rail.accentFocus
}
}
+99
View File
@@ -0,0 +1,99 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
ColumnLayout {
id: root
signal goBack()
readonly property var entries: [
{ title: "Nebula Demo", meta: "Local • Ready", icon: "🎮" },
{ title: "Retro Runner", meta: "Wine • Installed", icon: "👾" },
{ title: "Media Hub", meta: "App • Installed", icon: "🎬" }
]
property int focusIndex: 0
spacing: 20
Text {
text: "Library"
font: Theme.brandFont
color: Theme.textPrimary
Layout.leftMargin: 40
}
Text {
text: "Mock entries for v0 — scanners and launchers come later."
font: Theme.metaFont
color: Theme.textMuted
Layout.leftMargin: 40
Layout.bottomMargin: 8
}
ListView {
id: list
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 40
Layout.rightMargin: 40
clip: true
spacing: 12
model: root.entries
delegate: Rectangle {
width: list.width
height: 72
radius: 12
color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel
border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine
border.width: index === root.focusIndex ? 2 : 1
RowLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 16
Text {
text: modelData.icon
font.pixelSize: 28
}
ColumnLayout {
spacing: 2
Text {
text: modelData.title
font: Theme.titleFont
color: Theme.textPrimary
}
Text {
text: modelData.meta
font: Theme.metaFont
color: Theme.textMuted
}
}
}
}
}
function handleInput(action) {
switch (action) {
case InputRouter.Up:
focusIndex = Math.max(0, focusIndex - 1)
list.currentIndex = focusIndex
break
case InputRouter.Down:
focusIndex = Math.min(entries.length - 1, focusIndex + 1)
list.currentIndex = focusIndex
break
case InputRouter.Back:
root.goBack()
break
case InputRouter.Accept:
break
default:
break
}
}
}
+120
View File
@@ -0,0 +1,120 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
ColumnLayout {
id: root
signal goBack()
readonly property var categories: [
"Network",
"Audio",
"Display",
"Controller",
"System"
]
property int categoryIndex: 0
property int itemIndex: 0
spacing: 20
Text {
text: "Settings"
font: Theme.brandFont
color: Theme.textPrimary
Layout.leftMargin: 40
}
Row {
Layout.leftMargin: 40
spacing: 16
Repeater {
model: root.categories
Rectangle {
height: 40
width: categoryLabel.implicitWidth + 24
radius: 8
color: index === root.categoryIndex ? "#22304A66" : "transparent"
border.color: index === root.categoryIndex ? Theme.accentCyan : "transparent"
border.width: index === root.categoryIndex ? 2 : 0
Text {
id: categoryLabel
anchors.centerIn: parent
text: modelData
font: Theme.bodyFont
color: index === root.categoryIndex ? Theme.accentCyan : Theme.textMuted
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 40
radius: 16
color: Theme.backgroundPanel
border.color: Theme.accentLine
ColumnLayout {
anchors.fill: parent
anchors.margins: 24
spacing: 12
Text {
text: root.categories[root.categoryIndex]
font: Theme.titleFont
color: Theme.textPrimary
}
Repeater {
model: 3
Rectangle {
Layout.fillWidth: true
height: 52
radius: 10
color: index === root.itemIndex ? "#22304A66" : Theme.backgroundMid
border.color: index === root.itemIndex ? Theme.accentCyan : Theme.accentLine
border.width: index === root.itemIndex ? 2 : 1
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 16
text: "Placeholder setting " + (index + 1)
font: Theme.bodyFont
color: Theme.textPrimary
}
}
}
}
}
function handleInput(action) {
switch (action) {
case InputRouter.Left:
categoryIndex = Math.max(0, categoryIndex - 1)
break
case InputRouter.Right:
categoryIndex = Math.min(categories.length - 1, categoryIndex + 1)
break
case InputRouter.Up:
itemIndex = Math.max(0, itemIndex - 1)
break
case InputRouter.Down:
itemIndex = Math.min(2, itemIndex + 1)
break
case InputRouter.Back:
root.goBack()
break
default:
break
}
}
}