247 lines
9 KiB
QML
247 lines
9 KiB
QML
|
|
// 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:
|
||
|
|
//
|
||
|
|
// // main.cpp
|
||
|
|
// engine.rootContext()->setContextProperty("dragonRelay", &g_relayBackend);
|
||
|
|
//
|
||
|
|
// 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)
|
||
|
|
|
||
|
|
import QtQuick 2.15
|
||
|
|
import QtQuick.Controls 2.15
|
||
|
|
import QtQuick.Layouts 1.15
|
||
|
|
|
||
|
|
Page {
|
||
|
|
id: root
|
||
|
|
title: qsTr("DragonRelay")
|
||
|
|
|
||
|
|
// ── State ──────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
readonly property int stateIdle: 0
|
||
|
|
readonly property int stateConnecting: 1
|
||
|
|
readonly property int stateConnected: 2
|
||
|
|
readonly property int stateError: 3
|
||
|
|
|
||
|
|
// Bound to dragonRelay.status in real integration
|
||
|
|
property int currentState: stateIdle
|
||
|
|
property string statusText: qsTr("Not connected")
|
||
|
|
|
||
|
|
// ── Header bar ─────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
header: ToolBar {
|
||
|
|
RowLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.leftMargin: 12
|
||
|
|
anchors.rightMargin: 12
|
||
|
|
|
||
|
|
Label {
|
||
|
|
text: qsTr("🐉 DragonRelay")
|
||
|
|
font.pixelSize: 16
|
||
|
|
font.bold: true
|
||
|
|
}
|
||
|
|
|
||
|
|
Item { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
// Connection status pill
|
||
|
|
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Label {
|
||
|
|
id: statusLabel
|
||
|
|
anchors.centerIn: parent
|
||
|
|
text: root.statusText
|
||
|
|
color: "white"
|
||
|
|
font.pixelSize: 12
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Body ───────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
ColumnLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.margins: 24
|
||
|
|
spacing: 20
|
||
|
|
|
||
|
|
// ── 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
|
||
|
|
|
||
|
|
ColumnLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
spacing: 12
|
||
|
|
|
||
|
|
TextField {
|
||
|
|
id: urlField
|
||
|
|
Layout.fillWidth: true
|
||
|
|
placeholderText: qsTr("Relay URL e.g. http://10.0.0.5:8080")
|
||
|
|
text: "http://"
|
||
|
|
inputMethodHints: Qt.ImhUrlCharactersOnly
|
||
|
|
}
|
||
|
|
|
||
|
|
TextField {
|
||
|
|
id: userField
|
||
|
|
Layout.fillWidth: true
|
||
|
|
placeholderText: qsTr("Username")
|
||
|
|
}
|
||
|
|
|
||
|
|
TextField {
|
||
|
|
id: passField
|
||
|
|
Layout.fillWidth: true
|
||
|
|
placeholderText: qsTr("Password")
|
||
|
|
echoMode: TextInput.Password
|
||
|
|
}
|
||
|
|
|
||
|
|
CheckBox {
|
||
|
|
id: saveCheck
|
||
|
|
text: qsTr("Remember this server")
|
||
|
|
checked: true
|
||
|
|
}
|
||
|
|
|
||
|
|
Button {
|
||
|
|
id: connectBtn
|
||
|
|
Layout.fillWidth: true
|
||
|
|
text: root.currentState === root.stateConnecting
|
||
|
|
? qsTr("Connecting…")
|
||
|
|
: qsTr("Connect")
|
||
|
|
enabled: urlField.text.length > 7
|
||
|
|
&& userField.text.length > 0
|
||
|
|
&& passField.text.length > 0
|
||
|
|
&& root.currentState !== root.stateConnecting
|
||
|
|
|
||
|
|
onClicked: {
|
||
|
|
root.currentState = root.stateConnecting
|
||
|
|
root.statusText = qsTr("Connecting…")
|
||
|
|
// Real call: dragonRelay.connectRelay(urlField.text, userField.text, passField.text)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Error message
|
||
|
|
Label {
|
||
|
|
id: errorLabel
|
||
|
|
Layout.fillWidth: true
|
||
|
|
visible: root.currentState === root.stateError
|
||
|
|
color: "#ef4444"
|
||
|
|
wrapMode: Text.WordWrap
|
||
|
|
text: root.statusText
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Host list (shown when connected) ──────────────────────────────
|
||
|
|
|
||
|
|
GroupBox {
|
||
|
|
title: qsTr("Streaming hosts")
|
||
|
|
Layout.fillWidth: true
|
||
|
|
Layout.fillHeight: true
|
||
|
|
visible: root.currentState === root.stateConnected
|
||
|
|
|
||
|
|
ColumnLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
spacing: 8
|
||
|
|
|
||
|
|
ListView {
|
||
|
|
id: hostList
|
||
|
|
Layout.fillWidth: true
|
||
|
|
Layout.fillHeight: true
|
||
|
|
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" }
|
||
|
|
}
|
||
|
|
|
||
|
|
delegate: Rectangle {
|
||
|
|
width: hostList.width
|
||
|
|
height: 56
|
||
|
|
radius: 8
|
||
|
|
color: hostMouse.containsMouse ? "#374151" : "#1f2937"
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.leftMargin: 16
|
||
|
|
anchors.rightMargin: 16
|
||
|
|
|
||
|
|
Column {
|
||
|
|
Label {
|
||
|
|
text: model.hostName
|
||
|
|
font.pixelSize: 14
|
||
|
|
font.bold: true
|
||
|
|
color: "white"
|
||
|
|
}
|
||
|
|
Label {
|
||
|
|
text: model.hostIp
|
||
|
|
font.pixelSize: 11
|
||
|
|
color: "#9ca3af"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Item { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
Button {
|
||
|
|
text: qsTr("Stream")
|
||
|
|
onClicked: {
|
||
|
|
// Real call: dragonRelay.streamHost(model.hostIp, "Desktop")
|
||
|
|
console.log("stream", model.hostIp)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
MouseArea {
|
||
|
|
id: hostMouse
|
||
|
|
anchors.fill: parent
|
||
|
|
hoverEnabled: true
|
||
|
|
onDoubleClicked: {
|
||
|
|
// Real call: dragonRelay.streamHost(model.hostIp, "Desktop")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Button {
|
||
|
|
Layout.alignment: Qt.AlignRight
|
||
|
|
text: qsTr("Disconnect")
|
||
|
|
flat: true
|
||
|
|
onClicked: {
|
||
|
|
root.currentState = root.stateIdle
|
||
|
|
root.statusText = qsTr("Not connected")
|
||
|
|
// Real call: dragonRelay.disconnectRelay()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Item { Layout.fillHeight: true; visible: root.currentState !== root.stateConnected }
|
||
|
|
}
|
||
|
|
}
|