Wire DragonRelayView to the dragonRelay backend (real model + status binding)
This commit is contained in:
parent
41b431a11c
commit
967539de5d
1 changed files with 106 additions and 71 deletions
|
|
@ -1,22 +1,17 @@
|
|||
// DragonRelayView.qml
|
||||
//
|
||||
// Add a DragonRelay server and connect to its VPN.
|
||||
// Wire this view up to a DragonRelayBackend C++ object exposed to QML via
|
||||
// qmlRegisterType or setContextProperty:
|
||||
// DragonRelay server connection + host browser.
|
||||
//
|
||||
// // main.cpp
|
||||
// engine.rootContext()->setContextProperty("dragonRelay", &g_relayBackend);
|
||||
// Bound to the C++ DragonRelayBackend exposed via:
|
||||
//
|
||||
// DragonRelayBackend (to be written as a thin QObject wrapper around
|
||||
// RelayClient + TunnelManager) should expose:
|
||||
// Q_PROPERTY(int status ...) // 0=Idle, 1=Connecting, 2=Connected, 3=Error
|
||||
// Q_PROPERTY(QString statusText ...) // human-readable status
|
||||
// Q_PROPERTY(var hosts ...) // list model of {name, ip, port}
|
||||
// Q_INVOKABLE void connectRelay(url, username, password)
|
||||
// Q_INVOKABLE void disconnectRelay()
|
||||
// Q_INVOKABLE void streamHost(ip, app)
|
||||
// signal: hostsChanged()
|
||||
// signal: errorOccurred(message)
|
||||
// engine.rootContext()->setContextProperty("dragonRelay", &relayBackend);
|
||||
//
|
||||
// Status enum (must match DragonRelayBackend::Status):
|
||||
// 0 = Disconnected
|
||||
// 1 = Connecting
|
||||
// 2 = TunnelUp
|
||||
// 3 = Ready
|
||||
// 4 = Error
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
|
@ -26,24 +21,34 @@ Page {
|
|||
id: root
|
||||
title: qsTr("DragonRelay")
|
||||
|
||||
// ── State ──────────────────────────────────────────────────────────────
|
||||
readonly property int statusDisconnected: 0
|
||||
readonly property int statusConnecting: 1
|
||||
readonly property int statusTunnelUp: 2
|
||||
readonly property int statusReady: 3
|
||||
readonly property int statusError: 4
|
||||
|
||||
readonly property int stateIdle: 0
|
||||
readonly property int stateConnecting: 1
|
||||
readonly property int stateConnected: 2
|
||||
readonly property int stateError: 3
|
||||
readonly property int currentState: dragonRelay ? dragonRelay.status : statusDisconnected
|
||||
readonly property string statusText: dragonRelay ? dragonRelay.statusText
|
||||
: qsTr("Not connected")
|
||||
readonly property bool isLoginVisible:
|
||||
currentState === statusDisconnected || currentState === statusError
|
||||
readonly property bool isHostListVisible:
|
||||
currentState === statusTunnelUp || currentState === statusReady
|
||||
|
||||
// Bound to dragonRelay.status in real integration
|
||||
property int currentState: stateIdle
|
||||
property string statusText: qsTr("Not connected")
|
||||
|
||||
// Display picker state
|
||||
property bool showDisplayPicker: false
|
||||
property var pickerDisplays: []
|
||||
property string pickerHostIP: ""
|
||||
property string pickerHostName: ""
|
||||
|
||||
// ── Header bar ─────────────────────────────────────────────────────────
|
||||
property bool connectInFlight: false
|
||||
|
||||
Connections {
|
||||
target: dragonRelay
|
||||
function onStatusChanged() {
|
||||
if (root.currentState !== root.statusConnecting)
|
||||
root.connectInFlight = false
|
||||
}
|
||||
}
|
||||
|
||||
header: ToolBar {
|
||||
RowLayout {
|
||||
|
|
@ -67,17 +72,26 @@ Page {
|
|||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// Connection status pill
|
||||
Label {
|
||||
visible: root.isHostListVisible && dragonRelay
|
||||
&& dragonRelay.tunnelIP.length > 0
|
||||
text: dragonRelay ? dragonRelay.tunnelIP : ""
|
||||
color: "#9ca3af"
|
||||
font.pixelSize: 11
|
||||
rightPadding: 8
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: statusLabel.implicitWidth + 20
|
||||
height: 24
|
||||
radius: 12
|
||||
color: {
|
||||
switch (root.currentState) {
|
||||
case root.stateConnecting: return "#f59e0b" // amber
|
||||
case root.stateConnected: return "#10b981" // green
|
||||
case root.stateError: return "#ef4444" // red
|
||||
default: return "#6b7280" // grey
|
||||
case root.statusConnecting: return "#f59e0b"
|
||||
case root.statusTunnelUp: return "#10b981"
|
||||
case root.statusReady: return "#10b981"
|
||||
case root.statusError: return "#ef4444"
|
||||
default: return "#6b7280"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,27 +106,23 @@ Page {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Body ───────────────────────────────────────────────────────────────
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 24
|
||||
spacing: 20
|
||||
|
||||
// ── Login form (shown when idle or errored) ────────────────────────
|
||||
// Login form (shown when idle or errored)
|
||||
|
||||
GroupBox {
|
||||
id: loginBox
|
||||
title: qsTr("Connect to a DragonRelay server")
|
||||
Layout.fillWidth: true
|
||||
visible: root.currentState === root.stateIdle
|
||||
|| root.currentState === root.stateError
|
||||
visible: root.isLoginVisible
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 12
|
||||
|
||||
// Logo
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/app/assets/wilddragon-logo.jpg"
|
||||
|
|
@ -128,12 +138,21 @@ Page {
|
|||
placeholderText: qsTr("Relay URL e.g. https://relay.wilddragon.net")
|
||||
text: "https://"
|
||||
inputMethodHints: Qt.ImhUrlCharactersOnly
|
||||
Component.onCompleted: {
|
||||
if (dragonRelay && dragonRelay.lastURL && dragonRelay.lastURL.length > 0)
|
||||
text = dragonRelay.lastURL
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Username")
|
||||
Component.onCompleted: {
|
||||
if (dragonRelay && dragonRelay.lastUsername
|
||||
&& dragonRelay.lastUsername.length > 0)
|
||||
text = dragonRelay.lastUsername
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
|
|
@ -152,26 +171,28 @@ Page {
|
|||
Button {
|
||||
id: connectBtn
|
||||
Layout.fillWidth: true
|
||||
text: root.currentState === root.stateConnecting
|
||||
? qsTr("Connecting…")
|
||||
text: root.currentState === root.statusConnecting
|
||||
|| root.connectInFlight
|
||||
? qsTr("Connecting...")
|
||||
: qsTr("Connect")
|
||||
enabled: urlField.text.length > 7
|
||||
&& userField.text.length > 0
|
||||
&& passField.text.length > 0
|
||||
&& root.currentState !== root.stateConnecting
|
||||
&& root.currentState !== root.statusConnecting
|
||||
&& !root.connectInFlight
|
||||
|
||||
onClicked: {
|
||||
root.currentState = root.stateConnecting
|
||||
root.statusText = qsTr("Connecting…")
|
||||
// Real call: dragonRelay.connectRelay(urlField.text, userField.text, passField.text)
|
||||
root.connectInFlight = true
|
||||
dragonRelay.connectRelay(urlField.text,
|
||||
userField.text,
|
||||
passField.text)
|
||||
}
|
||||
}
|
||||
|
||||
// Error message
|
||||
Label {
|
||||
id: errorLabel
|
||||
Layout.fillWidth: true
|
||||
visible: root.currentState === root.stateError
|
||||
visible: root.currentState === root.statusError
|
||||
color: "#ef4444"
|
||||
wrapMode: Text.WordWrap
|
||||
text: root.statusText
|
||||
|
|
@ -179,13 +200,13 @@ Page {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Host list (shown when connected) ──────────────────────────────
|
||||
// Host list (shown when connected)
|
||||
|
||||
GroupBox {
|
||||
title: qsTr("Streaming hosts")
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: root.currentState === root.stateConnected
|
||||
visible: root.isHostListVisible
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -198,12 +219,7 @@ Page {
|
|||
clip: true
|
||||
spacing: 8
|
||||
|
||||
// In real integration: model: dragonRelay.hosts
|
||||
model: ListModel {
|
||||
// Placeholder for design-time preview
|
||||
ListElement { hostName: "Gaming PC"; hostIp: "10.99.0.10" }
|
||||
ListElement { hostName: "Living Room"; hostIp: "10.99.0.11" }
|
||||
}
|
||||
model: dragonRelay ? dragonRelay.hosts : []
|
||||
|
||||
delegate: Rectangle {
|
||||
width: hostList.width
|
||||
|
|
@ -218,13 +234,13 @@ Page {
|
|||
|
||||
Column {
|
||||
Label {
|
||||
text: model.hostName
|
||||
text: modelData.name
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
color: "white"
|
||||
}
|
||||
Label {
|
||||
text: model.hostIp
|
||||
text: modelData.ip + ":" + modelData.port
|
||||
font.pixelSize: 11
|
||||
color: "#9ca3af"
|
||||
}
|
||||
|
|
@ -235,14 +251,15 @@ Page {
|
|||
Button {
|
||||
text: qsTr("Stream")
|
||||
onClicked: {
|
||||
var displays = dragonRelay.displaysForHost(model.hostIp)
|
||||
if (!dragonRelay) return
|
||||
var displays = dragonRelay.displaysForHost(modelData.ip)
|
||||
if (displays && displays.length > 1) {
|
||||
root.pickerHostIP = model.hostIp
|
||||
root.pickerHostName = model.hostName
|
||||
root.pickerHostIP = modelData.ip
|
||||
root.pickerHostName = modelData.name
|
||||
root.pickerDisplays = displays
|
||||
root.showDisplayPicker = true
|
||||
} else {
|
||||
dragonRelay.streamHost(model.hostIp, "Desktop")
|
||||
dragonRelay.streamHost(modelData.ip, "Desktop", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -253,30 +270,47 @@ Page {
|
|||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onDoubleClicked: {
|
||||
// Real call: dragonRelay.streamHost(model.hostIp, "Desktop")
|
||||
if (dragonRelay)
|
||||
dragonRelay.streamHost(modelData.ip, "Desktop", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("No hosts visible yet - waiting for Artemis to register...")
|
||||
color: "#9ca3af"
|
||||
font.pixelSize: 12
|
||||
visible: hostList.count === 0
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
spacing: 8
|
||||
|
||||
Button {
|
||||
text: qsTr("Refresh")
|
||||
flat: true
|
||||
onClicked: {
|
||||
if (dragonRelay) dragonRelay.refreshHosts()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Disconnect")
|
||||
flat: true
|
||||
onClicked: {
|
||||
root.currentState = root.stateIdle
|
||||
root.statusText = qsTr("Not connected")
|
||||
// Real call: dragonRelay.disconnectRelay()
|
||||
if (dragonRelay) dragonRelay.disconnectRelay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; visible: root.currentState !== root.stateConnected }
|
||||
Item { Layout.fillHeight: true; visible: !root.isHostListVisible }
|
||||
}
|
||||
|
||||
// ── Display Picker Modal ───────────────────────────────────────────────
|
||||
|
||||
DragonDisplayPicker {
|
||||
visible: root.showDisplayPicker
|
||||
anchors.fill: parent
|
||||
|
|
@ -285,6 +319,7 @@ Page {
|
|||
displays: root.pickerDisplays
|
||||
onDisplaySelected: function(idx) {
|
||||
root.showDisplayPicker = false
|
||||
if (dragonRelay)
|
||||
dragonRelay.streamHostDisplay(root.pickerHostIP, idx)
|
||||
}
|
||||
onCancelled: { root.showDisplayPicker = false }
|
||||
|
|
|
|||
Loading…
Reference in a new issue