1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 22:15:38 -05:00

dock: re-work to separate pins from all open windows

This commit is contained in:
bbedward
2025-08-18 14:57:30 -04:00
parent b75342bf93
commit 525ea5ce1c
6 changed files with 506 additions and 613 deletions

View File

@@ -20,19 +20,45 @@ Item {
property point dragOffset: Qt.point(0, 0) property point dragOffset: Qt.point(0, 0)
property int targetIndex: -1 property int targetIndex: -1
property int originalIndex: -1 property int originalIndex: -1
property bool showWindowTitle: false
property string windowTitle: ""
property bool isHovered: mouseArea.containsMouse && !dragging
property bool showTooltip: mouseArea.containsMouse && !dragging
property string tooltipText: {
if (!appData)
return "";
// For window type, show app name + window title
if (appData.type === "window" && showWindowTitle) {
var desktopEntry = DesktopEntries.byId(appData.appId);
var appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId;
return appName + (windowTitle ? " • " + windowTitle : "");
}
// For pinned apps, just show app name
if (!appData.appId)
return "";
var desktopEntry = DesktopEntries.byId(appData.appId);
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId;
}
width: 40 width: 40
height: 40 height: 40
onIsHoveredChanged: {
if (isHovered) {
exitAnimation.stop();
if (!bounceAnimation.running)
bounceAnimation.restart();
property bool isHovered: mouseArea.containsMouse && !dragging } else {
bounceAnimation.stop();
transform: Translate { exitAnimation.restart();
id: translateY }
y: 0
} }
SequentialAnimation { SequentialAnimation {
id: bounceAnimation id: bounceAnimation
running: false running: false
NumberAnimation { NumberAnimation {
@@ -52,10 +78,12 @@ Item {
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasizedDecel easing.bezierCurve: Anims.emphasizedDecel
} }
} }
NumberAnimation { NumberAnimation {
id: exitAnimation id: exitAnimation
running: false running: false
target: translateY target: translateY
property: "y" property: "y"
@@ -65,18 +93,6 @@ Item {
easing.bezierCurve: Anims.emphasizedDecel easing.bezierCurve: Anims.emphasizedDecel
} }
onIsHoveredChanged: {
if (isHovered) {
exitAnimation.stop()
if (!bounceAnimation.running) {
bounceAnimation.restart()
}
} else {
bounceAnimation.stop()
exitAnimation.restart()
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
@@ -89,165 +105,133 @@ Item {
Timer { Timer {
id: longPressTimer id: longPressTimer
interval: 500 interval: 500
repeat: false repeat: false
onTriggered: { onTriggered: {
if (appData && appData.isPinned) { if (appData && appData.isPinned)
longPressing = true longPressing = true;
}
} }
} }
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
anchors.bottomMargin: -20 anchors.bottomMargin: -20
hoverEnabled: true hoverEnabled: true
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: (mouse) => {
onPressed: mouse => { if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
if (mouse.button === Qt.LeftButton && appData dragStartPos = Qt.point(mouse.x, mouse.y);
&& appData.isPinned) { longPressTimer.start();
dragStartPos = Qt.point(mouse.x, mouse.y)
longPressTimer.start()
} }
} }
onReleased: (mouse) => {
onReleased: mouse => { longPressTimer.stop();
longPressTimer.stop()
if (longPressing) { if (longPressing) {
if (dragging && targetIndex >= 0 if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps)
&& targetIndex !== originalIndex && dockApps) { dockApps.movePinnedApp(originalIndex, targetIndex);
dockApps.movePinnedApp(originalIndex, targetIndex)
}
longPressing = false longPressing = false;
dragging = false dragging = false;
dragOffset = Qt.point(0, 0) dragOffset = Qt.point(0, 0);
targetIndex = -1 targetIndex = -1;
originalIndex = -1 originalIndex = -1;
} }
} }
onPositionChanged: (mouse) => {
onPositionChanged: mouse => {
if (longPressing && !dragging) { if (longPressing && !dragging) {
var distance = Math.sqrt( var distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2));
Math.pow(mouse.x - dragStartPos.x,
2) + Math.pow(mouse.y - dragStartPos.y,
2))
if (distance > 5) { if (distance > 5) {
dragging = true dragging = true;
targetIndex = index targetIndex = index;
originalIndex = index originalIndex = index;
} }
} }
if (dragging) { if (dragging) {
dragOffset = Qt.point(mouse.x - dragStartPos.x, dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y);
mouse.y - dragStartPos.y)
if (dockApps) { if (dockApps) {
var threshold = 40 var threshold = 40;
var newTargetIndex = targetIndex var newTargetIndex = targetIndex;
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1)
if (dragOffset.x > threshold newTargetIndex = targetIndex + 1;
&& targetIndex < dockApps.pinnedAppCount - 1) { else if (dragOffset.x < -threshold && targetIndex > 0)
newTargetIndex = targetIndex + 1 newTargetIndex = targetIndex - 1;
} else if (dragOffset.x < -threshold
&& targetIndex > 0) {
newTargetIndex = targetIndex - 1
}
if (newTargetIndex !== targetIndex) { if (newTargetIndex !== targetIndex) {
targetIndex = newTargetIndex targetIndex = newTargetIndex;
dragStartPos = Qt.point(mouse.x, mouse.y) dragStartPos = Qt.point(mouse.x, mouse.y);
} }
} }
} }
} }
onClicked: (mouse) => {
onClicked: mouse => {
if (!appData || longPressing) if (!appData || longPressing)
return return ;
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
var windowCount = appData.windows ? appData.windows.count : 0 // Handle based on type
if (appData.type === "pinned") {
if (windowCount === 0) { // Launch the pinned app
if (appData && appData.appId) { if (appData && appData.appId) {
var desktopEntry = DesktopEntries.byId(appData.appId) var desktopEntry = DesktopEntries.byId(appData.appId);
if (desktopEntry) { if (desktopEntry)
AppUsageHistoryData.addAppUsage({ AppUsageHistoryData.addAppUsage({
"id": appData.appId, "id": appData.appId,
"name": desktopEntry.name "name": desktopEntry.name || appData.appId,
|| appData.appId, "icon": desktopEntry.icon || "",
"icon": desktopEntry.icon "exec": desktopEntry.exec || "",
|| "", "comment": desktopEntry.comment || ""
"exec": desktopEntry.exec });
|| "",
"comment": desktopEntry.comment Quickshell.execDetached(["gtk-launch", appData.appId]);
|| ""
})
} }
Quickshell.execDetached(["gtk-launch", appData.appId]) } else if (appData.type === "window") {
} // Focus the specific window
} else if (windowCount === 1) { if (appData.windowId)
var window = appData.windows.get(0) NiriService.focusWindow(appData.windowId);
NiriService.focusWindow(window.id)
} else {
windowsMenu.showForButton(root, appData, 40)
} }
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
if (appData && appData.appId) { if (appData && appData.appId) {
var desktopEntry = DesktopEntries.byId(appData.appId) var desktopEntry = DesktopEntries.byId(appData.appId);
if (desktopEntry) { if (desktopEntry)
AppUsageHistoryData.addAppUsage({ AppUsageHistoryData.addAppUsage({
"id": appData.appId, "id": appData.appId,
"name": desktopEntry.name "name": desktopEntry.name || appData.appId,
|| appData.appId, "icon": desktopEntry.icon || "",
"icon": desktopEntry.icon "exec": desktopEntry.exec || "",
|| "", "comment": desktopEntry.comment || ""
"exec": desktopEntry.exec });
|| "",
"comment": desktopEntry.comment Quickshell.execDetached(["gtk-launch", appData.appId]);
|| ""
})
}
Quickshell.execDetached(["gtk-launch", appData.appId])
} }
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
if (contextMenu) { if (contextMenu)
contextMenu.showForButton(root, appData, 40) contextMenu.showForButton(root, appData, 40);
}
}
}
}
property bool showTooltip: mouseArea.containsMouse && !dragging }
property string tooltipText: { }
if (!appData || !appData.appId)
return ""
var desktopEntry = DesktopEntries.byId(appData.appId)
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
} }
IconImage { IconImage {
id: iconImg id: iconImg
width: 40 width: 40
height: 40 height: 40
anchors.centerIn: parent anchors.centerIn: parent
source: { source: {
if (!appData || !appData.appId) if (!appData || !appData.appId)
return "" return "";
var desktopEntry = DesktopEntries.byId(appData.appId)
var desktopEntry = DesktopEntries.byId(appData.appId);
if (desktopEntry && desktopEntry.icon) { if (desktopEntry && desktopEntry.icon) {
var iconPath = Quickshell.iconPath( var iconPath = Quickshell.iconPath(desktopEntry.icon, SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme);
desktopEntry.icon, return iconPath;
SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme)
return iconPath
} }
return "" return "";
} }
smooth: true smooth: true
mipmap: true mipmap: true
@@ -270,44 +254,50 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: {
if (!appData || !appData.appId) if (!appData || !appData.appId)
return "?" return "?";
var desktopEntry = DesktopEntries.byId(appData.appId)
if (desktopEntry && desktopEntry.name) { var desktopEntry = DesktopEntries.byId(appData.appId);
return desktopEntry.name.charAt(0).toUpperCase() if (desktopEntry && desktopEntry.name)
} return desktopEntry.name.charAt(0).toUpperCase();
return appData.appId.charAt(0).toUpperCase()
return appData.appId.charAt(0).toUpperCase();
} }
font.pixelSize: 14 font.pixelSize: 14
color: Theme.primary color: Theme.primary
font.weight: Font.Bold font.weight: Font.Bold
} }
} }
Row { // Indicator for running/focused state
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: -2 anchors.bottomMargin: -2
spacing: 2 width: 8
Repeater {
model: appData && appData.windows ? Math.min(appData.windows.count, 4) : 0
Rectangle {
width: appData && appData.windows && appData.windows.count <= 3 ? 5 : 3
height: 2 height: 2
radius: 1 radius: 1
visible: appData && (appData.isRunning || appData.type === "window")
color: { color: {
if (!appData || !appData.windows || appData.windows.count === 0) if (!appData)
return "transparent" return "transparent";
var window = appData.windows.get(index)
return window // For window type, check if focused
&& window.id == NiriService.focusedWindowId ? Theme.primary : Qt.rgba( if (appData.type === "window" && appData.isFocused)
Theme.surfaceText.r, return Theme.primary;
Theme.surfaceText.g,
Theme.surfaceText.b, // For running apps, show dimmer indicator
0.6) if (appData.isRunning || appData.type === "window")
} return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
return "transparent";
} }
} }
transform: Translate {
id: translateY
y: 0
} }
} }

View File

@@ -48,84 +48,62 @@ Item {
clear() clear()
var items = [] var items = []
// Use ordered app IDs if available from Niri, fallback to unordered
var runningApps = NiriService.niriAvailable && NiriService.getRunningAppIdsOrdered
? NiriService.getRunningAppIdsOrdered()
: NiriService.getRunningAppIds()
var pinnedApps = [...(SessionData.pinnedApps || [])] var pinnedApps = [...(SessionData.pinnedApps || [])]
var addedApps = new Set()
// First section: Pinned apps (always visible, not representing running windows)
pinnedApps.forEach(appId => { pinnedApps.forEach(appId => {
var lowerAppId = appId.toLowerCase()
if (!addedApps.has(lowerAppId)) {
var windows = NiriService.getWindowsByAppId(
appId)
items.push({ items.push({
"type": "pinned",
"appId": appId, "appId": appId,
"windows": windows, "windowId": -1, // Use -1 instead of null to avoid ListModel warnings
"windowTitle": "",
"workspaceId": -1, // Use -1 instead of null
"isPinned": true, "isPinned": true,
"isRunning": windows.length > 0 "isRunning": false,
"isFocused": false
}) })
addedApps.add(lowerAppId)
}
}) })
root.pinnedAppCount = pinnedApps.length root.pinnedAppCount = pinnedApps.length
var appUsageRanking = AppUsageHistoryData.appUsageRanking || {}
var unpinnedApps = [] // Add separator between pinned and running if both exist
var unpinnedAppsSet = new Set() if (pinnedApps.length > 0 && NiriService.windows.length > 0) {
// First: Add ALL currently running apps that aren't pinned
// They come pre-ordered from NiriService if Niri is available
runningApps.forEach(appId => {
var lowerAppId = appId.toLowerCase()
if (!addedApps.has(lowerAppId)) {
unpinnedApps.push(appId)
unpinnedAppsSet.add(lowerAppId)
}
})
// Then: Fill remaining slots up to 3 with recently used apps
var remainingSlots = Math.max(0, 3 - unpinnedApps.length)
if (remainingSlots > 0) {
// Sort recent apps by usage
var recentApps = []
for (var appId in appUsageRanking) {
var lowerAppId = appId.toLowerCase()
if (!addedApps.has(lowerAppId) && !unpinnedAppsSet.has(
lowerAppId)) {
recentApps.push({
"appId": appId,
"lastUsed": appUsageRanking[appId].lastUsed
|| 0
})
}
}
recentApps.sort((a, b) => b.lastUsed - a.lastUsed)
var recentToAdd = Math.min(remainingSlots, recentApps.length)
for (var i = 0; i < recentToAdd; i++) {
unpinnedApps.push(recentApps[i].appId)
}
}
if (pinnedApps.length > 0 && unpinnedApps.length > 0) {
items.push({ items.push({
"type": "separator",
"appId": "__SEPARATOR__", "appId": "__SEPARATOR__",
"windows": [], "windowId": -1, // Use -1 instead of null
"windowTitle": "",
"workspaceId": -1, // Use -1 instead of null
"isPinned": false, "isPinned": false,
"isRunning": false "isRunning": false,
"isFocused": false
}) })
} }
unpinnedApps.forEach(appId => {
var windows = NiriService.getWindowsByAppId( // Second section: Running windows (sorted by display->workspace->position)
appId) // NiriService.windows is already sorted by sortWindowsByLayout
NiriService.windows.forEach(window => {
// Limit window title length for tooltip
var title = window.title || "(Unnamed)"
if (title.length > 50) {
title = title.substring(0, 47) + "..."
}
// Check if this window is focused - compare as numbers
var isFocused = window.id == NiriService.focusedWindowId
items.push({ items.push({
"appId": appId, "type": "window",
"windows": windows, "appId": window.app_id || "",
"windowId": window.id || -1,
"windowTitle": title,
"workspaceId": window.workspace_id || -1,
"isPinned": false, "isPinned": false,
"isRunning": windows.length > 0 "isRunning": true,
"isFocused": isFocused
}) })
}) })
items.forEach(item => { items.forEach(item => {
append(item) append(item)
}) })
@@ -136,11 +114,11 @@ Item {
id: delegateItem id: delegateItem
property alias dockButton: button property alias dockButton: button
width: model.appId === "__SEPARATOR__" ? 16 : 40 width: model.type === "separator" ? 16 : 40
height: 40 height: 40
Rectangle { Rectangle {
visible: model.appId === "__SEPARATOR__" visible: model.type === "separator"
width: 2 width: 2
height: 20 height: 20
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
@@ -150,7 +128,7 @@ Item {
DockAppButton { DockAppButton {
id: button id: button
visible: model.appId !== "__SEPARATOR__" visible: model.type !== "separator"
anchors.centerIn: parent anchors.centerIn: parent
width: 40 width: 40
@@ -161,6 +139,10 @@ Item {
windowsMenu: root.windowsMenu windowsMenu: root.windowsMenu
dockApps: root dockApps: root
index: model.index index: model.index
// Override tooltip for windows to show window title
showWindowTitle: model.type === "window"
windowTitle: model.windowTitle || ""
} }
} }
} }
@@ -174,7 +156,11 @@ Item {
function onWindowOpenedOrChanged() { function onWindowOpenedOrChanged() {
dockModel.updateModel() dockModel.updateModel()
} }
function onFocusedWindowIdChanged() {
dockModel.updateModel()
} }
}
Connections { Connections {
target: SessionData target: SessionData

View File

@@ -1,3 +1,4 @@
import Quickshell
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -12,24 +13,18 @@ Rectangle {
readonly property int maxNormalWidth: 456 readonly property int maxNormalWidth: 456
readonly property int maxCompactWidth: 288 readonly property int maxCompactWidth: 288
width: compactMode ? Math.min(baseWidth, width: compactMode ? Math.min(baseWidth, maxCompactWidth) : Math.min(baseWidth, maxNormalWidth)
maxCompactWidth) : Math.min(baseWidth,
maxNormalWidth)
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (!FocusedWindowService.focusedAppName if (!NiriService.focusedWindowTitle)
&& !FocusedWindowService.focusedWindowTitle) return "transparent";
return "transparent"
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
baseColor.a * Theme.widgetTransparency)
} }
clip: true clip: true
visible: FocusedWindowService.niriAvailable visible: NiriService.niriAvailable && NiriService.focusedWindowTitle
&& (FocusedWindowService.focusedAppName
|| FocusedWindowService.focusedWindowTitle)
Row { Row {
id: contentRow id: contentRow
@@ -40,7 +35,19 @@ Rectangle {
StyledText { StyledText {
id: appText id: appText
text: FocusedWindowService.focusedAppName || "" text: {
if (!NiriService.focusedWindowId)
return "";
var window = NiriService.windows.find((w) => {
return w.id == NiriService.focusedWindowId;
});
if (!window || !window.app_id)
return "";
var desktopEntry = DesktopEntries.byId(window.app_id);
return desktopEntry && desktopEntry.name ? desktopEntry.name : window.app_id;
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -62,7 +69,7 @@ Rectangle {
StyledText { StyledText {
id: titleText id: titleText
text: FocusedWindowService.focusedWindowTitle || "" text: NiriService.focusedWindowTitle || ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
@@ -72,6 +79,7 @@ Rectangle {
width: Math.min(implicitWidth, compactMode ? 280 : 250) width: Math.min(implicitWidth, compactMode ? 280 : 250)
visible: text.length > 0 visible: text.length > 0
} }
} }
MouseArea { MouseArea {
@@ -86,6 +94,7 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on width { Behavior on width {
@@ -93,5 +102,7 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }

View File

@@ -13,10 +13,8 @@ Rectangle {
property var parentScreen property var parentScreen
property var hoveredItem: null property var hoveredItem: null
property var topBar: null property var topBar: null
// The visual root for this window // The visual root for this window
property Item windowRoot: (Window.window ? Window.window.contentItem : null) property Item windowRoot: (Window.window ? Window.window.contentItem : null)
readonly property int windowCount: NiriService.windows.length readonly property int windowCount: NiriService.windows.length
readonly property int calculatedWidth: windowCount > 0 ? windowCount * 24 + (windowCount - 1) * Theme.spacingXS + Theme.spacingS * 2 : 0 readonly property int calculatedWidth: windowCount > 0 ? windowCount * 24 + (windowCount - 1) * Theme.spacingXS + Theme.spacingS * 2 : 0
@@ -27,35 +25,37 @@ Rectangle {
clip: false clip: false
color: { color: {
if (windowCount === 0) if (windowCount === 0)
return "transparent" return "transparent";
const baseColor = Theme.secondaryHover const baseColor = Theme.secondaryHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
baseColor.a * Theme.widgetTransparency)
} }
Row { Row {
id: windowRow id: windowRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
Repeater { Repeater {
id: windowRepeater id: windowRepeater
model: NiriService.windows model: NiriService.windows
delegate: Item { delegate: Item {
id: delegateItem id: delegateItem
property bool isFocused: String(modelData.id) === String(FocusedWindowService.focusedWindowId)
property bool isFocused: String(modelData.id) === String(NiriService.focusedWindowId)
property string appId: modelData.app_id || "" property string appId: modelData.app_id || ""
property string windowTitle: modelData.title || "(Unnamed)" property string windowTitle: modelData.title || "(Unnamed)"
property int windowId: modelData.id property int windowId: modelData.id
property string tooltipText: { property string tooltipText: {
var appName = "Unknown" var appName = "Unknown";
if (appId) { if (appId) {
var desktopEntry = DesktopEntries.byId(appId) var desktopEntry = DesktopEntries.byId(appId);
appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appId appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appId;
} }
return appName + (windowTitle ? " • " + windowTitle : "") return appName + (windowTitle ? " • " + windowTitle : "");
} }
width: 24 width: 24
@@ -65,11 +65,10 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (isFocused) { if (isFocused)
return mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) return mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
} else { else
return mouseArea.containsMouse ? Qt.rgba(Theme.primaryHover.r, Theme.primaryHover.g, Theme.primaryHover.b, 0.1) : "transparent" return mouseArea.containsMouse ? Qt.rgba(Theme.primaryHover.r, Theme.primaryHover.g, Theme.primaryHover.b, 0.1) : "transparent";
}
} }
Behavior on color { Behavior on color {
@@ -77,25 +76,28 @@ Rectangle {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
} }
// App icon // App icon
IconImage { IconImage {
id: iconImg id: iconImg
anchors.centerIn: parent anchors.centerIn: parent
width: 18 width: 18
height: 18 height: 18
source: { source: {
if (!appId) return "" if (!appId)
var desktopEntry = DesktopEntries.byId(appId) return "";
var desktopEntry = DesktopEntries.byId(appId);
if (desktopEntry && desktopEntry.icon) { if (desktopEntry && desktopEntry.icon) {
var iconPath = Quickshell.iconPath( var iconPath = Quickshell.iconPath(desktopEntry.icon, SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme);
desktopEntry.icon, return iconPath;
SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme)
return iconPath
} }
return "" return "";
} }
smooth: true smooth: true
mipmap: true mipmap: true
@@ -108,12 +110,14 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
visible: !iconImg.visible visible: !iconImg.visible
text: { text: {
if (!appId) return "?" if (!appId)
var desktopEntry = DesktopEntries.byId(appId) return "?";
if (desktopEntry && desktopEntry.name) {
return desktopEntry.name.charAt(0).toUpperCase() var desktopEntry = DesktopEntries.byId(appId);
} if (desktopEntry && desktopEntry.name)
return appId.charAt(0).toUpperCase() return desktopEntry.name.charAt(0).toUpperCase();
return appId.charAt(0).toUpperCase();
} }
font.pixelSize: 10 font.pixelSize: 10
color: Theme.surfaceText color: Theme.surfaceText
@@ -122,40 +126,47 @@ Rectangle {
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
NiriService.focusWindow(windowId) NiriService.focusWindow(windowId);
} }
onEntered: { onEntered: {
root.hoveredItem = delegateItem root.hoveredItem = delegateItem;
var globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height) var globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
tooltipLoader.active = true tooltipLoader.active = true;
if (tooltipLoader.item) { if (tooltipLoader.item) {
var tooltipY = Theme.barHeight + SettingsData.topBarSpacing + Theme.spacingXS var tooltipY = Theme.barHeight + SettingsData.topBarSpacing + Theme.spacingXS;
tooltipLoader.item.showTooltip(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen) tooltipLoader.item.showTooltip(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen);
} }
} }
onExited: { onExited: {
if (root.hoveredItem === delegateItem) { if (root.hoveredItem === delegateItem) {
root.hoveredItem = null root.hoveredItem = null;
if (tooltipLoader.item) { if (tooltipLoader.item)
tooltipLoader.item.hideTooltip() tooltipLoader.item.hideTooltip();
}
tooltipLoader.active = false tooltipLoader.active = false;
}
}
}
} }
} }
} }
}
}
}
Loader { Loader {
id: tooltipLoader id: tooltipLoader
active: false active: false
sourceComponent: RunningAppsTooltip { sourceComponent: RunningAppsTooltip {
} }
} }
} }

View File

@@ -1,116 +0,0 @@
pragma Singleton
pragma ComponentBehavior
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property bool niriAvailable: false
property string focusedAppId: ""
property string focusedAppName: ""
property string focusedWindowTitle: ""
property int focusedWindowId: -1
function updateFromNiriData() {
if (!root.niriAvailable) {
clearFocusedWindow()
return
}
let focusedWindow = NiriService.windows.find(w => w.is_focused)
if (focusedWindow) {
root.focusedAppId = focusedWindow.app_id || ""
root.focusedWindowTitle = focusedWindow.title || ""
root.focusedAppName = getDisplayName(focusedWindow.app_id || "")
root.focusedWindowId = parseInt(focusedWindow.id) || -1
} else {
setWorkspaceFallback()
}
}
function clearFocusedWindow() {
root.focusedAppId = ""
root.focusedAppName = ""
root.focusedWindowTitle = ""
root.focusedWindowId = -1
}
function setWorkspaceFallback() {
if (NiriService.focusedWorkspaceIndex >= 0 && NiriService.allWorkspaces.length > 0) {
const workspace = NiriService.allWorkspaces[NiriService.focusedWorkspaceIndex]
if (workspace) {
root.focusedAppId = "niri"
root.focusedAppName = "niri"
if (workspace.name && workspace.name.length > 0) {
root.focusedWindowTitle = workspace.name
} else {
root.focusedWindowTitle = "workspace " + workspace.idx
}
root.focusedWindowId = -1
} else {
clearFocusedWindow()
}
} else {
clearFocusedWindow()
}
}
function getDisplayName(appId) {
if (!appId)
return ""
const desktopEntry = DesktopEntries.byId(appId)
return desktopEntry && desktopEntry.name ? desktopEntry.name : ""
}
Component.onCompleted: {
root.niriAvailable = NiriService.niriAvailable
NiriService.onNiriAvailableChanged.connect(() => {
root.niriAvailable = NiriService.niriAvailable
if (root.niriAvailable)
updateFromNiriData()
})
if (root.niriAvailable)
updateFromNiriData()
}
Connections {
function onFocusedWindowIdChanged() {
const focusedWindowId = NiriService.focusedWindowId
if (!focusedWindowId) {
setWorkspaceFallback()
return
}
const focusedWindow = NiriService.windows.find(
w => w.id == focusedWindowId)
if (focusedWindow) {
root.focusedAppId = focusedWindow.app_id || ""
root.focusedWindowTitle = focusedWindow.title || ""
root.focusedAppName = getDisplayName(focusedWindow.app_id || "")
root.focusedWindowId = parseInt(focusedWindow.id) || -1
} else {
setWorkspaceFallback()
}
}
function onWindowsChanged() {
updateFromNiriData()
}
function onWindowOpenedOrChanged(windowData) {
if (windowData.is_focused) {
root.focusedAppId = windowData.app_id || ""
root.focusedWindowTitle = windowData.title || ""
root.focusedAppName = getDisplayName(windowData.app_id || "")
root.focusedWindowId = parseInt(windowData.id) || -1
}
}
target: NiriService
}
}

View File

@@ -307,6 +307,17 @@ Singleton {
function handleWindowsChanged(data) { function handleWindowsChanged(data) {
windows = sortWindowsByLayout(data.windows) windows = sortWindowsByLayout(data.windows)
// Extract focused window from initial state
var focusedWindow = windows.find(w => w.is_focused)
if (focusedWindow) {
focusedWindowId = String(focusedWindow.id)
focusedWindowIndex = windows.findIndex(w => w.id === focusedWindow.id)
} else {
focusedWindowId = ""
focusedWindowIndex = -1
}
updateFocusedWindow() updateFocusedWindow()
} }