summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml902
1 files changed, 902 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml
new file mode 100644
index 0000000..ae58802
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml
@@ -0,0 +1,902 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Effects
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Widgets
+import qs.Common
+import qs.Modules.Plugins
+import qs.Services
+import qs.Widgets
+
+BasePill {
+ id: root
+
+ enableBackgroundHover: false
+ enableCursor: false
+ section: "left"
+
+ property var widgetData: null
+ property var hoveredItem: null
+ property var topBar: null
+ property bool isAutoHideBar: false
+ property Item windowRoot: (Window.window ? Window.window.contentItem : null)
+
+ readonly property real effectiveBarThickness: {
+ if (barThickness > 0 && barSpacing > 0) {
+ return barThickness + barSpacing;
+ }
+ const innerPadding = barConfig?.innerPadding ?? 4;
+ const spacing = barConfig?.spacing ?? 4;
+ return Math.max(26 + innerPadding * 0.6, Theme.barHeight - 4 - (8 - innerPadding)) + spacing;
+ }
+
+ readonly property var barBounds: {
+ if (!parentScreen || !barConfig) {
+ return {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0,
+ "wingSize": 0
+ };
+ }
+ const barPosition = axis.edge === "left" ? 2 : (axis.edge === "right" ? 3 : (axis.edge === "top" ? 0 : 1));
+ return SettingsData.getBarBounds(parentScreen, effectiveBarThickness, barPosition, barConfig);
+ }
+
+ readonly property real barY: barBounds.y
+
+ readonly property real minTooltipY: {
+ if (!parentScreen || !isVerticalOrientation) {
+ return 0;
+ }
+
+ if (isAutoHideBar) {
+ return 0;
+ }
+
+ if (parentScreen.y > 0) {
+ return effectiveBarThickness;
+ }
+
+ return 0;
+ }
+
+ property int _desktopEntriesUpdateTrigger: 0
+ property int _toplevelsUpdateTrigger: 0
+ property int _appIdSubstitutionsTrigger: 0
+
+ readonly property bool _currentWorkspace: widgetData?.runningAppsCurrentWorkspace !== undefined ? widgetData.runningAppsCurrentWorkspace : SettingsData.runningAppsCurrentWorkspace
+ readonly property bool _currentMonitor: widgetData?.runningAppsCurrentMonitor !== undefined ? widgetData.runningAppsCurrentMonitor : SettingsData.runningAppsCurrentMonitor
+ readonly property bool _groupByApp: widgetData?.runningAppsGroupByApp !== undefined ? widgetData.runningAppsGroupByApp : SettingsData.runningAppsGroupByApp
+
+ readonly property var sortedToplevels: {
+ _toplevelsUpdateTrigger;
+ let toplevels = CompositorService.sortedToplevels;
+ if (!toplevels || toplevels.length === 0)
+ return [];
+
+ if (_currentWorkspace)
+ toplevels = CompositorService.filterCurrentWorkspace(toplevels, parentScreen?.name) || [];
+ if (_currentMonitor)
+ toplevels = CompositorService.filterCurrentDisplay(toplevels, parentScreen?.name) || [];
+ return toplevels;
+ }
+
+ Connections {
+ target: CompositorService
+ function onToplevelsChanged() {
+ _toplevelsUpdateTrigger++;
+ }
+ }
+
+ Connections {
+ target: DesktopEntries
+ function onApplicationsChanged() {
+ _desktopEntriesUpdateTrigger++;
+ }
+ }
+
+ Connections {
+ target: SettingsData
+ function onAppIdSubstitutionsChanged() {
+ _appIdSubstitutionsTrigger++;
+ }
+ }
+ readonly property var groupedWindows: {
+ if (!_groupByApp) {
+ return [];
+ }
+ try {
+ if (!sortedToplevels || sortedToplevels.length === 0) {
+ return [];
+ }
+ const appGroups = new Map();
+ sortedToplevels.forEach((toplevel, index) => {
+ if (!toplevel)
+ return;
+ const appId = toplevel?.appId || "unknown";
+ if (!appGroups.has(appId)) {
+ appGroups.set(appId, {
+ "appId": appId,
+ "windows": []
+ });
+ }
+ appGroups.get(appId).windows.push({
+ "toplevel": toplevel,
+ "windowId": index,
+ "windowTitle": toplevel?.title || "(Unnamed)"
+ });
+ });
+ return Array.from(appGroups.values());
+ } catch (e) {
+ return [];
+ }
+ }
+ readonly property int windowCount: _groupByApp ? (groupedWindows?.length || 0) : (sortedToplevels?.length || 0)
+ readonly property real iconCellSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) + 6
+
+ readonly property string focusedAppId: {
+ if (!sortedToplevels || sortedToplevels.length === 0)
+ return "";
+ for (let i = 0; i < sortedToplevels.length; i++) {
+ if (sortedToplevels[i].activated)
+ return sortedToplevels[i].appId || "";
+ }
+ return "";
+ }
+
+ visible: windowCount > 0
+
+ property real scrollAccumulator: 0
+ property real touchpadThreshold: 500
+
+ onWheel: function (wheelEvent) {
+ wheelEvent.accepted = true;
+ const deltaY = wheelEvent.angleDelta.y;
+ const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
+
+ const windows = root.sortedToplevels;
+ if (windows.length < 2)
+ return;
+
+ if (isMouseWheel) {
+ let currentIndex = -1;
+ for (var i = 0; i < windows.length; i++) {
+ if (windows[i].activated) {
+ currentIndex = i;
+ break;
+ }
+ }
+
+ let nextIndex;
+ if (deltaY < 0) {
+ nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, windows.length - 1);
+ } else {
+ nextIndex = currentIndex === -1 ? windows.length - 1 : Math.max(currentIndex - 1, 0);
+ }
+
+ const nextWindow = windows[nextIndex];
+ if (nextWindow)
+ nextWindow.activate();
+ } else {
+ scrollAccumulator += deltaY;
+
+ if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
+ let currentIndex = -1;
+ for (var i = 0; i < windows.length; i++) {
+ if (windows[i].activated) {
+ currentIndex = i;
+ break;
+ }
+ }
+
+ let nextIndex;
+ if (scrollAccumulator < 0) {
+ nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, windows.length - 1);
+ } else {
+ nextIndex = currentIndex === -1 ? windows.length - 1 : Math.max(currentIndex - 1, 0);
+ }
+
+ const nextWindow = windows[nextIndex];
+ if (nextWindow)
+ nextWindow.activate();
+
+ scrollAccumulator = 0;
+ }
+ }
+ }
+
+ content: Component {
+ Item {
+ implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
+ implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
+
+ Loader {
+ id: layoutLoader
+ anchors.centerIn: parent
+ sourceComponent: root.isVerticalOrientation ? columnLayout : rowLayout
+ }
+ }
+ }
+
+ Component {
+ id: rowLayout
+ Row {
+ spacing: Theme.spacingXS
+
+ Repeater {
+ id: windowRepeater
+ model: ScriptModel {
+ values: _groupByApp ? groupedWindows : sortedToplevels
+ objectProp: _groupByApp ? "appId" : "address"
+ }
+
+ delegate: Item {
+ id: delegateItem
+
+ property bool isGrouped: root._groupByApp
+ property var groupData: isGrouped ? modelData : null
+ property var toplevelData: isGrouped ? (modelData.windows.length > 0 ? modelData.windows[0].toplevel : null) : modelData
+ property bool isFocused: isGrouped ? (root.focusedAppId === appId) : (toplevelData ? toplevelData.activated : false)
+ property string appId: isGrouped ? modelData.appId : (modelData.appId || "")
+ readonly property string effectiveAppId: {
+ root._appIdSubstitutionsTrigger;
+ return Paths.moddedAppId(appId);
+ }
+ property string windowTitle: toplevelData ? (toplevelData.title || "(Unnamed)") : "(Unnamed)"
+ property var toplevelObject: toplevelData
+ property int windowCount: isGrouped ? modelData.windows.length : 1
+ property string tooltipText: {
+ root._desktopEntriesUpdateTrigger;
+ const desktopEntry = effectiveAppId ? DesktopEntries.heuristicLookup(effectiveAppId) : null;
+ const appName = effectiveAppId ? Paths.getAppName(effectiveAppId, desktopEntry) : "Unknown";
+
+ if (isGrouped && windowCount > 1) {
+ return appName + " (" + windowCount + " windows)";
+ }
+ return appName + (windowTitle ? " • " + windowTitle : "");
+ }
+ readonly property real visualWidth: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? root.iconCellSize : (root.iconCellSize + Theme.spacingXS + 120)
+
+ width: visualWidth
+ height: root.barThickness
+
+ Rectangle {
+ id: visualContent
+ width: delegateItem.visualWidth
+ height: root.iconCellSize
+ anchors.centerIn: parent
+ radius: Theme.cornerRadius
+ color: {
+ if (isFocused) {
+ return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.45);
+ }
+ return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
+ }
+
+ // App icon
+ IconImage {
+ id: iconImg
+ anchors.left: parent.left
+ anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)) / 2) : Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
+ height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
+ source: {
+ root._desktopEntriesUpdateTrigger;
+ root._appIdSubstitutionsTrigger;
+ if (!effectiveAppId)
+ return "";
+ const desktopEntry = DesktopEntries.heuristicLookup(effectiveAppId);
+ return Paths.getAppIcon(effectiveAppId, desktopEntry);
+ }
+ smooth: true
+ mipmap: true
+ asynchronous: true
+ visible: status === Image.Ready
+ layer.enabled: appId === "org.quickshell" || appId === "com.danklinux.dms"
+ layer.smooth: true
+ layer.mipmap: true
+ layer.effect: MultiEffect {
+ saturation: 0
+ colorization: 1
+ colorizationColor: Theme.primary
+ }
+ }
+
+ DankIcon {
+ anchors.left: parent.left
+ anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)) / 2) : Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
+ name: "sports_esports"
+ color: Theme.widgetTextColor
+ visible: !iconImg.visible && Paths.isSteamApp(effectiveAppId)
+ }
+
+ Text {
+ anchors.centerIn: parent
+ visible: !iconImg.visible && !Paths.isSteamApp(effectiveAppId)
+ text: {
+ root._desktopEntriesUpdateTrigger;
+ if (!effectiveAppId)
+ return "?";
+ const desktopEntry = DesktopEntries.heuristicLookup(effectiveAppId);
+ const appName = Paths.getAppName(effectiveAppId, desktopEntry);
+ return appName.charAt(0).toUpperCase();
+ }
+ font.pixelSize: 10
+ color: Theme.widgetTextColor
+ }
+
+ Rectangle {
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? -2 : 2
+ anchors.bottomMargin: -2
+ width: 14
+ height: 14
+ radius: 7
+ color: Theme.primary
+ visible: isGrouped && windowCount > 1
+ z: 10
+
+ StyledText {
+ anchors.centerIn: parent
+ text: windowCount > 9 ? "9+" : windowCount
+ font.pixelSize: 9
+ color: Theme.surface
+ }
+ }
+
+ StyledText {
+ anchors.left: iconImg.right
+ anchors.leftMargin: Theme.spacingXS
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingS
+ anchors.verticalCenter: parent.verticalCenter
+ visible: !(widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
+ text: windowTitle
+ font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ color: Theme.widgetTextColor
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ }
+
+ DankRipple {
+ id: itemRipple
+ cornerRadius: Theme.cornerRadius
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ onPressed: mouse => {
+ const pos = mapToItem(visualContent, mouse.x, mouse.y);
+ itemRipple.trigger(pos.x, pos.y);
+ }
+ onClicked: mouse => {
+ if (mouse.button === Qt.LeftButton) {
+ if (isGrouped && windowCount > 1) {
+ let currentIndex = -1;
+ for (var i = 0; i < groupData.windows.length; i++) {
+ if (groupData.windows[i].toplevel.activated) {
+ currentIndex = i;
+ break;
+ }
+ }
+ const nextIndex = (currentIndex + 1) % groupData.windows.length;
+ groupData.windows[nextIndex].toplevel.activate();
+ } else if (toplevelObject) {
+ toplevelObject.activate();
+ }
+ } else if (mouse.button === Qt.RightButton) {
+ if (tooltipLoader.item) {
+ tooltipLoader.item.hide();
+ }
+ tooltipLoader.active = false;
+
+ windowContextMenuLoader.active = true;
+ if (windowContextMenuLoader.item) {
+ windowContextMenuLoader.item.currentWindow = toplevelObject;
+ // Pass bar context
+ windowContextMenuLoader.item.triggerBarConfig = root.barConfig;
+ windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1));
+ windowContextMenuLoader.item.triggerBarThickness = root.barThickness;
+ windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
+ if (root.isVerticalOrientation) {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
+ const screenX = root.parentScreen ? root.parentScreen.x : 0;
+ const screenY = root.parentScreen ? root.parentScreen.y : 0;
+ const relativeY = globalPos.y - screenY;
+ // Add minTooltipY offset to account for top bar
+ const adjustedY = relativeY + root.minTooltipY;
+ const xPos = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
+ windowContextMenuLoader.item.showAt(xPos, adjustedY, true, root.axis?.edge);
+ } else {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
+ const screenX = root.parentScreen ? root.parentScreen.x : 0;
+ const relativeX = globalPos.x - screenX;
+ const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
+ const isBottom = root.axis?.edge === "bottom";
+ const yPos = isBottom ? (screenHeight - root.barThickness - root.barSpacing - 32 - Theme.spacingXS) : (root.barThickness + root.barSpacing + Theme.spacingXS);
+ windowContextMenuLoader.item.showAt(relativeX, yPos, false, root.axis?.edge);
+ }
+ }
+ } else if (mouse.button === Qt.MiddleButton) {
+ if (toplevelObject) {
+ if (typeof toplevelObject.close === "function") {
+ toplevelObject.close();
+ }
+ }
+ }
+ }
+ onEntered: {
+ root.hoveredItem = delegateItem;
+ tooltipLoader.active = true;
+ if (tooltipLoader.item) {
+ if (root.isVerticalOrientation) {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
+ const screenX = root.parentScreen ? root.parentScreen.x : 0;
+ const screenY = root.parentScreen ? root.parentScreen.y : 0;
+ const relativeY = globalPos.y - screenY;
+ const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
+ const isLeft = root.axis?.edge === "left";
+ const adjustedY = relativeY + root.minTooltipY;
+ const finalX = screenX + tooltipX;
+ tooltipLoader.item.show(delegateItem.tooltipText, finalX, adjustedY, root.parentScreen, isLeft, !isLeft);
+ } else {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
+ const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
+ const isBottom = root.axis?.edge === "bottom";
+ const tooltipY = isBottom ? (screenHeight - root.barThickness - root.barSpacing - Theme.spacingXS - 35) : (root.barThickness + root.barSpacing + Theme.spacingXS);
+ tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
+ }
+ }
+ }
+ onExited: {
+ if (root.hoveredItem === delegateItem) {
+ root.hoveredItem = null;
+ if (tooltipLoader.item) {
+ tooltipLoader.item.hide();
+ }
+
+ tooltipLoader.active = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: columnLayout
+ Column {
+ spacing: Theme.spacingXS
+
+ Repeater {
+ id: windowRepeater
+ model: ScriptModel {
+ values: _groupByApp ? groupedWindows : sortedToplevels
+ objectProp: _groupByApp ? "appId" : "address"
+ }
+
+ delegate: Item {
+ id: delegateItem
+
+ property bool isGrouped: root._groupByApp
+ property var groupData: isGrouped ? modelData : null
+ property var toplevelData: isGrouped ? (modelData.windows.length > 0 ? modelData.windows[0].toplevel : null) : modelData
+ property bool isFocused: isGrouped ? (root.focusedAppId === appId) : (toplevelData ? toplevelData.activated : false)
+ property string appId: isGrouped ? modelData.appId : (modelData.appId || "")
+ readonly property string effectiveAppId: {
+ root._appIdSubstitutionsTrigger;
+ return Paths.moddedAppId(appId);
+ }
+ property string windowTitle: toplevelData ? (toplevelData.title || "(Unnamed)") : "(Unnamed)"
+ property var toplevelObject: toplevelData
+ property int windowCount: isGrouped ? modelData.windows.length : 1
+ property string tooltipText: {
+ root._desktopEntriesUpdateTrigger;
+ const desktopEntry = effectiveAppId ? DesktopEntries.heuristicLookup(effectiveAppId) : null;
+ const appName = effectiveAppId ? Paths.getAppName(effectiveAppId, desktopEntry) : "Unknown";
+
+ if (isGrouped && windowCount > 1) {
+ return appName + " (" + windowCount + " windows)";
+ }
+ return appName + (windowTitle ? " • " + windowTitle : "");
+ }
+ readonly property real visualWidth: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? root.iconCellSize : (root.iconCellSize + Theme.spacingXS + 120)
+
+ width: root.barThickness
+ height: root.iconCellSize
+
+ Rectangle {
+ id: visualContent
+ width: delegateItem.visualWidth
+ height: root.iconCellSize
+ anchors.centerIn: parent
+ radius: Theme.cornerRadius
+ color: {
+ if (isFocused) {
+ return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.45);
+ }
+ return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
+ }
+
+ IconImage {
+ id: iconImg
+ anchors.left: parent.left
+ anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)) / 2) : Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
+ height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
+ source: {
+ root._desktopEntriesUpdateTrigger;
+ root._appIdSubstitutionsTrigger;
+ if (!effectiveAppId)
+ return "";
+ const desktopEntry = DesktopEntries.heuristicLookup(effectiveAppId);
+ return Paths.getAppIcon(effectiveAppId, desktopEntry);
+ }
+ smooth: true
+ mipmap: true
+ asynchronous: true
+ visible: status === Image.Ready
+ layer.enabled: appId === "org.quickshell" || appId === "com.danklinux.dms"
+ layer.smooth: true
+ layer.mipmap: true
+ layer.effect: MultiEffect {
+ saturation: 0
+ colorization: 1
+ colorizationColor: Theme.primary
+ }
+ }
+
+ DankIcon {
+ anchors.left: parent.left
+ anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)) / 2) : Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
+ name: "sports_esports"
+ color: Theme.widgetTextColor
+ visible: !iconImg.visible && Paths.isSteamApp(effectiveAppId)
+ }
+
+ Text {
+ anchors.centerIn: parent
+ visible: !iconImg.visible && !Paths.isSteamApp(effectiveAppId)
+ text: {
+ root._desktopEntriesUpdateTrigger;
+ if (!effectiveAppId)
+ return "?";
+ const desktopEntry = DesktopEntries.heuristicLookup(effectiveAppId);
+ const appName = Paths.getAppName(effectiveAppId, desktopEntry);
+ return appName.charAt(0).toUpperCase();
+ }
+ font.pixelSize: 10
+ color: Theme.widgetTextColor
+ }
+
+ Rectangle {
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? -2 : 2
+ anchors.bottomMargin: -2
+ width: 14
+ height: 14
+ radius: 7
+ color: Theme.primary
+ visible: isGrouped && windowCount > 1
+ z: 10
+
+ StyledText {
+ anchors.centerIn: parent
+ text: windowCount > 9 ? "9+" : windowCount
+ font.pixelSize: 9
+ color: Theme.surface
+ }
+ }
+
+ StyledText {
+ anchors.left: iconImg.right
+ anchors.leftMargin: Theme.spacingXS
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingS
+ anchors.verticalCenter: parent.verticalCenter
+ visible: !(widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
+ text: windowTitle
+ font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
+ color: Theme.widgetTextColor
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ }
+
+ DankRipple {
+ id: itemRipple
+ cornerRadius: Theme.cornerRadius
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onPressed: mouse => {
+ const pos = mapToItem(visualContent, mouse.x, mouse.y);
+ itemRipple.trigger(pos.x, pos.y);
+ }
+ onClicked: mouse => {
+ if (mouse.button === Qt.LeftButton) {
+ if (isGrouped && windowCount > 1) {
+ let currentIndex = -1;
+ for (var i = 0; i < groupData.windows.length; i++) {
+ if (groupData.windows[i].toplevel.activated) {
+ currentIndex = i;
+ break;
+ }
+ }
+ const nextIndex = (currentIndex + 1) % groupData.windows.length;
+ groupData.windows[nextIndex].toplevel.activate();
+ } else if (toplevelObject) {
+ toplevelObject.activate();
+ }
+ } else if (mouse.button === Qt.RightButton) {
+ if (tooltipLoader.item) {
+ tooltipLoader.item.hide();
+ }
+ tooltipLoader.active = false;
+
+ windowContextMenuLoader.active = true;
+ if (windowContextMenuLoader.item) {
+ windowContextMenuLoader.item.currentWindow = toplevelObject;
+ // Pass bar context
+ windowContextMenuLoader.item.triggerBarConfig = root.barConfig;
+ windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1));
+ windowContextMenuLoader.item.triggerBarThickness = root.barThickness;
+ windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
+ if (root.isVerticalOrientation) {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
+ const screenX = root.parentScreen ? root.parentScreen.x : 0;
+ const screenY = root.parentScreen ? root.parentScreen.y : 0;
+ const relativeY = globalPos.y - screenY;
+ // Add minTooltipY offset to account for top bar
+ const adjustedY = relativeY + root.minTooltipY;
+ const xPos = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
+ windowContextMenuLoader.item.showAt(xPos, adjustedY, true, root.axis?.edge);
+ } else {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
+ const screenX = root.parentScreen ? root.parentScreen.x : 0;
+ const relativeX = globalPos.x - screenX;
+ const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
+ const isBottom = root.axis?.edge === "bottom";
+ const yPos = isBottom ? (screenHeight - root.barThickness - root.barSpacing - 32 - Theme.spacingXS) : (root.barThickness + root.barSpacing + Theme.spacingXS);
+ windowContextMenuLoader.item.showAt(relativeX, yPos, false, root.axis?.edge);
+ }
+ }
+ }
+ }
+ onEntered: {
+ root.hoveredItem = delegateItem;
+ tooltipLoader.active = true;
+ if (tooltipLoader.item) {
+ if (root.isVerticalOrientation) {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
+ const screenX = root.parentScreen ? root.parentScreen.x : 0;
+ const screenY = root.parentScreen ? root.parentScreen.y : 0;
+ const relativeY = globalPos.y - screenY;
+ const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
+ const isLeft = root.axis?.edge === "left";
+ const adjustedY = relativeY + root.minTooltipY;
+ const finalX = screenX + tooltipX;
+ tooltipLoader.item.show(delegateItem.tooltipText, finalX, adjustedY, root.parentScreen, isLeft, !isLeft);
+ } else {
+ const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
+ const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
+ const isBottom = root.axis?.edge === "bottom";
+ const tooltipY = isBottom ? (screenHeight - root.barThickness - root.barSpacing - Theme.spacingXS - 35) : (root.barThickness + root.barSpacing + Theme.spacingXS);
+ tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
+ }
+ }
+ }
+ onExited: {
+ if (root.hoveredItem === delegateItem) {
+ root.hoveredItem = null;
+ if (tooltipLoader.item) {
+ tooltipLoader.item.hide();
+ }
+
+ tooltipLoader.active = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Loader {
+ id: tooltipLoader
+
+ active: false
+
+ sourceComponent: DankTooltip {}
+ }
+
+ Loader {
+ id: windowContextMenuLoader
+ active: false
+ sourceComponent: PanelWindow {
+ id: contextMenuWindow
+
+ WindowBlur {
+ targetWindow: contextMenuWindow
+ blurX: contextMenuRect.x
+ blurY: contextMenuRect.y
+ blurWidth: contextMenuWindow.isVisible ? contextMenuRect.width : 0
+ blurHeight: contextMenuWindow.isVisible ? contextMenuRect.height : 0
+ blurRadius: Theme.cornerRadius
+ }
+
+ property var currentWindow: null
+ property bool isVisible: false
+ property point anchorPos: Qt.point(0, 0)
+ property bool isVertical: false
+ property string edge: "top"
+
+ // New properties for bar context
+ property int triggerBarPosition: (SettingsData.barConfigs[0]?.position ?? SettingsData.Position.Top)
+ property real triggerBarThickness: 0
+ property real triggerBarSpacing: 0
+ property var triggerBarConfig: null
+
+ readonly property real effectiveBarThickness: {
+ if (triggerBarThickness > 0 && triggerBarSpacing > 0) {
+ return triggerBarThickness + triggerBarSpacing;
+ }
+ return Math.max(26 + (barConfig?.innerPadding ?? 4) * 0.6, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))) + (barConfig?.spacing ?? 4);
+ }
+
+ property var barBounds: {
+ if (!contextMenuWindow.screen || !triggerBarConfig) {
+ return {
+ "x": 0,
+ "y": 0,
+ "width": 0,
+ "height": 0,
+ "wingSize": 0
+ };
+ }
+ return SettingsData.getBarBounds(contextMenuWindow.screen, effectiveBarThickness, triggerBarPosition, triggerBarConfig);
+ }
+
+ property real barY: barBounds.y
+
+ function showAt(x, y, vertical, barEdge) {
+ screen = root.parentScreen;
+ anchorPos = Qt.point(x, y);
+ isVertical = vertical ?? false;
+ edge = barEdge ?? "top";
+ isVisible = true;
+ visible = true;
+
+ if (screen) {
+ TrayMenuManager.registerMenu(screen.name, contextMenuWindow);
+ }
+ }
+
+ function close() {
+ isVisible = false;
+ visible = false;
+ windowContextMenuLoader.active = false;
+
+ if (screen) {
+ TrayMenuManager.unregisterMenu(screen.name);
+ }
+ }
+
+ implicitWidth: 100
+ implicitHeight: 40
+ visible: false
+ color: "transparent"
+
+ WlrLayershell.layer: WlrLayershell.Overlay
+ WlrLayershell.exclusiveZone: -1
+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
+
+ anchors {
+ top: true
+ left: true
+ right: true
+ bottom: true
+ }
+
+ Component.onDestruction: {
+ if (screen) {
+ TrayMenuManager.unregisterMenu(screen.name);
+ }
+ }
+
+ Connections {
+ target: PopoutManager
+ function onPopoutOpening() {
+ contextMenuWindow.close();
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: contextMenuWindow.close()
+ }
+
+ Rectangle {
+ id: contextMenuRect
+ x: {
+ if (contextMenuWindow.isVertical) {
+ if (contextMenuWindow.edge === "left") {
+ return Math.min(contextMenuWindow.width - width - 10, contextMenuWindow.anchorPos.x);
+ } else {
+ return Math.max(10, contextMenuWindow.anchorPos.x - width);
+ }
+ } else {
+ const left = 10;
+ const right = contextMenuWindow.width - width - 10;
+ const want = contextMenuWindow.anchorPos.x - width / 2;
+ return Math.max(left, Math.min(right, want));
+ }
+ }
+ y: {
+ if (contextMenuWindow.isVertical) {
+ const top = Math.max(barY, 10);
+ const bottom = contextMenuWindow.height - height - 10;
+ const want = contextMenuWindow.anchorPos.y - height / 2;
+ return Math.max(top, Math.min(bottom, want));
+ } else {
+ return contextMenuWindow.anchorPos.y;
+ }
+ }
+ width: 100
+ height: 32
+ color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
+ radius: Theme.cornerRadius
+ border.width: BlurService.enabled ? BlurService.borderWidth : 1
+ border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
+
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.radius
+ color: closeMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
+ }
+
+ StyledText {
+ anchors.centerIn: parent
+ text: I18n.tr("Close")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.widgetTextColor
+ }
+
+ MouseArea {
+ id: closeMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ if (contextMenuWindow.currentWindow) {
+ contextMenuWindow.currentWindow.close();
+ }
+ contextMenuWindow.close();
+ }
+ }
+ }
+ }
+ }
+}