204 lines
6.9 KiB
QML
204 lines
6.9 KiB
QML
|
|
// DragonDisplayPicker.qml
|
||
|
|
//
|
||
|
|
// Multi-display picker component for streaming to a specific monitor on a remote host.
|
||
|
|
// Shows a modal overlay with a list of available displays and lets the user select one.
|
||
|
|
//
|
||
|
|
// Usage:
|
||
|
|
// DragonDisplayPicker {
|
||
|
|
// visible: showDisplayPicker
|
||
|
|
// anchors.fill: parent
|
||
|
|
// hostIP: pickerHostIP
|
||
|
|
// hostName: pickerHostName
|
||
|
|
// displays: pickerDisplays
|
||
|
|
// onDisplaySelected: function(idx) {
|
||
|
|
// showDisplayPicker = false
|
||
|
|
// dragonRelay.streamHostDisplay(pickerHostIP, idx)
|
||
|
|
// }
|
||
|
|
// onCancelled: { showDisplayPicker = false }
|
||
|
|
// }
|
||
|
|
|
||
|
|
import QtQuick 2.15
|
||
|
|
import QtQuick.Controls 2.15
|
||
|
|
import QtQuick.Layouts 1.15
|
||
|
|
|
||
|
|
Item {
|
||
|
|
id: root
|
||
|
|
|
||
|
|
property string hostIP: ""
|
||
|
|
property string hostName: ""
|
||
|
|
property var displays: [] // array of display maps
|
||
|
|
|
||
|
|
signal displaySelected(int displayIndex)
|
||
|
|
signal cancelled()
|
||
|
|
|
||
|
|
// ── Modal backdrop ─────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
anchors.fill: parent
|
||
|
|
color: "rgba(0, 0, 0, 0.7)"
|
||
|
|
|
||
|
|
MouseArea {
|
||
|
|
anchors.fill: parent
|
||
|
|
onClicked: root.cancelled()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Dialog box ─────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
id: dialogBox
|
||
|
|
anchors.centerIn: parent
|
||
|
|
width: Math.min(parent.width - 48, 500)
|
||
|
|
height: Math.min(parent.height - 48, 400)
|
||
|
|
radius: 12
|
||
|
|
color: "#0f172a" // dark blue slate
|
||
|
|
border.color: "#374151"
|
||
|
|
border.width: 1
|
||
|
|
|
||
|
|
ColumnLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.margins: 20
|
||
|
|
spacing: 16
|
||
|
|
|
||
|
|
// ── Title ──────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
Label {
|
||
|
|
text: qsTr("Select Display")
|
||
|
|
font.pixelSize: 18
|
||
|
|
font.bold: true
|
||
|
|
color: "white"
|
||
|
|
}
|
||
|
|
|
||
|
|
Label {
|
||
|
|
text: qsTr("Host: %1 (%2)").arg(root.hostName).arg(root.hostIP)
|
||
|
|
font.pixelSize: 12
|
||
|
|
color: "#9ca3af"
|
||
|
|
Layout.fillWidth: true
|
||
|
|
elide: Text.ElideRight
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Divider ────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
height: 1
|
||
|
|
color: "#374151"
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Display list ───────────────────────────────────────────────
|
||
|
|
|
||
|
|
ListView {
|
||
|
|
id: displayList
|
||
|
|
Layout.fillWidth: true
|
||
|
|
Layout.fillHeight: true
|
||
|
|
spacing: 8
|
||
|
|
clip: true
|
||
|
|
|
||
|
|
model: root.displays
|
||
|
|
|
||
|
|
delegate: Rectangle {
|
||
|
|
width: displayList.width
|
||
|
|
height: 56
|
||
|
|
radius: 8
|
||
|
|
color: displayMouse.containsMouse ? "#374151" : "#1f2937"
|
||
|
|
border.color: displayMouse.containsMouse ? "#4b5563" : "transparent"
|
||
|
|
border.width: 1
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.leftMargin: 16
|
||
|
|
anchors.rightMargin: 16
|
||
|
|
spacing: 12
|
||
|
|
|
||
|
|
Column {
|
||
|
|
spacing: 2
|
||
|
|
|
||
|
|
Label {
|
||
|
|
text: modelData.friendlyName || modelData.name
|
||
|
|
font.pixelSize: 14
|
||
|
|
font.bold: true
|
||
|
|
color: "white"
|
||
|
|
}
|
||
|
|
|
||
|
|
Label {
|
||
|
|
text: qsTr("%1x%2").arg(modelData.width).arg(modelData.height)
|
||
|
|
font.pixelSize: 11
|
||
|
|
color: "#9ca3af"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Item { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
visible: modelData.isPrimary
|
||
|
|
width: primaryLabel.implicitWidth + 12
|
||
|
|
height: 20
|
||
|
|
radius: 4
|
||
|
|
color: "#10b981" // green
|
||
|
|
|
||
|
|
Label {
|
||
|
|
id: primaryLabel
|
||
|
|
anchors.centerIn: parent
|
||
|
|
text: qsTr("Primary")
|
||
|
|
font.pixelSize: 10
|
||
|
|
color: "white"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
MouseArea {
|
||
|
|
id: displayMouse
|
||
|
|
anchors.fill: parent
|
||
|
|
hoverEnabled: true
|
||
|
|
onClicked: {
|
||
|
|
root.displaySelected(index)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Empty state message
|
||
|
|
Label {
|
||
|
|
anchors.centerIn: parent
|
||
|
|
text: qsTr("No displays available")
|
||
|
|
color: "#9ca3af"
|
||
|
|
font.pixelSize: 12
|
||
|
|
visible: displayList.count === 0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Divider ────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
height: 1
|
||
|
|
color: "#374151"
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Buttons ────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
spacing: 12
|
||
|
|
Layout.fillWidth: true
|
||
|
|
|
||
|
|
Item { Layout.fillWidth: true }
|
||
|
|
|
||
|
|
Button {
|
||
|
|
text: qsTr("Cancel")
|
||
|
|
flat: true
|
||
|
|
onClicked: root.cancelled()
|
||
|
|
}
|
||
|
|
|
||
|
|
Button {
|
||
|
|
text: qsTr("Select")
|
||
|
|
enabled: displayList.currentIndex >= 0
|
||
|
|
onClicked: {
|
||
|
|
if (displayList.currentIndex >= 0) {
|
||
|
|
root.displaySelected(displayList.currentIndex)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|