181 lines
No EOL
6.4 KiB
QML
181 lines
No EOL
6.4 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Services.SystemTray
|
|
import "root:/Data" as Data
|
|
|
|
// System tray with optimized icon caching
|
|
Row {
|
|
property var bar
|
|
property var shell
|
|
property var trayMenu
|
|
spacing: 8
|
|
Layout.alignment: Qt.AlignVCenter
|
|
|
|
property bool containsMouse: false
|
|
property var systemTray: SystemTray
|
|
|
|
// Custom icon cache for memory optimization
|
|
property var iconCache: ({})
|
|
property var iconCacheCount: ({})
|
|
|
|
// Cache cleanup to prevent memory leaks
|
|
Timer {
|
|
interval: 120000
|
|
repeat: true
|
|
running: systemTray.items.length > 0
|
|
onTriggered: {
|
|
// Decrement counters and remove unused icons
|
|
for (let icon in iconCacheCount) {
|
|
iconCacheCount[icon]--
|
|
if (iconCacheCount[icon] <= 0) {
|
|
delete iconCache[icon]
|
|
delete iconCacheCount[icon]
|
|
}
|
|
}
|
|
|
|
// Enforce maximum cache size
|
|
const maxCacheSize = 10;
|
|
const cacheKeys = Object.keys(iconCache);
|
|
if (cacheKeys.length > maxCacheSize) {
|
|
const toRemove = cacheKeys.slice(0, cacheKeys.length - maxCacheSize);
|
|
toRemove.forEach(key => {
|
|
delete iconCache[key];
|
|
delete iconCacheCount[key];
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: systemTray.items
|
|
delegate: Item {
|
|
width: 24
|
|
height: 24
|
|
property bool isHovered: trayMouseArea.containsMouse
|
|
|
|
onIsHoveredChanged: updateParentHoverState()
|
|
Component.onCompleted: updateParentHoverState()
|
|
|
|
function updateParentHoverState() {
|
|
let anyHovered = false
|
|
for (let i = 0; i < parent.children.length; i++) {
|
|
if (parent.children[i].isHovered) {
|
|
anyHovered = true
|
|
break
|
|
}
|
|
}
|
|
parent.containsMouse = anyHovered
|
|
}
|
|
|
|
// Hover animations
|
|
scale: isHovered ? 1.15 : 1.0
|
|
Behavior on scale {
|
|
enabled: isHovered
|
|
NumberAnimation {
|
|
duration: 150
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
|
|
rotation: isHovered ? 5 : 0
|
|
Behavior on rotation {
|
|
enabled: isHovered
|
|
NumberAnimation {
|
|
duration: 200
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
|
|
Image {
|
|
id: trayIcon
|
|
anchors.centerIn: parent
|
|
width: 18
|
|
height: 18
|
|
sourceSize.width: 18
|
|
sourceSize.height: 18
|
|
smooth: false // Memory savings
|
|
asynchronous: true
|
|
cache: false // Use custom cache instead
|
|
source: {
|
|
let icon = modelData?.icon || "";
|
|
if (!icon) return "";
|
|
|
|
// Return cached icon if available
|
|
if (iconCache[icon]) {
|
|
iconCacheCount[icon] = 2
|
|
return iconCache[icon];
|
|
}
|
|
|
|
// Process icon path
|
|
let finalPath = icon;
|
|
if (icon.includes("?path=")) {
|
|
const [name, path] = icon.split("?path=");
|
|
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
|
finalPath = `file://${path}/${fileName}`;
|
|
}
|
|
|
|
// Cache the processed path
|
|
iconCache[icon] = finalPath;
|
|
iconCacheCount[icon] = 2;
|
|
return finalPath;
|
|
}
|
|
opacity: status === Image.Ready ? 1 : 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: 300
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
let icon = modelData?.icon || "";
|
|
if (icon) {
|
|
delete iconCache[icon];
|
|
delete iconCacheCount[icon];
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: trayMouseArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
onClicked: (mouse) => {
|
|
if (!modelData) return;
|
|
|
|
if (mouse.button === Qt.LeftButton) {
|
|
if (trayMenu && trayMenu.visible) {
|
|
trayMenu.hide()
|
|
}
|
|
if (!modelData.onlyMenu) {
|
|
modelData.activate()
|
|
}
|
|
} else if (mouse.button === Qt.MiddleButton) {
|
|
if (trayMenu && trayMenu.visible) {
|
|
trayMenu.hide()
|
|
}
|
|
modelData.secondaryActivate && modelData.secondaryActivate()
|
|
} else if (mouse.button === Qt.RightButton) {
|
|
if (trayMenu && trayMenu.visible) {
|
|
trayMenu.hide()
|
|
return
|
|
}
|
|
// Show context menu if available
|
|
if (modelData.hasMenu && modelData.menu && trayMenu) {
|
|
trayMenu.menu = modelData.menu
|
|
const iconCenter = Qt.point(width / 2, height)
|
|
const iconPos = mapToItem(trayMenu.parent, 0, 0)
|
|
const menuX = iconPos.x - (trayMenu.width / 2) + (width / 2)
|
|
const menuY = iconPos.y + height + 15
|
|
trayMenu.show(Qt.point(menuX, menuY), trayMenu.parent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |