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() } } } } }