[plasma/kscreen] kcm: kcm: allow moving the displays pixel-by-pixel using arrow keys
Xaver Hugl <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit c7b9b8b5f94f6caf3ff38f61eb9672d6921d8617 by Xaver Hugl, on behalf of Antti Savolainen.
Committed on 31/07/2026 at 17:33.
Pushed by zamundaaa into branch 'master'.
kcm: allow moving the displays pixel-by-pixel using arrow keys
Also fade out the badge when the coordinates are shown
BUG: 519347
BUG: 521731
M +27 -10 kcm/output_model.cpp
M +1 -0 kcm/output_model.h
M +46 -2 kcm/ui/Output.qml
https://invent.kde.org/plasma/kscreen/-/commit/c7b9b8b5f94f6caf3ff38f61eb9672d6921d8617
diff --git a/kcm/output_model.cpp b/kcm/output_model.cpp
index 723a91ac..1e866160 100644
--- a/kcm/output_model.cpp
+++ b/kcm/output_model.cpp
@@ -178,6 +178,32 @@ QVariant OutputModel::data(const QModelIndex &index, int role) const
return QVariant();
}
+bool OutputModel::setPosition(int outputIndex, const QPoint &valToPoint, bool snapEnabled)
+{
+ if (outputIndex < 0 || outputIndex >= m_outputs.count()) {
+ return false;
+ }
+
+ Output &output = m_outputs[outputIndex];
+ QPoint val = valToPoint;
+
+ if (output.pos == val) {
+ return false;
+ }
+
+ if (snapEnabled) {
+ snap(output, val);
+ }
+
+ output.pos = val;
+ updatePositions();
+
+ Q_EMIT positionChanged();
+ const QModelIndex index = createIndex(outputIndex, 0);
+ Q_EMIT dataChanged(index, index, {PositionRole});
+ return true;
+}
+
bool OutputModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= m_outputs.count()) {
@@ -188,16 +214,7 @@ bool OutputModel::setData(const QModelIndex &index, const QVariant &value, int r
switch (role) {
case PositionRole:
if (value.canConvert<QPoint>()) {
- QPoint val = value.toPoint();
- if (output.pos == val) {
- return false;
- }
- snap(output, val);
- m_outputs[index.row()].pos = val;
- updatePositions();
- Q_EMIT positionChanged();
- Q_EMIT dataChanged(index, index, {role});
- return true;
+ return setPosition(index.row(), value.toPoint());
}
break;
case EnabledRole:
diff --git a/kcm/output_model.h b/kcm/output_model.h
index 8456b2bb..8eb1d994 100644
--- a/kcm/output_model.h
+++ b/kcm/output_model.h
@@ -93,6 +93,7 @@ public:
bool positionsNormalized() const;
bool isMoving() const;
+ Q_INVOKABLE bool setPosition(int outputIndex, const QPoint &valToPoint, bool snapEnabled = true);
Q_SIGNALS:
void positionChanged();
diff --git a/kcm/ui/Output.qml b/kcm/ui/Output.qml
index d91fcbf9..7ef17f81 100644
--- a/kcm/ui/Output.qml
+++ b/kcm/ui/Output.qml
@@ -22,6 +22,7 @@ Item {
model.resolution.width.toString(), model.resolution.height.toString(), Math.round(model.scale * 100.0))
property string textWithoutScale: i18nc("Width, height; separated with no-break space", "(%1 × %2)",
model.resolution.width.toString(), model.resolution.height.toString())
+ property bool arrowKeyHeld: false
onIsSelectedChanged: {
if (isSelected) {
@@ -122,7 +123,7 @@ Item {
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
number: model.numberByConnector
- visible: labelContainer.height >= implicitHeight + Kirigami.Units.smallSpacing * 2
+ visible: labelContainer.height >= implicitHeight + Kirigami.Units.smallSpacing * 2 && !(tapHandler.isLongPressed || dragHandler.active || output.arrowKeyHeld)
}
QQC2.Label {
@@ -221,7 +222,7 @@ Item {
radius: Kirigami.Units.cornerRadius
opacity: model.enabled &&
- (tapHandler.isLongPressed || dragHandler.active) ? 0.9 : 0.0
+ (tapHandler.isLongPressed || dragHandler.active || output.arrowKeyHeld) ? 0.9 : 0.0
color: Kirigami.Theme.disabledTextColor
@@ -282,6 +283,7 @@ Item {
onPressedChanged: {
if (pressed) {
root.selectedOutput = model.index;
+ output.forceActiveFocus()
dragStartPosition = Qt.point(output.x, output.y)
} else {
isLongPressed = false;
@@ -309,5 +311,47 @@ Item {
}
}
}
+
+ Timer {
+ id: uiLinger
+ interval: 600
+ onTriggered: {
+ arrowKeyHeld = false
+ }
+ }
+
+ Keys.onPressed: (event) => {
+ if (!output.interactive) {
+ return
+ }
+
+ uiLinger.stop()
+ const delta = Qt.point(0, 0);
+ if (event.key === Qt.Key_Left) {
+ delta.x = -1
+ arrowKeyHeld = true
+ model.interactiveMove = true;
+ } else if (event.key === Qt.Key_Right) {
+ delta.x = 1
+ arrowKeyHeld = true
+ model.interactiveMove = true;
+ } else if (event.key === Qt.Key_Up) {
+ delta.y = -1
+ arrowKeyHeld = true
+ model.interactiveMove = true;
+ } else if (event.key === Qt.Key_Down) {
+ delta.y = 1
+ arrowKeyHeld = true
+ model.interactiveMove = true;
+ }
+ kcm.outputModel.sourceModel.setPosition(model.index, Qt.point(model.position.x + delta.x, model.position.y + delta.y), false)
+ }
+ Keys.onReleased: (event) => {
+ if (event.isAutoRepeat) {
+ return
+ }
+ model.interactiveMove = false
+ uiLinger.start()
+ }
}