Add task panel to Folio drawer

Show running windows in the app drawer with previews, window
actions, and virtual desktop controls. Hide dock task icons while
the panel is open so the same windows are not duplicated.

Use TaskManager to move windows between desktops, and expose
desktop activation through the containment.
This commit is contained in:
Marco Allegretti 2026-04-29 12:10:31 +02:00
parent c469ad49b6
commit 4077292801
7 changed files with 803 additions and 5 deletions

View file

@ -83,6 +83,7 @@ find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
find_package(Plasma CONFIG REQUIRED) find_package(Plasma CONFIG REQUIRED)
find_package(PlasmaQuick CONFIG REQUIRED) find_package(PlasmaQuick CONFIG REQUIRED)
find_package(PlasmaActivities CONFIG REQUIRED) find_package(PlasmaActivities CONFIG REQUIRED)
find_package(LibTaskManager CONFIG REQUIRED)
find_package(KF6Screen CONFIG REQUIRED) find_package(KF6Screen CONFIG REQUIRED)
find_package(KWayland CONFIG REQUIRED) find_package(KWayland CONFIG REQUIRED)
find_package(KPipeWire ${PROJECT_DEP_VERSION} REQUIRED) find_package(KPipeWire ${PROJECT_DEP_VERSION} REQUIRED)

View file

@ -20,6 +20,7 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
qml/main.qml qml/main.qml
qml/PipeWireThumbnail.qml qml/PipeWireThumbnail.qml
qml/PlaceholderDelegate.qml qml/PlaceholderDelegate.qml
qml/RunningAppsPanel.qml
qml/WidgetDragItem.qml qml/WidgetDragItem.qml
qml/config.qml qml/config.qml
CPP_SOURCES CPP_SOURCES
@ -95,6 +96,7 @@ target_link_libraries(org.kde.plasma.mobile.homescreen.folio PRIVATE
KF6::Service KF6::Service
KF6::KIOGui KF6::KIOGui
KF6::Notifications KF6::Notifications
PW::LibTaskManager
Plasma::KWaylandClient Plasma::KWaylandClient
KF6::WindowSystem KF6::WindowSystem
KF6::JobWidgets KF6::JobWidgets

View file

@ -4,6 +4,8 @@
#include "homescreen.h" #include "homescreen.h"
#include <virtualdesktopinfo.h>
#include <KWindowSystem> #include <KWindowSystem>
#include <QDBusConnection> #include <QDBusConnection>
@ -96,4 +98,14 @@ void HomeScreen::triggerOverview() const
QDBusConnection::sessionBus().send(message); QDBusConnection::sessionBus().send(message);
} }
void HomeScreen::activateVirtualDesktop(const QVariant &desktop) const
{
if (!desktop.isValid() || desktop.toString().isEmpty()) {
return;
}
TaskManager::VirtualDesktopInfo virtualDesktopInfo;
virtualDesktopInfo.requestActivate(desktop);
}
#include "homescreen.moc" #include "homescreen.moc"

View file

@ -6,6 +6,7 @@
#include <Plasma/Containment> #include <Plasma/Containment>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QVariant>
#include "applicationlistmodel.h" #include "applicationlistmodel.h"
#include "delegatetoucharea.h" #include "delegatetoucharea.h"
@ -49,6 +50,7 @@ public:
void configChanged() override; void configChanged() override;
Q_INVOKABLE void triggerOverview() const; Q_INVOKABLE void triggerOverview() const;
Q_INVOKABLE void activateVirtualDesktop(const QVariant &desktop) const;
FolioSettings *folioSettings(); FolioSettings *folioSettings();
HomeScreenState *homeScreenState(); HomeScreenState *homeScreenState();

View file

@ -24,12 +24,14 @@ MouseArea {
property MobileShell.MaskManager maskManager property MobileShell.MaskManager maskManager
property var homeScreen property var homeScreen
property bool suppressRunningTasks: false
signal delegateDragRequested(var item) signal delegateDragRequested(var item)
// Convergence mode: show running apps alongside favourites // Convergence mode: show running apps alongside favourites
readonly property bool convergenceMode: ShellSettings.Settings.convergenceModeEnabled readonly property bool convergenceMode: ShellSettings.Settings.convergenceModeEnabled
readonly property int totalItemCount: repeater.count + (convergenceMode ? taskRepeater.count : 0) readonly property bool showRunningTasks: convergenceMode && !suppressRunningTasks
readonly property int totalItemCount: repeater.count + (showRunningTasks ? taskRepeater.count : 0)
// In convergence mode, size icons to fit the dock bar instead of using page grid cells // In convergence mode, size icons to fit the dock bar instead of using page grid cells
readonly property real dockCellWidth: convergenceMode ? root.height : folio.HomeScreenState.pageCellWidth readonly property real dockCellWidth: convergenceMode ? root.height : folio.HomeScreenState.pageCellWidth
@ -59,7 +61,7 @@ MouseArea {
: root.width / 2 : root.width / 2
// Visible spacer between pinned favourites and running tasks // Visible spacer between pinned favourites and running tasks
readonly property bool showSpacer: convergenceMode && repeater.count > 0 && taskRepeater.count > 0 readonly property bool showSpacer: showRunningTasks && repeater.count > 0 && taskRepeater.count > 0
property real spacerWidth: showSpacer ? Kirigami.Units.largeSpacing * 2 : 0 property real spacerWidth: showSpacer ? Kirigami.Units.largeSpacing * 2 : 0
Behavior on spacerWidth { Behavior on spacerWidth {
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad } NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
@ -936,7 +938,7 @@ MouseArea {
Repeater { Repeater {
id: taskRepeater id: taskRepeater
model: root.convergenceMode ? tasksModel : null model: root.showRunningTasks ? tasksModel : null
delegate: Item { delegate: Item {
id: taskDelegate id: taskDelegate

View file

@ -0,0 +1,762 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.components 3.0 as PC3
import org.kde.taskmanager as TaskManager
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
Item {
id: root
required property var folio
readonly property bool hasTasks: allTasksModel.count > 0
property bool sortByName: false
property int dragTargetDesktopIndex: -1
property string pendingMoveTaskKey: ""
property string pendingMoveTargetName: ""
signal taskActivated()
function taskStorageId(taskModel) {
var id = taskModel ? taskModel.AppId || "" : ""
if (id && !id.endsWith(".desktop")) {
id += ".desktop"
}
return id
}
function taskKey(taskModel) {
const winIds = taskModel && taskModel.WinIdList ? taskModel.WinIdList : []
if (winIds.length > 0) {
var key = ""
for (var i = 0; i < winIds.length; ++i) {
key += String(winIds[i]) + "|"
}
return key
}
return String(taskModel ? taskModel.AppId || "" : "") + "|" + String(taskModel ? taskModel.display || "" : "")
}
function markTaskMove(taskKey, desktopIndex) {
pendingMoveTaskKey = taskKey
pendingMoveTargetName = desktopName(desktopIndex)
pendingMoveResetTimer.restart()
}
function mixColor(base, overlay, ratio) {
return Qt.rgba(
base.r + (overlay.r - base.r) * ratio,
base.g + (overlay.g - base.g) * ratio,
base.b + (overlay.b - base.b) * ratio,
base.a + (overlay.a - base.a) * ratio)
}
function desktopName(index) {
const names = virtualDesktopInfo.desktopNames
if (names && names.length > index && String(names[index]).length > 0) {
return String(names[index])
}
return "Desktop " + (index + 1)
}
function isCurrentDesktop(desktopId) {
return String(desktopId) === String(virtualDesktopInfo.currentDesktop)
}
Timer {
id: pendingMoveResetTimer
interval: 1200
onTriggered: {
root.pendingMoveTaskKey = ""
root.pendingMoveTargetName = ""
}
}
component PanelIconButton: MouseArea {
id: button
property string iconName
property string toolTipText
property bool checked: false
signal triggered()
width: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
height: width
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
opacity: enabled ? 1 : 0.35
onClicked: button.triggered()
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: button.containsPress
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.16)
: button.checked
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, button.containsMouse ? 0.22 : 0.16)
: button.containsMouse
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
: "transparent"
Behavior on color {
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
}
Kirigami.Icon {
anchors.centerIn: parent
width: Kirigami.Units.iconSizes.small
height: width
source: button.iconName
active: button.containsMouse || button.checked
}
PC3.ToolTip {
text: button.toolTipText
visible: button.containsMouse && button.toolTipText.length > 0
}
}
TaskManager.VirtualDesktopInfo { id: virtualDesktopInfo }
TaskManager.ActivityInfo { id: activityInfo }
TaskManager.TasksModel {
id: allTasksModel
filterByVirtualDesktop: false
filterByActivity: true
filterNotMaximized: false
filterByScreen: true
filterHidden: false
activity: activityInfo.currentActivity
groupMode: TaskManager.TasksModel.GroupApplications
}
TaskManager.TasksModel {
id: tasksModel
filterByVirtualDesktop: true
filterByActivity: true
filterNotMaximized: false
filterByScreen: true
filterHidden: false
virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity
groupMode: TaskManager.TasksModel.GroupApplications
sortMode: root.sortByName ? TaskManager.TasksModel.SortAlpha : TaskManager.TasksModel.SortLastActivated
}
Rectangle {
id: panelShadow
anchors.fill: panelBackground
anchors.topMargin: 2
radius: panelBackground.radius
color: Qt.rgba(0, 0, 0, 0.35)
}
Rectangle {
id: panelBackground
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
border.width: 1
border.pixelAligned: false
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.14)
}
MouseArea {
anchors.fill: parent
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
PC3.Label {
Layout.fillWidth: true
text: i18n("Running")
font.weight: Font.Medium
elide: Text.ElideRight
}
Row {
spacing: 1
Repeater {
model: [
{ label: i18n("Recent"), byName: false },
{ label: i18n("Name"), byName: true }
]
delegate: MouseArea {
id: sortButton
required property var modelData
readonly property bool checked: root.sortByName === modelData.byName
width: Math.max(Kirigami.Units.gridUnit * 3.5, label.implicitWidth + Kirigami.Units.smallSpacing * 3)
height: Kirigami.Units.gridUnit * 1.6
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.sortByName = modelData.byName
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: sortButton.checked
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, sortButton.containsMouse ? 0.28 : 0.2)
: sortButton.containsMouse
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
: "transparent"
}
PC3.Label {
id: label
anchors.centerIn: parent
text: sortButton.modelData.label
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
color: sortButton.checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
}
}
}
}
}
RowLayout {
id: desktopStrip
Layout.fillWidth: true
visible: virtualDesktopInfo.numberOfDesktops > 1
spacing: Kirigami.Units.smallSpacing
PC3.Label {
text: i18n("Desktops")
opacity: 0.7
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
}
Item {
id: desktopDropSurface
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.4
function desktopIndexAt(x) {
if (virtualDesktopInfo.numberOfDesktops <= 0) {
return -1
}
const localX = desktopRow.mapFromItem(desktopDropSurface, x, 0).x
var nearestIndex = -1
var nearestDistance = Number.MAX_VALUE
for (var i = 0; i < virtualDesktopInfo.numberOfDesktops; ++i) {
const item = desktopRepeater.itemAt(i)
if (!item) {
continue
}
if (localX >= item.x && localX <= item.x + item.width) {
return i
}
const center = item.x + item.width / 2
const distance = Math.abs(localX - center)
if (distance < nearestDistance) {
nearestIndex = i
nearestDistance = distance
}
}
return nearestIndex
}
Row {
id: desktopRow
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
Repeater {
id: desktopRepeater
model: virtualDesktopInfo.desktopIds
delegate: MouseArea {
id: desktopButton
required property int index
required property var modelData
readonly property bool checked: root.isCurrentDesktop(modelData)
readonly property string desktopLabel: root.desktopName(index)
readonly property bool dragHovered: desktopDropArea.containsDrag && root.dragTargetDesktopIndex === index
width: Math.max(Kirigami.Units.gridUnit * 5.5, (desktopRow.width / Math.max(1, virtualDesktopInfo.numberOfDesktops)) - Kirigami.Units.smallSpacing)
height: desktopRow.height
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.folio.activateVirtualDesktop(modelData)
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
scale: desktopButton.dragHovered ? 1.03 : 1
color: desktopButton.checked
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, desktopButton.containsMouse || desktopButton.dragHovered ? 0.32 : 0.24)
: desktopButton.dragHovered
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.18)
: desktopButton.containsMouse
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.045)
border.width: 1
border.pixelAligned: false
border.color: desktopButton.checked || desktopButton.dragHovered
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.55)
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.14)
Behavior on color {
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Kirigami.Units.smallSpacing / 2
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
radius: height / 2
visible: desktopButton.checked
color: Kirigami.Theme.highlightColor
}
PC3.Label {
anchors.centerIn: parent
width: parent.width - Kirigami.Units.smallSpacing * 2
text: desktopButton.desktopLabel
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
font.weight: desktopButton.checked || desktopButton.dragHovered ? Font.Medium : Font.Normal
font.pixelSize: Math.min(Kirigami.Theme.defaultFont.pixelSize, parent.height * 0.42)
color: desktopButton.checked || desktopButton.dragHovered ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
}
}
}
}
DropArea {
id: desktopDropArea
anchors.fill: parent
keys: ["folio-running-task"]
onEntered: (drag) => {
root.dragTargetDesktopIndex = desktopDropSurface.desktopIndexAt(drag.x)
drag.accept(Qt.MoveAction)
}
onPositionChanged: (drag) => {
root.dragTargetDesktopIndex = desktopDropSurface.desktopIndexAt(drag.x)
drag.accept(Qt.MoveAction)
}
onExited: root.dragTargetDesktopIndex = -1
onDropped: (drop) => {
const desktopIndex = desktopDropSurface.desktopIndexAt(drop.x)
const desktopId = desktopIndex >= 0 ? virtualDesktopInfo.desktopIds[desktopIndex] : ""
if (!drop.source || !drop.source.moveToDesktop || String(desktopId).length === 0) {
root.dragTargetDesktopIndex = -1
return
}
drop.source.moveToDesktop(desktopId, desktopIndex)
root.dragTargetDesktopIndex = -1
drop.accept(Qt.MoveAction)
}
}
}
}
GridView {
id: taskGrid
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
model: tasksModel
boundsBehavior: Flickable.StopAtBounds
interactive: contentHeight > height
readonly property int columns: Math.max(1, Math.floor(width / (Kirigami.Units.gridUnit * 14)))
cellWidth: Math.floor(width / columns)
cellHeight: Kirigami.Units.gridUnit * 10
delegate: Item {
id: taskCard
required property int index
required property var model
width: taskGrid.cellWidth - Kirigami.Units.smallSpacing
height: taskGrid.cellHeight - Kirigami.Units.smallSpacing
readonly property var modelIndex: tasksModel.makeModelIndex(index)
readonly property var winIds: model.WinIdList ? model.WinIdList : []
readonly property int previewCount: Math.max(1, Math.min(2, winIds.length))
readonly property bool activeTask: model.IsActive === true
readonly property bool minimizedTask: model.IsMinimized === true
readonly property bool maximizedTask: model.IsMaximized === true
readonly property bool groupTask: model.IsGroupParent === true
readonly property bool desktopsChangeable: model.IsVirtualDesktopsChangeable === true
readonly property string storageId: root.taskStorageId(model)
readonly property string taskKey: root.taskKey(model)
readonly property bool pinned: storageId !== "" && root.folio.FavouritesModel.containsApplication(storageId)
readonly property bool pendingMove: root.pendingMoveTaskKey === taskKey
function taskIndexForPreview(previewIndex) {
return taskCard.groupTask
? tasksModel.makeModelIndex(taskCard.index, previewIndex)
: taskCard.modelIndex
}
function titleForPreview(previewIndex) {
if (!taskCard.groupTask) {
return taskCard.model.display || ""
}
return tasksModel.data(tasksModel.makeModelIndex(taskCard.index, previewIndex), 0) || taskCard.model.display || ""
}
function activate(previewIndex) {
tasksModel.requestActivate(taskIndexForPreview(previewIndex || 0))
root.taskActivated()
}
function moveToDesktop(desktopId, desktopIndex) {
if (!taskCard.desktopsChangeable || String(desktopId).length === 0) {
return
}
root.markTaskMove(taskCard.taskKey, desktopIndex)
tasksModel.requestVirtualDesktops(taskCard.modelIndex, [desktopId])
}
Item {
id: dragProxy
parent: root
width: taskCard.width
height: taskCard.height
z: 1000
visible: cardArea.drag.active
opacity: 0.9
Drag.active: cardArea.drag.active
Drag.hotSpot.x: cardArea.pressX
Drag.hotSpot.y: cardArea.pressY
Drag.keys: ["folio-running-task"]
Drag.proposedAction: Qt.MoveAction
Drag.source: taskCard
Drag.supportedActions: Qt.MoveAction
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.2)
border.width: 1
border.pixelAligned: false
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.6)
}
RowLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Layout.preferredWidth
source: taskCard.model.decoration
}
PC3.Label {
Layout.fillWidth: true
text: taskCard.model.display || ""
elide: Text.ElideRight
maximumLineCount: 1
}
}
}
MouseArea {
id: cardArea
anchors.fill: parent
hoverEnabled: true
cursorShape: taskCard.desktopsChangeable ? Qt.OpenHandCursor : Qt.PointingHandCursor
enabled: !taskCard.pendingMove
property real pressX: width / 2
property real pressY: height / 2
property bool wasDragged: false
drag.target: taskCard.desktopsChangeable ? dragProxy : undefined
drag.threshold: Math.max(4, Kirigami.Units.smallSpacing)
drag.smoothed: false
onPressed: (mouse) => {
wasDragged = false
pressX = mouse.x
pressY = mouse.y
const pos = taskCard.mapToItem(root, 0, 0)
dragProxy.x = pos.x
dragProxy.y = pos.y
}
onPositionChanged: {
if (drag.active) {
wasDragged = true
}
}
onReleased: {
if (wasDragged) {
dragProxy.Drag.drop()
}
}
onClicked: {
if (!wasDragged) {
taskCard.activate(0)
}
}
}
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: taskCard.activeTask
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, cardArea.containsMouse ? 0.18 : 0.12)
: cardArea.containsMouse
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.08)
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.04)
border.width: 1
border.pixelAligned: false
border.color: taskCard.activeTask
? root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.5)
: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.12)
Behavior on color {
ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Row {
id: previewRow
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 5
spacing: Kirigami.Units.smallSpacing
Repeater {
model: taskCard.previewCount
delegate: MouseArea {
id: previewArea
required property int index
readonly property string childUuid: taskCard.winIds.length > index ? taskCard.winIds[index] : ""
width: (previewRow.width - previewRow.spacing * (taskCard.previewCount - 1)) / taskCard.previewCount
height: previewRow.height
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: taskCard.activate(index)
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, previewArea.containsMouse ? 0.1 : 0.06)
border.width: 1
border.pixelAligned: false
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.14)
}
Loader {
id: thumbnailLoader
anchors.fill: parent
anchors.margins: 1
active: previewArea.childUuid !== "" && root.visible
sourceComponent: PipeWireThumbnail {
windowUuid: previewArea.childUuid
}
}
Kirigami.Icon {
anchors.centerIn: parent
width: Kirigami.Units.iconSizes.large
height: width
source: taskCard.model.decoration
visible: !thumbnailLoader.item || !thumbnailLoader.item.hasThumbnail
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: titleLabel.implicitHeight + Kirigami.Units.smallSpacing
radius: Kirigami.Units.cornerRadius
color: Qt.rgba(0, 0, 0, 0.48)
visible: taskCard.previewCount > 1
PC3.Label {
id: titleLabel
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Kirigami.Units.smallSpacing
text: taskCard.titleForPreview(previewArea.index)
elide: Text.ElideRight
maximumLineCount: 1
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Layout.preferredWidth
source: taskCard.model.decoration
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
PC3.Label {
Layout.fillWidth: true
text: taskCard.model.display || ""
font.weight: taskCard.activeTask ? Font.Medium : Font.Normal
elide: Text.ElideRight
maximumLineCount: 1
}
Row {
spacing: Kirigami.Units.smallSpacing
PC3.Label {
text: taskCard.activeTask ? i18n("Active") : taskCard.minimizedTask ? i18n("Minimized") : i18n("Open")
opacity: 0.65
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
}
PC3.Label {
visible: taskCard.maximizedTask
text: i18n("Maximized")
opacity: 0.65
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
}
PC3.Label {
visible: taskCard.winIds.length > 1
text: i18np("%1 window", "%1 windows", taskCard.winIds.length)
opacity: 0.65
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Item { Layout.fillWidth: true }
PanelIconButton {
iconName: taskCard.pinned ? "emblem-favorite" : "window-pin"
toolTipText: taskCard.pinned ? i18n("Pinned") : i18n("Pin to Dock")
checked: taskCard.pinned
enabled: taskCard.storageId !== "" && !taskCard.pinned && !root.folio.FolioSettings.lockLayout
onTriggered: root.folio.FavouritesModel.addApplication(taskCard.storageId)
}
PanelIconButton {
iconName: taskCard.minimizedTask ? "window-restore" : "window-minimize"
toolTipText: taskCard.minimizedTask ? i18n("Restore") : i18n("Minimize")
onTriggered: tasksModel.requestToggleMinimized(taskCard.modelIndex)
}
PanelIconButton {
iconName: taskCard.maximizedTask ? "window-restore" : "window-maximize"
toolTipText: taskCard.maximizedTask ? i18n("Restore") : i18n("Maximize")
enabled: !taskCard.groupTask
onTriggered: tasksModel.requestToggleMaximized(taskCard.modelIndex)
}
PanelIconButton {
iconName: "window-close"
toolTipText: taskCard.winIds.length > 1 ? i18n("Close All") : i18n("Close")
onTriggered: tasksModel.requestClose(taskCard.modelIndex)
}
}
}
Rectangle {
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
visible: taskCard.pendingMove
color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.18)
border.width: 1
border.pixelAligned: false
border.color: root.mixColor(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.55)
PC3.Label {
anchors.centerIn: parent
width: parent.width - Kirigami.Units.gridUnit
text: i18n("Moving to %1", root.pendingMoveTargetName)
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
font.weight: Font.Medium
color: Kirigami.Theme.highlightColor
}
}
}
PC3.ScrollBar.vertical: PC3.ScrollBar {
interactive: true
enabled: taskGrid.contentHeight > taskGrid.height
implicitWidth: Kirigami.Units.smallSpacing
}
PC3.Label {
anchors.centerIn: parent
width: parent.width - Kirigami.Units.gridUnit * 2
visible: taskGrid.count === 0
text: i18n("No windows on this desktop")
horizontalAlignment: Text.AlignHCenter
opacity: 0.65
wrapMode: Text.WordWrap
}
}
}
}

View file

@ -32,7 +32,7 @@ import "./private"
ContainmentItem { ContainmentItem {
id: root id: root
property Folio.HomeScreen folio: root.plasmoid property var folio: root.plasmoid
// Tracks whether the Game Center grid is visible within gaming mode. // Tracks whether the Game Center grid is visible within gaming mode.
// If gaming mode is already enabled at startup, open it immediately so // If gaming mode is already enabled at startup, open it immediately so
@ -365,6 +365,7 @@ ContainmentItem {
folio: root.folio folio: root.folio
maskManager: root.maskManager maskManager: root.maskManager
homeScreen: folioHomeScreen homeScreen: folioHomeScreen
suppressRunningTasks: runningAppsPanel.visible
transform: Translate { y: dockOverlay.dockOffset } transform: Translate { y: dockOverlay.dockOffset }
// Dock is an opaque panel use Window colorset so all content // Dock is an opaque panel use Window colorset so all content
// (labels, hover highlights, icon tints) follows the system theme // (labels, hover highlights, icon tints) follows the system theme
@ -519,7 +520,9 @@ ContainmentItem {
width: tileSize width: tileSize
height: overlayDrawer.popupHeight height: overlayDrawer.popupHeight
x: categoryPanel.x + categoryPanel.width + Kirigami.Units.smallSpacing x: runningAppsPanel.visible
? runningAppsPanel.x + runningAppsPanel.width + Kirigami.Units.smallSpacing
: categoryPanel.x + categoryPanel.width + Kirigami.Units.smallSpacing
y: overlayDrawer.y y: overlayDrawer.y
opacity: overlayDrawer.opacity opacity: overlayDrawer.opacity
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
@ -739,6 +742,20 @@ ContainmentItem {
} }
} }
} }
RunningAppsPanel {
id: runningAppsPanel
folio: root.folio
x: categoryPanel.x + categoryPanel.width + Kirigami.Units.smallSpacing
y: overlayDrawer.y
width: Math.max(0, parent.width - x - powerPanel.width - Kirigami.Units.smallSpacing * 2)
height: overlayDrawer.popupHeight
opacity: overlayDrawer.opacity
visible: hasTasks && opacity > 0
onTaskActivated: folio.HomeScreenState.closeAppDrawer()
}
} }
// Game Center overlay full-screen grid of games shown when gaming mode // Game Center overlay full-screen grid of games shown when gaming mode