aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules')
-rw-r--r--desktop/modules/monitors/MonitorsPage.qml250
1 files changed, 247 insertions, 3 deletions
diff --git a/desktop/modules/monitors/MonitorsPage.qml b/desktop/modules/monitors/MonitorsPage.qml
index 3df7b1c..df13d92 100644
--- a/desktop/modules/monitors/MonitorsPage.qml
+++ b/desktop/modules/monitors/MonitorsPage.qml
@@ -10,14 +10,258 @@
// GNU General Public License for more details.
import QtQuick
+import QtQuick.Controls as C
import "../.."
+import "../appearance" as A
+import "Mon.js" as M
+// The keep-or-revert bar while an Apply is live, the layout drawn to scale
+// (drag a monitor to move it; it snaps to the other's edges), and one card
+// per monitor: mode, scale, rotation, VRR, position.
Column {
id: page
required property QtObject mod
+ spacing: 14
+
+ // Leaving the page with an Apply unanswered is a Revert.
+ Component.onDestruction: if (page.mod.pending) page.mod.revert()
+
+ // The stock ComboBox paints from the palette, light by default; these
+ // are the desktop's colours, as in qtmaildir's TagsKeysPage.
+ component Combo: C.ComboBox {
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ palette {
+ base: Theme.surface; window: Theme.base; button: Theme.surface
+ text: Theme.text; buttonText: Theme.text; windowText: Theme.text
+ highlight: Theme.accent; highlightedText: Theme.base
+ mid: Qt.alpha(Theme.text, 0.2); dark: Qt.alpha(Theme.text, 0.2); light: Theme.surface
+ }
+ }
+
+ component Label: Text {
+ width: 90
+ anchors.verticalCenter: parent.verticalCenter
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ color: Theme.subtext
+ }
+
+ Rectangle {
+ visible: page.mod.pending
+ width: parent.width
+ height: 44
+ radius: 10
+ color: Qt.alpha(Theme.yellow, 0.18)
+ border.width: 1
+ border.color: Qt.alpha(Theme.yellow, 0.5)
+ Text {
+ anchors { left: parent.left; leftMargin: 14; verticalCenter: parent.verticalCenter }
+ text: "Keep these settings? Reverting in " + page.mod.countdown + "s"
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ color: Theme.text
+ }
+ Row {
+ anchors { right: parent.right; rightMargin: 10; verticalCenter: parent.verticalCenter }
+ spacing: 8
+ Button { text: "Revert"; onClicked: page.mod.revert() }
+ Button { text: "Keep"; onClicked: page.mod.keep() }
+ }
+ }
+
+ // ---- layout ----
+ Rectangle {
+ id: canvas
+ width: parent.width
+ height: 260
+ radius: 10
+ color: Theme.surface
+
+ // Logical rectangles, and the fit of their bounds into the canvas.
+ readonly property var rects: page.mod.draft.map(d => {
+ const p = M.xy(d), s = M.size(d.mode, d.scale, d.transform);
+ return { x: p.x, y: p.y, w: s.w, h: s.h };
+ })
+ readonly property real minX: Math.min.apply(null, rects.map(r => r.x).concat([0]))
+ readonly property real minY: Math.min.apply(null, rects.map(r => r.y).concat([0]))
+ readonly property real maxX: Math.max.apply(null, rects.map(r => r.x + r.w).concat([1]))
+ readonly property real maxY: Math.max.apply(null, rects.map(r => r.y + r.h).concat([1]))
+ readonly property real k: Math.min((width - 40) / (maxX - minX), (height - 40) / (maxY - minY))
+ readonly property real ox: (width - (maxX - minX) * k) / 2
+ readonly property real oy: (height - (maxY - minY) * k) / 2
+
+ Repeater {
+ model: page.mod.draft.length
+ Rectangle {
+ id: mon
+ required property int index
+ readonly property var r: canvas.rects[index] ?? { x: 0, y: 0, w: 1, h: 1 }
+ readonly property var d: page.mod.draft[index]
+ // The drag offset in canvas pixels, zero when not dragging.
+ property real dx: 0
+ property real dy: 0
+ x: canvas.ox + (r.x - canvas.minX) * canvas.k + dx
+ y: canvas.oy + (r.y - canvas.minY) * canvas.k + dy
+ width: r.w * canvas.k
+ height: r.h * canvas.k
+ radius: 6
+ color: Qt.alpha(Theme.accent, drag.pressed ? 0.35 : 0.18)
+ border.width: 1
+ border.color: Qt.alpha(Theme.accent, 0.7)
+
+ Column {
+ anchors.centerIn: parent
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: mon.d ? mon.d.name : ""
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; bold: true }
+ color: Theme.text
+ }
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: mon.d ? mon.d.model : ""
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 }
+ color: Theme.subtext
+ }
+ }
+
+ MouseArea {
+ id: drag
+ anchors.fill: parent
+ enabled: !page.mod.pending
+ cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
+ property point start
+ onPressed: m => start = mapToItem(canvas, m.x, m.y)
+ onPositionChanged: m => {
+ const p = mapToItem(canvas, m.x, m.y);
+ mon.dx = p.x - start.x;
+ mon.dy = p.y - start.y;
+ }
+ onReleased: {
+ const moved = { x: mon.r.x + mon.dx / canvas.k, y: mon.r.y + mon.dy / canvas.k, w: mon.r.w, h: mon.r.h };
+ const others = canvas.rects.filter((o, i) => i !== mon.index);
+ const s = M.snap(moved, others, 64);
+ mon.dx = 0;
+ mon.dy = 0;
+ if (s.x !== mon.r.x || s.y !== mon.r.y) page.mod.set(mon.index, "position", s.x + "x" + s.y);
+ }
+ }
+ }
+ }
+ }
+
+ // ---- cards ----
+ // Keyed on orig, a new array on every read, so Discard and a reload
+ // rebuild the cards and their controls rebind to the draft: Toggle and
+ // ComboBox set their own checked and currentIndex on a click.
+ Repeater {
+ model: page.mod.orig
+ Rectangle {
+ id: card
+ required property int index
+ readonly property var d: page.mod.draft[index]
+ readonly property bool auto: !!d && M.AUTO.includes(d.position)
+ objectName: d ? "mon:" + d.name : ""
+ width: page.width
+ height: rows.implicitHeight + 28
+ radius: 10
+ color: Theme.surface
+ visible: !!d
+
+ Column {
+ id: rows
+ anchors { fill: parent; margins: 14 }
+ spacing: 8
+
+ Text {
+ text: card.d ? card.d.name + " " + card.d.make + " " + card.d.model : ""
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; bold: true }
+ color: Theme.text
+ }
+
+ Row {
+ spacing: 8
+ Label { text: "Mode" }
+ Combo {
+ width: 220
+ model: card.d ? card.d.modes.map(m => m.replace("@", " @ ") + " Hz") : []
+ currentIndex: card.d ? card.d.modes.indexOf(card.d.mode) : -1
+ onActivated: i => page.mod.set(card.index, "mode", card.d.modes[i])
+ }
+ }
+
+ Row {
+ spacing: 8
+ Label { text: "Scale" }
+ // Read on every change: a click on Apply does not end editing.
+ A.Field {
+ value: card.d ? card.d.scale : ""
+ onValueChanged: if (card.d && value !== card.d.scale) page.mod.set(card.index, "scale", value)
+ }
+ }
+
+ Row {
+ spacing: 8
+ Label { text: "Rotation" }
+ Repeater {
+ model: [0, 90, 180, 270]
+ A.Tab {
+ required property int modelData
+ required property int index
+ text: modelData + "°"
+ selected: !!card.d && card.d.transform % 4 === index
+ onClicked: page.mod.set(card.index, "transform", (card.d.transform >= 4 ? 4 : 0) + index)
+ }
+ }
+ A.Toggle {
+ anchors.verticalCenter: parent.verticalCenter
+ label: "Flipped"
+ checked: !!card.d && card.d.transform >= 4
+ onToggled: page.mod.set(card.index, "transform", card.d.transform % 4 + (checked ? 4 : 0))
+ }
+ }
+
+ Row {
+ spacing: 8
+ Label { text: "VRR" }
+ A.Toggle {
+ anchors.verticalCenter: parent.verticalCenter
+ label: "Adaptive sync"
+ checked: !!card.d && card.d.vrr
+ onToggled: page.mod.set(card.index, "vrr", checked)
+ }
+ }
+
+ Row {
+ spacing: 8
+ Label { text: "Position" }
+ Combo {
+ width: 220
+ model: ["exact"].concat(M.AUTO)
+ currentIndex: card.auto ? M.AUTO.indexOf(card.d.position) + 1 : 0
+ // Rebound after a pick, so a later drag to an exact
+ // position shows here.
+ onActivated: i => {
+ page.mod.set(card.index, "position", i === 0 ? card.d.x + "x" + card.d.y : M.AUTO[i - 1]);
+ currentIndex = Qt.binding(() => card.auto ? M.AUTO.indexOf(card.d.position) + 1 : 0);
+ }
+ }
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ visible: !card.auto
+ text: card.d ? card.d.position.replace("x", ", ") + " drag in the layout to move" : ""
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ color: Theme.subtext
+ }
+ }
+ }
+ }
+ }
+
Text {
- text: page.mod.draft.map(d => d.name + " " + d.mode).join(", ")
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
- color: Theme.text
+ width: parent.width
+ wrapMode: Text.Wrap
+ text: "Apply tries the settings live. Keep writes sections/monitors.lua; Revert, leaving the page, "
+ + "or 15 seconds with no answer restores it. Only connected monitors are written."
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ color: Theme.subtext
}
}