shift-shell/components/mobileshell/qml/actiondrawer/private/SystemTrayPopup.qml
Marco Allegretti a3173160e2 Render shell-owned icons with theme masks
Use masked Kirigami icons with explicit theme colors for shell controls so the Shift icon theme renders reliably across light and dark surfaces. Replace the status-bar battery helper with theme icon names so battery glyphs also come from org.shift.icons.

Give the app-thumbnail close affordance a symbolic white X on a dark circular backing so it remains visible over previews.
2026-05-17 08:57:06 +02:00

250 lines
No EOL
9.6 KiB
QML

// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.1
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.systemtray as SystemTray
QQC2.Popup {
id: popup
modal: true
dim: true
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutside
x: parent ? Math.round((parent.width - width) / 2) : 0
y: parent ? Math.round((parent.height - height) / 2) : 0
width: Math.min(Kirigami.Units.gridUnit * 22,
parent ? parent.width - Kirigami.Units.gridUnit * 4 : 420)
height: Math.min(Kirigami.Units.gridUnit * 24,
parent ? parent.height - Kirigami.Units.gridUnit * 4 : 480)
padding: Kirigami.Units.smallSpacing
readonly property int trayItemCount: trayList.count
function show() {
popup.open();
}
SystemTray.StatusNotifierModel {
id: trayModel
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.cornerRadius
border.color: Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
border.width: 1
shadow.size: Kirigami.Units.gridUnit
shadow.color: Qt.rgba(0, 0, 0, 0.45)
shadow.yOffset: 2
}
contentItem: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.topMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: implicitWidth
source: "preferences-desktop-notification-symbolic"
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
isMask: true
color: Kirigami.Theme.textColor
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
QQC2.Label {
Layout.fillWidth: true
text: i18n("System Tray")
font.weight: Font.Bold
elide: Text.ElideRight
}
QQC2.Label {
Layout.fillWidth: true
text: trayList.count > 0 ? i18np("%1 status item", "%1 status items", trayList.count) : i18n("No status items")
opacity: 0.65
elide: Text.ElideRight
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.12)
}
ListView {
id: trayList
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: Kirigami.Units.smallSpacing
boundsBehavior: Flickable.StopAtBounds
model: trayModel
delegate: Item {
id: trayItem
width: ListView.view.width
height: Kirigami.Units.gridUnit * 3
readonly property string itemTitle: model.toolTipTitle ? model.toolTipTitle : (model.title ? model.title : i18n("Status Item"))
readonly property string itemStatus: {
if (model.status === "Passive") {
return i18n("Hidden");
}
if (model.category === "ApplicationStatus") {
return i18n("Application status");
}
return model.status ? model.status : i18n("Active");
}
readonly property bool itemActive: model.category !== "ApplicationStatus" && model.status !== "Passive"
function triggerOperation(operationName) {
if (!model.service) {
return;
}
let operation = model.service.operationDescription(operationName);
if (!operation) {
return;
}
let operationPoint = trayItem.mapToGlobal(trayItem.width, trayItem.height / 2);
operation.x = operationPoint.x;
operation.y = operationPoint.y;
model.service.startOperationCall(operation);
}
Rectangle {
id: rowBackground
anchors.fill: parent
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.rightMargin: Kirigami.Units.smallSpacing
radius: Kirigami.Units.cornerRadius
color: trayMouse.pressed ? Qt.rgba(Kirigami.Theme.textColor.r,
Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.08)
: trayMouse.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r,
Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.04)
: Kirigami.Theme.alternateBackgroundColor
border.width: 1
border.color: Kirigami.ColorUtils.linearInterpolation(
Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, trayItem.itemActive ? 0.16 : 0.08)
opacity: trayItem.itemActive ? 1 : 0.72
}
RowLayout {
anchors.fill: rowBackground
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
anchors.rightMargin: Kirigami.Units.smallSpacing * 2
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: implicitWidth
source: model.iconName ? model.iconName : (model.icon ? model.icon : "")
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
isMask: true
color: Kirigami.Theme.textColor
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 2
MobileShell.MarqueeLabel {
Layout.fillWidth: true
inputText: trayItem.itemTitle
font.weight: Font.Bold
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
}
MobileShell.MarqueeLabel {
Layout.fillWidth: true
inputText: trayItem.itemStatus
opacity: 0.6
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
}
}
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: implicitWidth
source: "go-next-symbolic"
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
isMask: true
color: Kirigami.Theme.textColor
opacity: 0.45
}
}
QQC2.ToolTip.text: trayItem.itemTitle
QQC2.ToolTip.visible: trayMouse.containsMouse && trayItem.itemTitle !== ""
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
MouseArea {
id: trayMouse
anchors.fill: rowBackground
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
trayItem.triggerOperation(mouse.button === Qt.RightButton ? "ContextMenu" : "Activate");
}
}
}
QQC2.Label {
anchors.centerIn: parent
visible: trayList.count === 0
text: i18n("No status items")
opacity: 0.65
}
}
}
enter: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
NumberAnimation { property: "scale"; from: 0.9; to: 1; duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
exit: Transition {
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: Kirigami.Units.shortDuration; easing.type: Easing.InCubic }
}
QQC2.Overlay.modal: Rectangle {
color: Qt.rgba(0, 0, 0, 0.5)
}
}