328 lines
12 KiB
QML
328 lines
12 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Shapes
|
|
|
|
import org.kde.plasma.plasmoid
|
|
import org.kde.plasma.core as PlasmaCore
|
|
import org.kde.taskmanager as TaskManager
|
|
|
|
PlasmoidItem {
|
|
id: root
|
|
|
|
// Константы геометрии кнопок в стиле macOS
|
|
readonly property real buttonSize: 13
|
|
readonly property real buttonSpacing: 8
|
|
readonly property real horizontalPadding: 6
|
|
readonly property real verticalPadding: 4
|
|
|
|
readonly property real contentWidth: (buttonSize * 3) + (buttonSpacing * 2) + (horizontalPadding * 2)
|
|
readonly property real contentHeight: buttonSize + (verticalPadding * 2)
|
|
|
|
// Состояние текущего активного окна
|
|
property bool isWindowMaximized: false
|
|
property bool canClose: true
|
|
property bool canMinimize: true
|
|
property bool canMaximize: true
|
|
|
|
// Режим редактирования панели/рабочего стола Plasma
|
|
readonly property bool inEditMode: (Plasmoid.containment?.corona?.editMode ?? false) || Plasmoid.userConfiguring
|
|
readonly property bool shouldBeVisible: isWindowMaximized || inEditMode
|
|
|
|
// Интеграция с панелью Plasma 6
|
|
Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground
|
|
Plasmoid.status: shouldBeVisible ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.HiddenStatus
|
|
|
|
// Видимость и схлопывание виджета в 0px на панели, когда окно не развернуто и не в режиме редактирования
|
|
visible: shouldBeVisible
|
|
|
|
implicitWidth: shouldBeVisible ? contentWidth : 0
|
|
implicitHeight: shouldBeVisible ? contentHeight : 0
|
|
|
|
Layout.preferredWidth: shouldBeVisible ? contentWidth : 0
|
|
Layout.minimumWidth: shouldBeVisible ? contentWidth : 0
|
|
Layout.maximumWidth: shouldBeVisible ? contentWidth : 0
|
|
Layout.preferredHeight: shouldBeVisible ? contentHeight : 0
|
|
Layout.minimumHeight: shouldBeVisible ? contentHeight : 0
|
|
Layout.maximumHeight: shouldBeVisible ? contentHeight : 0
|
|
|
|
preferredRepresentation: fullRepresentation
|
|
compactRepresentation: fullRepresentation
|
|
|
|
// Отслеживание виртуальных рабочих столов
|
|
TaskManager.VirtualDesktopInfo {
|
|
id: virtualDesktopInfo
|
|
onCurrentDesktopChanged: root.updateWindowState()
|
|
}
|
|
|
|
// Отслеживание активностей KDE
|
|
TaskManager.ActivityInfo {
|
|
id: activityInfo
|
|
onCurrentActivityChanged: root.updateWindowState()
|
|
}
|
|
|
|
// Модель окон и задач
|
|
TaskManager.TasksModel {
|
|
id: tasksModel
|
|
virtualDesktop: virtualDesktopInfo.currentDesktop
|
|
activity: activityInfo.currentActivity
|
|
filterByVirtualDesktop: true
|
|
filterByActivity: true
|
|
groupMode: TaskManager.TasksModel.GroupDisabled
|
|
|
|
onActiveTaskChanged: root.updateWindowState()
|
|
onDataChanged: root.updateWindowState()
|
|
onRowsInserted: root.updateWindowState()
|
|
onRowsRemoved: root.updateWindowState()
|
|
onModelReset: root.updateWindowState()
|
|
onCountChanged: root.updateWindowState()
|
|
}
|
|
|
|
// Получение актуального QModelIndex активного окна
|
|
function getActiveTaskIndex() {
|
|
let idx = tasksModel.activeTask;
|
|
if (idx && idx.valid && tasksModel.data(idx, TaskManager.AbstractTasksModel.IsActive)) {
|
|
return idx;
|
|
}
|
|
for (let i = 0; i < tasksModel.rowCount(); ++i) {
|
|
let mIdx = tasksModel.index(i, 0);
|
|
if (tasksModel.data(mIdx, TaskManager.AbstractTasksModel.IsActive)) {
|
|
return mIdx;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Обновление состояния развернутости и активности окна
|
|
function updateWindowState() {
|
|
let idx = getActiveTaskIndex();
|
|
if (idx && idx.valid) {
|
|
let isLauncher = Boolean(tasksModel.data(idx, TaskManager.AbstractTasksModel.IsLauncher));
|
|
let isStartup = Boolean(tasksModel.data(idx, TaskManager.AbstractTasksModel.IsStartup));
|
|
let isActive = Boolean(tasksModel.data(idx, TaskManager.AbstractTasksModel.IsActive));
|
|
let isMax = Boolean(tasksModel.data(idx, TaskManager.AbstractTasksModel.IsMaximized));
|
|
let isFull = Boolean(tasksModel.data(idx, TaskManager.AbstractTasksModel.IsFullScreen));
|
|
let isMin = Boolean(tasksModel.data(idx, TaskManager.AbstractTasksModel.IsMinimized));
|
|
|
|
let closeable = tasksModel.data(idx, TaskManager.AbstractTasksModel.IsClosable);
|
|
let minimizable = tasksModel.data(idx, TaskManager.AbstractTasksModel.IsMinimizable);
|
|
let maximizable = tasksModel.data(idx, TaskManager.AbstractTasksModel.IsMaximizable);
|
|
|
|
canClose = (closeable !== undefined && closeable !== null) ? Boolean(closeable) : true;
|
|
canMinimize = (minimizable !== undefined && minimizable !== null) ? Boolean(minimizable) : true;
|
|
canMaximize = (maximizable !== undefined && maximizable !== null) ? Boolean(maximizable) : true;
|
|
|
|
// Виджет показывается ТОЛЬКО когда активное окно развернуто на весь экран и не свернуто
|
|
isWindowMaximized = (isActive && !isLauncher && !isStartup && (isMax || isFull) && !isMin);
|
|
} else {
|
|
isWindowMaximized = false;
|
|
}
|
|
}
|
|
|
|
// Действия над окном через TasksModel
|
|
function closeWindow() {
|
|
let idx = getActiveTaskIndex();
|
|
if (idx && idx.valid) {
|
|
tasksModel.requestClose(idx);
|
|
}
|
|
}
|
|
|
|
function minimizeWindow() {
|
|
let idx = getActiveTaskIndex();
|
|
if (idx && idx.valid) {
|
|
tasksModel.requestToggleMinimized(idx);
|
|
}
|
|
}
|
|
|
|
function toggleMaximizeWindow() {
|
|
let idx = getActiveTaskIndex();
|
|
if (idx && idx.valid) {
|
|
tasksModel.requestToggleMaximized(idx);
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
updateWindowState();
|
|
}
|
|
|
|
// Представление кнопок
|
|
fullRepresentation: Item {
|
|
id: buttonsContainer
|
|
|
|
implicitWidth: root.contentWidth
|
|
implicitHeight: root.contentHeight
|
|
visible: root.shouldBeVisible
|
|
|
|
// Подсветка-заглушка в режиме редактирования Plasma, если окно не развернуто
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: root.inEditMode && !root.isWindowMaximized
|
|
color: Qt.rgba(1, 1, 1, 0.08)
|
|
border.color: Qt.rgba(1, 1, 1, 0.35)
|
|
border.width: 1
|
|
radius: 4
|
|
}
|
|
|
|
// Отслеживание наведения курсора на группу кнопок
|
|
HoverHandler {
|
|
id: clusterHoverHandler
|
|
}
|
|
|
|
readonly property bool isClusterHovered: clusterHoverHandler.hovered
|
|
|
|
RowLayout {
|
|
id: buttonsRow
|
|
anchors.centerIn: parent
|
|
spacing: root.buttonSpacing
|
|
opacity: root.isWindowMaximized ? 1.0 : (root.inEditMode ? 0.6 : 0.0)
|
|
|
|
// Кнопка «Закрыть» (красная)
|
|
Rectangle {
|
|
id: closeButton
|
|
width: root.buttonSize
|
|
height: root.buttonSize
|
|
radius: root.buttonSize / 2
|
|
color: closeMouseArea.pressed ? "#bf4740" : "#ff5f56"
|
|
border.color: "#e0443e"
|
|
border.width: 1
|
|
antialiasing: true
|
|
opacity: root.canClose ? 1.0 : 0.4
|
|
enabled: root.canClose
|
|
|
|
// Символ крестика (✕)
|
|
Item {
|
|
anchors.centerIn: parent
|
|
width: 6
|
|
height: 6
|
|
opacity: buttonsContainer.isClusterHovered ? 1.0 : 0.0
|
|
Behavior on opacity {
|
|
NumberAnimation { duration: 120 }
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: 7
|
|
height: 1.2
|
|
color: "#4c0002"
|
|
radius: 0.6
|
|
rotation: 45
|
|
antialiasing: true
|
|
}
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: 7
|
|
height: 1.2
|
|
color: "#4c0002"
|
|
radius: 0.6
|
|
rotation: -45
|
|
antialiasing: true
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: closeMouseArea
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.closeWindow()
|
|
}
|
|
}
|
|
|
|
// Кнопка «Свернуть» (желтая)
|
|
Rectangle {
|
|
id: minimizeButton
|
|
width: root.buttonSize
|
|
height: root.buttonSize
|
|
radius: root.buttonSize / 2
|
|
color: minMouseArea.pressed ? "#bfa020" : "#ffbd2e"
|
|
border.color: "#dea125"
|
|
border.width: 1
|
|
antialiasing: true
|
|
opacity: root.canMinimize ? 1.0 : 0.4
|
|
enabled: root.canMinimize
|
|
|
|
// Символ минуса (−)
|
|
Item {
|
|
anchors.centerIn: parent
|
|
width: 6
|
|
height: 6
|
|
opacity: buttonsContainer.isClusterHovered ? 1.0 : 0.0
|
|
Behavior on opacity {
|
|
NumberAnimation { duration: 120 }
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: 7
|
|
height: 1.2
|
|
color: "#7d4f00"
|
|
radius: 0.6
|
|
antialiasing: true
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: minMouseArea
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.minimizeWindow()
|
|
}
|
|
}
|
|
|
|
// Кнопка «Развернуть / Восстановить» (зеленая)
|
|
Rectangle {
|
|
id: maximizeButton
|
|
width: root.buttonSize
|
|
height: root.buttonSize
|
|
radius: root.buttonSize / 2
|
|
color: maxMouseArea.pressed ? "#1da030" : "#27c93f"
|
|
border.color: "#1aab29"
|
|
border.width: 1
|
|
antialiasing: true
|
|
opacity: root.canMaximize ? 1.0 : 0.4
|
|
enabled: root.canMaximize
|
|
|
|
// Символ стрелок macOS (два диагональных треугольника)
|
|
Shape {
|
|
anchors.centerIn: parent
|
|
width: 6
|
|
height: 6
|
|
opacity: buttonsContainer.isClusterHovered ? 1.0 : 0.0
|
|
Behavior on opacity {
|
|
NumberAnimation { duration: 120 }
|
|
}
|
|
asynchronous: false
|
|
|
|
ShapePath {
|
|
strokeWidth: 0
|
|
strokeColor: "transparent"
|
|
fillColor: "#0d5516"
|
|
startX: 6
|
|
startY: 0
|
|
PathLine { x: 1.8; y: 0 }
|
|
PathLine { x: 6; y: 4.2 }
|
|
PathLine { x: 6; y: 0 }
|
|
}
|
|
ShapePath {
|
|
strokeWidth: 0
|
|
strokeColor: "transparent"
|
|
fillColor: "#0d5516"
|
|
startX: 0
|
|
startY: 6
|
|
PathLine { x: 4.2; y: 6 }
|
|
PathLine { x: 0; y: 1.8 }
|
|
PathLine { x: 0; y: 6 }
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: maxMouseArea
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.toggleMaximizeWindow()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|