135 lines
5.4 KiB
QML
135 lines
5.4 KiB
QML
import QtQuick
|
|
import QtQuick.Effects
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Services.SystemTray
|
|
|
|
import "." as M
|
|
import "../services" as S
|
|
|
|
M.BarModule {
|
|
id: root
|
|
spacing: S.Theme.moduleSpacing + 2
|
|
cursorShape: Qt.ArrowCursor
|
|
|
|
// Workaround: Qt 6 compiled bindings break on `visible` when the expression
|
|
// references Repeater.count on a Row-derived component. The binding silently
|
|
// dies (imperative overwrite by the engine) and never re-evaluates. Routing
|
|
// through a custom property forces the interpreted binding path, which works.
|
|
property bool _shouldBeVisible: S.Modules.tray.enable && _trayRepeater.count > 0
|
|
visible: _shouldBeVisible
|
|
|
|
required property var bar
|
|
property var _activeMenu: null
|
|
|
|
// --- debug logging (remove once tray is confirmed working) ---
|
|
on_ShouldBeVisibleChanged: console.log("[TrayModule] _shouldBeVisible:", _shouldBeVisible, "actual visible:", visible)
|
|
Component.onCompleted: console.log("[TrayModule] created, enable:", S.Modules.tray.enable, "repeater count:", _trayRepeater.count)
|
|
onVisibleChanged: console.log("[TrayModule] visible:", visible, "enable:", S.Modules.tray.enable, "count:", _trayRepeater.count)
|
|
Connections {
|
|
target: _trayRepeater
|
|
function onCountChanged() {
|
|
console.log("[TrayModule] repeater count:", _trayRepeater.count, "visible:", root.visible, "shouldBe:", S.Modules.tray.enable && _trayRepeater.count > 0);
|
|
}
|
|
}
|
|
Connections {
|
|
target: SystemTray.items
|
|
function onValuesChanged() {
|
|
console.log("[TrayModule] model valuesChanged, values.length:", SystemTray.items.values.length, "repeater count:", _trayRepeater.count);
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
id: _trayRepeater
|
|
model: SystemTray.items
|
|
|
|
delegate: Item {
|
|
id: iconItem
|
|
required property SystemTrayItem modelData
|
|
|
|
readonly property bool _needsAttention: modelData.status === 2
|
|
property bool _hovered: false
|
|
property real _pulseOpacity: 1
|
|
|
|
implicitWidth: S.Theme.fontSize + 4
|
|
implicitHeight: S.Theme.fontSize + 4
|
|
|
|
M.PulseAnimation on _pulseOpacity {
|
|
running: iconItem._needsAttention
|
|
minOpacity: 0.3
|
|
}
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
opacity: iconItem._pulseOpacity
|
|
|
|
layer.enabled: iconItem._needsAttention || iconItem._hovered
|
|
layer.effect: MultiEffect {
|
|
shadowEnabled: true
|
|
shadowColor: iconItem._needsAttention ? S.Theme.base08 : S.Theme.base05
|
|
shadowBlur: iconItem._needsAttention ? 0.8 : 0.5
|
|
shadowVerticalOffset: 0
|
|
shadowHorizontalOffset: 0
|
|
}
|
|
|
|
M.ThemedIcon {
|
|
anchors.fill: parent
|
|
source: iconItem.modelData.icon
|
|
tint: iconItem._needsAttention ? S.Theme.base08 : root.accentColor
|
|
}
|
|
}
|
|
|
|
HoverHandler {
|
|
onHoveredChanged: {
|
|
iconItem._hovered = hovered;
|
|
const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription].filter(s => s).join("\n") || iconItem.modelData.title;
|
|
if (hovered && tip) {
|
|
M.TooltipState.text = tip;
|
|
M.TooltipState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
|
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
|
M.TooltipState.accentColor = root.accentColor;
|
|
M.TooltipState.visible = true;
|
|
} else if (!hovered) {
|
|
M.TooltipState.visible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
onClicked: mouse => {
|
|
if (mouse.button === Qt.LeftButton) {
|
|
iconItem.modelData.activate();
|
|
} else if (mouse.button === Qt.RightButton) {
|
|
if (iconItem.modelData.menu) {
|
|
if (root._activeMenu && root._activeMenu !== menuLoader)
|
|
root._activeMenu.active = false;
|
|
menuLoader.active = true;
|
|
M.TooltipState.visible = false;
|
|
root._activeMenu = menuLoader;
|
|
} else {
|
|
iconItem.modelData.secondaryActivate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Per-icon context menu window, created on demand
|
|
LazyLoader {
|
|
id: menuLoader
|
|
active: false
|
|
M.TrayMenu {
|
|
accentColor: root.accentColor
|
|
handle: iconItem.modelData.menu
|
|
screen: root.bar.screen
|
|
anchorX: iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
|
|
onDismissed: {
|
|
menuLoader.active = false;
|
|
root._activeMenu = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|