diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml | 902 |
1 files changed, 902 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/DankBar/Widgets/RunningApps.qml new file mode 100644 index 0000000..ae58802 --- /dev/null +++ b/raveos-hyprland-theme/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(); + } + } + } + } + } +} |