311 lines
17 KiB
QML
311 lines
17 KiB
QML
|
|
import QtQuick 2.15
|
||
|
|
import QtQuick.Layouts 1.15
|
||
|
|
import QtQuick.Controls 2.15
|
||
|
|
import org.kde.plasma.plasmoid 2.0
|
||
|
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||
|
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||
|
|
import org.kde.plasma.private.pager 2.0
|
||
|
|
|
||
|
|
Item {
|
||
|
|
id: root
|
||
|
|
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
// CONFIGURATION
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
readonly property bool showPreviews: plasmoid.configuration.showWindowPreviews
|
||
|
|
readonly property bool showIcons: plasmoid.configuration.showWindowIcons
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
// PAGER MODEL (native KDE plugin with real window geometry)
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
PagerModel {
|
||
|
|
id: pagerModel
|
||
|
|
enabled: root.visible
|
||
|
|
showDesktop: false
|
||
|
|
pagerType: PagerModel.VirtualDesktops
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
// HELPER FUNCTIONS
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
function currentDesktopName() {
|
||
|
|
if (pagerModel.count > 0 && pagerModel.currentPage >= 0) {
|
||
|
|
var idx = pagerModel.index(pagerModel.currentPage, 0)
|
||
|
|
var name = pagerModel.data(idx, Qt.DisplayRole)
|
||
|
|
return name || ("Desktop " + (pagerModel.currentPage + 1))
|
||
|
|
}
|
||
|
|
return "Desktop"
|
||
|
|
}
|
||
|
|
|
||
|
|
function addDesktop() { pagerModel.addDesktop() }
|
||
|
|
function removeDesktop() { pagerModel.removeDesktop() }
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
// HOVER TIMERS
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
property bool hoverCompact: false
|
||
|
|
property bool hoverPopup: false
|
||
|
|
Timer { id: openTimer; interval: 80; onTriggered: plasmoid.expanded = true }
|
||
|
|
Timer { id: closeTimer; interval: 300; onTriggered: if (!hoverCompact && !hoverPopup) plasmoid.expanded = false }
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
// COMPACT REPRESENTATION
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
Plasmoid.compactRepresentation: MouseArea {
|
||
|
|
id: compactArea
|
||
|
|
Layout.minimumWidth: compactLabel.implicitWidth + 16
|
||
|
|
hoverEnabled: true
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
anchors.fill: parent
|
||
|
|
color: parent.containsMouse ? PlasmaCore.Theme.highlightColor : "transparent"
|
||
|
|
opacity: 0.2; radius: 3
|
||
|
|
}
|
||
|
|
|
||
|
|
PlasmaComponents.Label {
|
||
|
|
id: compactLabel
|
||
|
|
anchors.centerIn: parent
|
||
|
|
text: currentDesktopName()
|
||
|
|
font.bold: true
|
||
|
|
}
|
||
|
|
|
||
|
|
onEntered: { hoverCompact = true; closeTimer.stop(); openTimer.start() }
|
||
|
|
onExited: { hoverCompact = false; openTimer.stop(); if (!hoverPopup) closeTimer.start() }
|
||
|
|
onClicked: { openTimer.stop(); closeTimer.stop(); plasmoid.expanded = !plasmoid.expanded }
|
||
|
|
onWheel: {
|
||
|
|
var next = wheel.angleDelta.y > 0
|
||
|
|
? (pagerModel.currentPage - 1 + pagerModel.count) % pagerModel.count
|
||
|
|
: (pagerModel.currentPage + 1) % pagerModel.count
|
||
|
|
pagerModel.changePage(next)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
// FULL REPRESENTATION
|
||
|
|
// ─────────────────────────────────────────────────────────────────────────
|
||
|
|
Plasmoid.fullRepresentation: Item {
|
||
|
|
id: popup
|
||
|
|
|
||
|
|
readonly property int cols: Math.max(1, Math.ceil(Math.sqrt(pagerModel.count)))
|
||
|
|
readonly property int rows: Math.max(1, Math.ceil(pagerModel.count / cols))
|
||
|
|
readonly property real deskW: 130
|
||
|
|
readonly property real deskH: pagerModel.pagerItemSize.height > 0
|
||
|
|
? deskW * pagerModel.pagerItemSize.height / pagerModel.pagerItemSize.width
|
||
|
|
: deskW * 9 / 16
|
||
|
|
readonly property real scaleX: deskW / Math.max(1, pagerModel.pagerItemSize.width)
|
||
|
|
readonly property real scaleY: deskH / Math.max(1, pagerModel.pagerItemSize.height)
|
||
|
|
|
||
|
|
Layout.preferredWidth: cols * (deskW + 8) + 32
|
||
|
|
Layout.preferredHeight: rows * (deskH + 8) + 56
|
||
|
|
Layout.minimumWidth: 200
|
||
|
|
Layout.minimumHeight: 120
|
||
|
|
|
||
|
|
MouseArea {
|
||
|
|
anchors.fill: parent
|
||
|
|
hoverEnabled: true
|
||
|
|
onEntered: { hoverPopup = true; closeTimer.stop() }
|
||
|
|
onExited: { hoverPopup = false; if (!hoverCompact) closeTimer.start() }
|
||
|
|
}
|
||
|
|
|
||
|
|
ColumnLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.margins: 10
|
||
|
|
spacing: 8
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────
|
||
|
|
// DESKTOP GRID
|
||
|
|
// ─────────────────────────────────────────────────────────────
|
||
|
|
Grid {
|
||
|
|
id: desktopGrid
|
||
|
|
Layout.fillWidth: true
|
||
|
|
Layout.fillHeight: true
|
||
|
|
Layout.alignment: Qt.AlignHCenter
|
||
|
|
columns: popup.cols
|
||
|
|
spacing: 6
|
||
|
|
|
||
|
|
Repeater {
|
||
|
|
id: repeater
|
||
|
|
model: pagerModel
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
id: desktop
|
||
|
|
width: popup.deskW
|
||
|
|
height: popup.deskH
|
||
|
|
|
||
|
|
readonly property string desktopName: model.display || ("Desktop " + (index + 1))
|
||
|
|
readonly property bool isActive: index === pagerModel.currentPage
|
||
|
|
|
||
|
|
color: isActive ? Qt.darker(PlasmaCore.Theme.highlightColor, 1.3) : PlasmaCore.Theme.backgroundColor
|
||
|
|
border.width: isActive ? 2 : 1
|
||
|
|
border.color: isActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.disabledTextColor
|
||
|
|
radius: 4
|
||
|
|
clip: true
|
||
|
|
|
||
|
|
MouseArea {
|
||
|
|
anchors.fill: parent
|
||
|
|
hoverEnabled: true
|
||
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||
|
|
onEntered: { hoverPopup = true; closeTimer.stop(); parent.opacity = 1 }
|
||
|
|
onExited: parent.opacity = 0.92
|
||
|
|
onClicked: mouse.button === Qt.RightButton ? ctxMenu.popup() : (pagerModel.changePage(index), plasmoid.expanded = false)
|
||
|
|
}
|
||
|
|
|
||
|
|
opacity: 0.92
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────
|
||
|
|
// WINDOWS with real geometry
|
||
|
|
// ─────────────────────────────────────────────────
|
||
|
|
Item {
|
||
|
|
anchors.fill: parent
|
||
|
|
anchors.margins: 2
|
||
|
|
clip: true
|
||
|
|
visible: showPreviews
|
||
|
|
|
||
|
|
Repeater {
|
||
|
|
model: TasksModel
|
||
|
|
|
||
|
|
Rectangle {
|
||
|
|
readonly property rect geo: model.Geometry
|
||
|
|
readonly property bool minimized: model.IsMinimized === true
|
||
|
|
|
||
|
|
x: Math.round(geo.x * popup.scaleX)
|
||
|
|
y: Math.round(geo.y * popup.scaleY)
|
||
|
|
width: Math.max(8, Math.round(geo.width * popup.scaleX))
|
||
|
|
height: Math.max(6, Math.round(geo.height * popup.scaleY))
|
||
|
|
visible: !minimized
|
||
|
|
|
||
|
|
color: model.IsActive ? Qt.rgba(1,1,1,0.4) : Qt.rgba(1,1,1,0.2)
|
||
|
|
border.width: 1
|
||
|
|
border.color: model.IsActive ? PlasmaCore.Theme.highlightColor : PlasmaCore.Theme.textColor
|
||
|
|
radius: 2
|
||
|
|
|
||
|
|
PlasmaCore.IconItem {
|
||
|
|
visible: showIcons && parent.width > 16 && parent.height > 12
|
||
|
|
anchors.centerIn: parent
|
||
|
|
width: Math.min(parent.width - 4, parent.height - 4, 20)
|
||
|
|
height: width
|
||
|
|
source: model.decoration || "application-x-executable"
|
||
|
|
usesPlasmaTheme: false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────
|
||
|
|
// DESKTOP LABEL (centered)
|
||
|
|
// ─────────────────────────────────────────────────
|
||
|
|
Column {
|
||
|
|
anchors.centerIn: parent
|
||
|
|
visible: !showPreviews
|
||
|
|
PlasmaComponents.Label {
|
||
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
||
|
|
text: index + 1
|
||
|
|
font.bold: true; font.pixelSize: 18
|
||
|
|
color: desktop.isActive ? PlasmaCore.Theme.highlightedTextColor : PlasmaCore.Theme.textColor
|
||
|
|
}
|
||
|
|
PlasmaComponents.Label {
|
||
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
||
|
|
text: desktop.desktopName
|
||
|
|
font.pixelSize: 10
|
||
|
|
color: desktop.isActive ? PlasmaCore.Theme.highlightedTextColor : PlasmaCore.Theme.textColor
|
||
|
|
width: popup.deskW - 10
|
||
|
|
elide: Text.ElideRight
|
||
|
|
horizontalAlignment: Text.AlignHCenter
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Badge when previews enabled
|
||
|
|
Rectangle {
|
||
|
|
visible: showPreviews
|
||
|
|
anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 3 }
|
||
|
|
width: badgeLbl.implicitWidth + 12; height: badgeLbl.implicitHeight + 4
|
||
|
|
color: Qt.rgba(0,0,0,0.7); radius: 3
|
||
|
|
PlasmaComponents.Label {
|
||
|
|
id: badgeLbl; anchors.centerIn: parent
|
||
|
|
text: (index + 1) + " " + desktop.desktopName
|
||
|
|
font.pixelSize: 11; font.bold: true; color: "white"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Context menu
|
||
|
|
Menu {
|
||
|
|
id: ctxMenu
|
||
|
|
MenuItem {
|
||
|
|
text: "Rename..."
|
||
|
|
icon.name: "edit-rename"
|
||
|
|
onTriggered: { renameDlg.idx = index; renameDlg.open(); renameField.text = desktop.desktopName; renameField.selectAll() }
|
||
|
|
}
|
||
|
|
MenuItem {
|
||
|
|
text: "Delete"
|
||
|
|
icon.name: "edit-delete"
|
||
|
|
enabled: pagerModel.count > 1
|
||
|
|
onTriggered: pagerModel.removeDesktop()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────
|
||
|
|
// BOTTOM BAR
|
||
|
|
// ─────────────────────────────────────────────────────────────
|
||
|
|
MouseArea {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
Layout.preferredHeight: 36
|
||
|
|
hoverEnabled: true
|
||
|
|
onEntered: { hoverPopup = true; closeTimer.stop() }
|
||
|
|
onExited: { hoverPopup = false; if (!hoverCompact) closeTimer.start() }
|
||
|
|
|
||
|
|
RowLayout {
|
||
|
|
anchors.fill: parent
|
||
|
|
spacing: 8
|
||
|
|
PlasmaComponents.Button {
|
||
|
|
icon.name: "list-add"; text: "Add"
|
||
|
|
onClicked: pagerModel.addDesktop()
|
||
|
|
}
|
||
|
|
Item { Layout.fillWidth: true }
|
||
|
|
PlasmaComponents.Label {
|
||
|
|
text: pagerModel.count + " desktops"
|
||
|
|
opacity: 0.6; font.pixelSize: 11
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────────────────────────
|
||
|
|
// RENAME DIALOG
|
||
|
|
// ─────────────────────────────────────────────────────────────────
|
||
|
|
Dialog {
|
||
|
|
id: renameDlg
|
||
|
|
property int idx: 0
|
||
|
|
title: "Rename Desktop"
|
||
|
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||
|
|
anchors.centerIn: parent
|
||
|
|
contentItem: PlasmaComponents.TextField {
|
||
|
|
id: renameField
|
||
|
|
Layout.preferredWidth: 180
|
||
|
|
onAccepted: renameDlg.accept()
|
||
|
|
}
|
||
|
|
onAccepted: {
|
||
|
|
if (renameField.text.trim()) {
|
||
|
|
// Use KWin DBus to rename
|
||
|
|
var desktopItem = repeater.itemAt(idx)
|
||
|
|
if (desktopItem) {
|
||
|
|
execSource.connectSource("qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '" +
|
||
|
|
pagerModel.data(pagerModel.index(idx, 0), 0x0100 + 1) + "' '" +
|
||
|
|
renameField.text.trim().replace(/'/g, "'\\''") + "'")
|
||
|
|
}
|
||
|
|
pagerModel.refresh()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
PlasmaCore.DataSource {
|
||
|
|
id: execSource
|
||
|
|
engine: "executable"
|
||
|
|
onNewData: disconnectSource(sourceName)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|