mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-07 05:55:37 -05:00
dock: re-work to separate pins from all open windows
This commit is contained in:
@@ -7,307 +7,297 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var appData
|
property var appData
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
property var windowsMenu: null
|
property var windowsMenu: null
|
||||||
property var dockApps: null
|
property var dockApps: null
|
||||||
property int index: -1
|
property int index: -1
|
||||||
property bool longPressing: false
|
property bool longPressing: false
|
||||||
property bool dragging: false
|
property bool dragging: false
|
||||||
property point dragStartPos: Qt.point(0, 0)
|
property point dragStartPos: Qt.point(0, 0)
|
||||||
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 "";
|
||||||
|
|
||||||
width: 40
|
// For window type, show app name + window title
|
||||||
height: 40
|
if (appData.type === "window" && showWindowTitle) {
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
var appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId;
|
||||||
|
return appName + (windowTitle ? " • " + windowTitle : "");
|
||||||
transform: Translate {
|
|
||||||
id: translateY
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
SequentialAnimation {
|
|
||||||
id: bounceAnimation
|
|
||||||
running: false
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
target: translateY
|
|
||||||
property: "y"
|
|
||||||
to: -10
|
|
||||||
duration: Anims.durShort
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasizedAccel
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
target: translateY
|
|
||||||
property: "y"
|
|
||||||
to: -8
|
|
||||||
duration: Anims.durShort
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasizedDecel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: exitAnimation
|
|
||||||
running: false
|
|
||||||
target: translateY
|
|
||||||
property: "y"
|
|
||||||
to: 0
|
|
||||||
duration: Anims.durShort
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
easing.bezierCurve: Anims.emphasizedDecel
|
|
||||||
}
|
|
||||||
|
|
||||||
onIsHoveredChanged: {
|
|
||||||
if (isHovered) {
|
|
||||||
exitAnimation.stop()
|
|
||||||
if (!bounceAnimation.running) {
|
|
||||||
bounceAnimation.restart()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bounceAnimation.stop()
|
|
||||||
exitAnimation.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
|
||||||
border.width: 2
|
|
||||||
border.color: Theme.primary
|
|
||||||
visible: dragging
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: longPressTimer
|
|
||||||
interval: 500
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (appData && appData.isPinned) {
|
|
||||||
longPressing = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.bottomMargin: -20
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
||||||
|
|
||||||
onPressed: mouse => {
|
|
||||||
if (mouse.button === Qt.LeftButton && appData
|
|
||||||
&& appData.isPinned) {
|
|
||||||
dragStartPos = Qt.point(mouse.x, mouse.y)
|
|
||||||
longPressTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onReleased: mouse => {
|
|
||||||
longPressTimer.stop()
|
|
||||||
if (longPressing) {
|
|
||||||
if (dragging && targetIndex >= 0
|
|
||||||
&& targetIndex !== originalIndex && dockApps) {
|
|
||||||
dockApps.movePinnedApp(originalIndex, targetIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
longPressing = false
|
|
||||||
dragging = false
|
|
||||||
dragOffset = Qt.point(0, 0)
|
|
||||||
targetIndex = -1
|
|
||||||
originalIndex = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPositionChanged: mouse => {
|
|
||||||
if (longPressing && !dragging) {
|
|
||||||
var distance = Math.sqrt(
|
|
||||||
Math.pow(mouse.x - dragStartPos.x,
|
|
||||||
2) + Math.pow(mouse.y - dragStartPos.y,
|
|
||||||
2))
|
|
||||||
if (distance > 5) {
|
|
||||||
dragging = true
|
|
||||||
targetIndex = index
|
|
||||||
originalIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dragging) {
|
|
||||||
dragOffset = Qt.point(mouse.x - dragStartPos.x,
|
|
||||||
mouse.y - dragStartPos.y)
|
|
||||||
|
|
||||||
if (dockApps) {
|
|
||||||
var threshold = 40
|
|
||||||
var newTargetIndex = targetIndex
|
|
||||||
|
|
||||||
if (dragOffset.x > threshold
|
|
||||||
&& targetIndex < dockApps.pinnedAppCount - 1) {
|
|
||||||
newTargetIndex = targetIndex + 1
|
|
||||||
} else if (dragOffset.x < -threshold
|
|
||||||
&& targetIndex > 0) {
|
|
||||||
newTargetIndex = targetIndex - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newTargetIndex !== targetIndex) {
|
|
||||||
targetIndex = newTargetIndex
|
|
||||||
dragStartPos = Qt.point(mouse.x, mouse.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: mouse => {
|
|
||||||
if (!appData || longPressing)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
var windowCount = appData.windows ? appData.windows.count : 0
|
|
||||||
|
|
||||||
if (windowCount === 0) {
|
|
||||||
if (appData && appData.appId) {
|
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
|
||||||
if (desktopEntry) {
|
|
||||||
AppUsageHistoryData.addAppUsage({
|
|
||||||
"id": appData.appId,
|
|
||||||
"name": desktopEntry.name
|
|
||||||
|| appData.appId,
|
|
||||||
"icon": desktopEntry.icon
|
|
||||||
|| "",
|
|
||||||
"exec": desktopEntry.exec
|
|
||||||
|| "",
|
|
||||||
"comment": desktopEntry.comment
|
|
||||||
|| ""
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Quickshell.execDetached(["gtk-launch", appData.appId])
|
|
||||||
}
|
|
||||||
} else if (windowCount === 1) {
|
|
||||||
var window = appData.windows.get(0)
|
|
||||||
NiriService.focusWindow(window.id)
|
|
||||||
} else {
|
|
||||||
windowsMenu.showForButton(root, appData, 40)
|
|
||||||
}
|
|
||||||
} else if (mouse.button === Qt.MiddleButton) {
|
|
||||||
if (appData && appData.appId) {
|
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
|
||||||
if (desktopEntry) {
|
|
||||||
AppUsageHistoryData.addAppUsage({
|
|
||||||
"id": appData.appId,
|
|
||||||
"name": desktopEntry.name
|
|
||||||
|| appData.appId,
|
|
||||||
"icon": desktopEntry.icon
|
|
||||||
|| "",
|
|
||||||
"exec": desktopEntry.exec
|
|
||||||
|| "",
|
|
||||||
"comment": desktopEntry.comment
|
|
||||||
|| ""
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Quickshell.execDetached(["gtk-launch", appData.appId])
|
|
||||||
}
|
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
|
||||||
if (contextMenu) {
|
|
||||||
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 {
|
|
||||||
id: iconImg
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
anchors.centerIn: parent
|
|
||||||
source: {
|
|
||||||
if (!appData || !appData.appId)
|
|
||||||
return ""
|
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
|
||||||
if (desktopEntry && desktopEntry.icon) {
|
|
||||||
var iconPath = Quickshell.iconPath(
|
|
||||||
desktopEntry.icon,
|
|
||||||
SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme)
|
|
||||||
return iconPath
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
smooth: true
|
|
||||||
mipmap: true
|
|
||||||
asynchronous: true
|
|
||||||
visible: status === Image.Ready
|
|
||||||
implicitSize: 40
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
anchors.centerIn: parent
|
|
||||||
visible: !iconImg.visible
|
|
||||||
color: Theme.surfaceLight
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.primarySelected
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: {
|
|
||||||
if (!appData || !appData.appId)
|
|
||||||
return "?"
|
|
||||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
|
||||||
if (desktopEntry && desktopEntry.name) {
|
|
||||||
return desktopEntry.name.charAt(0).toUpperCase()
|
|
||||||
}
|
}
|
||||||
return appData.appId.charAt(0).toUpperCase()
|
// For pinned apps, just show app name
|
||||||
}
|
if (!appData.appId)
|
||||||
font.pixelSize: 14
|
return "";
|
||||||
color: Theme.primary
|
|
||||||
font.weight: Font.Bold
|
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||||
|
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
width: 40
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
height: 40
|
||||||
anchors.bottom: parent.bottom
|
onIsHoveredChanged: {
|
||||||
anchors.bottomMargin: -2
|
if (isHovered) {
|
||||||
spacing: 2
|
exitAnimation.stop();
|
||||||
|
if (!bounceAnimation.running)
|
||||||
|
bounceAnimation.restart();
|
||||||
|
|
||||||
Repeater {
|
} else {
|
||||||
model: appData && appData.windows ? Math.min(appData.windows.count, 4) : 0
|
bounceAnimation.stop();
|
||||||
|
exitAnimation.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
SequentialAnimation {
|
||||||
width: appData && appData.windows && appData.windows.count <= 3 ? 5 : 3
|
id: bounceAnimation
|
||||||
|
|
||||||
|
running: false
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: translateY
|
||||||
|
property: "y"
|
||||||
|
to: -10
|
||||||
|
duration: Anims.durShort
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasizedAccel
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: translateY
|
||||||
|
property: "y"
|
||||||
|
to: -8
|
||||||
|
duration: Anims.durShort
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasizedDecel
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
id: exitAnimation
|
||||||
|
|
||||||
|
running: false
|
||||||
|
target: translateY
|
||||||
|
property: "y"
|
||||||
|
to: 0
|
||||||
|
duration: Anims.durShort
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Anims.emphasizedDecel
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||||
|
border.width: 2
|
||||||
|
border.color: Theme.primary
|
||||||
|
visible: dragging
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: longPressTimer
|
||||||
|
|
||||||
|
interval: 500
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (appData && appData.isPinned)
|
||||||
|
longPressing = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.bottomMargin: -20
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
onPressed: (mouse) => {
|
||||||
|
if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
|
||||||
|
dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||||
|
longPressTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: (mouse) => {
|
||||||
|
longPressTimer.stop();
|
||||||
|
if (longPressing) {
|
||||||
|
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps)
|
||||||
|
dockApps.movePinnedApp(originalIndex, targetIndex);
|
||||||
|
|
||||||
|
longPressing = false;
|
||||||
|
dragging = false;
|
||||||
|
dragOffset = Qt.point(0, 0);
|
||||||
|
targetIndex = -1;
|
||||||
|
originalIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (longPressing && !dragging) {
|
||||||
|
var distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2));
|
||||||
|
if (distance > 5) {
|
||||||
|
dragging = true;
|
||||||
|
targetIndex = index;
|
||||||
|
originalIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dragging) {
|
||||||
|
dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y);
|
||||||
|
if (dockApps) {
|
||||||
|
var threshold = 40;
|
||||||
|
var newTargetIndex = targetIndex;
|
||||||
|
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1)
|
||||||
|
newTargetIndex = targetIndex + 1;
|
||||||
|
else if (dragOffset.x < -threshold && targetIndex > 0)
|
||||||
|
newTargetIndex = targetIndex - 1;
|
||||||
|
if (newTargetIndex !== targetIndex) {
|
||||||
|
targetIndex = newTargetIndex;
|
||||||
|
dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (!appData || longPressing)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
if (mouse.button === Qt.LeftButton) {
|
||||||
|
// Handle based on type
|
||||||
|
if (appData.type === "pinned") {
|
||||||
|
// Launch the pinned app
|
||||||
|
if (appData && appData.appId) {
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||||
|
if (desktopEntry)
|
||||||
|
AppUsageHistoryData.addAppUsage({
|
||||||
|
"id": appData.appId,
|
||||||
|
"name": desktopEntry.name || appData.appId,
|
||||||
|
"icon": desktopEntry.icon || "",
|
||||||
|
"exec": desktopEntry.exec || "",
|
||||||
|
"comment": desktopEntry.comment || ""
|
||||||
|
});
|
||||||
|
|
||||||
|
Quickshell.execDetached(["gtk-launch", appData.appId]);
|
||||||
|
}
|
||||||
|
} else if (appData.type === "window") {
|
||||||
|
// Focus the specific window
|
||||||
|
if (appData.windowId)
|
||||||
|
NiriService.focusWindow(appData.windowId);
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (mouse.button === Qt.MiddleButton) {
|
||||||
|
if (appData && appData.appId) {
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||||
|
if (desktopEntry)
|
||||||
|
AppUsageHistoryData.addAppUsage({
|
||||||
|
"id": appData.appId,
|
||||||
|
"name": desktopEntry.name || appData.appId,
|
||||||
|
"icon": desktopEntry.icon || "",
|
||||||
|
"exec": desktopEntry.exec || "",
|
||||||
|
"comment": desktopEntry.comment || ""
|
||||||
|
});
|
||||||
|
|
||||||
|
Quickshell.execDetached(["gtk-launch", appData.appId]);
|
||||||
|
}
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
|
if (contextMenu)
|
||||||
|
contextMenu.showForButton(root, appData, 40);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
id: iconImg
|
||||||
|
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
anchors.centerIn: parent
|
||||||
|
source: {
|
||||||
|
if (!appData || !appData.appId)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||||
|
if (desktopEntry && desktopEntry.icon) {
|
||||||
|
var iconPath = Quickshell.iconPath(desktopEntry.icon, SettingsData.iconTheme === "System Default" ? "" : SettingsData.iconTheme);
|
||||||
|
return iconPath;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
|
asynchronous: true
|
||||||
|
visible: status === Image.Ready
|
||||||
|
implicitSize: 40
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !iconImg.visible
|
||||||
|
color: Theme.surfaceLight
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.primarySelected
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: {
|
||||||
|
if (!appData || !appData.appId)
|
||||||
|
return "?";
|
||||||
|
|
||||||
|
var desktopEntry = DesktopEntries.byId(appData.appId);
|
||||||
|
if (desktopEntry && desktopEntry.name)
|
||||||
|
return desktopEntry.name.charAt(0).toUpperCase();
|
||||||
|
|
||||||
|
return appData.appId.charAt(0).toUpperCase();
|
||||||
|
}
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: Theme.primary
|
||||||
|
font.weight: Font.Bold
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicator for running/focused state
|
||||||
|
Rectangle {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: -2
|
||||||
|
width: 8
|
||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,87 +48,65 @@ 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({
|
|
||||||
"appId": appId,
|
|
||||||
"windows": windows,
|
|
||||||
"isPinned": true,
|
|
||||||
"isRunning": windows.length > 0
|
|
||||||
})
|
|
||||||
addedApps.add(lowerAppId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
root.pinnedAppCount = pinnedApps.length
|
|
||||||
var appUsageRanking = AppUsageHistoryData.appUsageRanking || {}
|
|
||||||
|
|
||||||
var unpinnedApps = []
|
|
||||||
var unpinnedAppsSet = new Set()
|
|
||||||
|
|
||||||
// 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({
|
||||||
"appId": "__SEPARATOR__",
|
"type": "pinned",
|
||||||
"windows": [],
|
"appId": appId,
|
||||||
"isPinned": false,
|
"windowId": -1, // Use -1 instead of null to avoid ListModel warnings
|
||||||
"isRunning": false
|
"windowTitle": "",
|
||||||
})
|
"workspaceId": -1, // Use -1 instead of null
|
||||||
|
"isPinned": true,
|
||||||
|
"isRunning": false,
|
||||||
|
"isFocused": false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
root.pinnedAppCount = pinnedApps.length
|
||||||
|
|
||||||
|
// Add separator between pinned and running if both exist
|
||||||
|
if (pinnedApps.length > 0 && NiriService.windows.length > 0) {
|
||||||
|
items.push({
|
||||||
|
"type": "separator",
|
||||||
|
"appId": "__SEPARATOR__",
|
||||||
|
"windowId": -1, // Use -1 instead of null
|
||||||
|
"windowTitle": "",
|
||||||
|
"workspaceId": -1, // Use -1 instead of null
|
||||||
|
"isPinned": 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
|
||||||
items.push({
|
NiriService.windows.forEach(window => {
|
||||||
"appId": appId,
|
// Limit window title length for tooltip
|
||||||
"windows": windows,
|
var title = window.title || "(Unnamed)"
|
||||||
"isPinned": false,
|
if (title.length > 50) {
|
||||||
"isRunning": windows.length > 0
|
title = title.substring(0, 47) + "..."
|
||||||
})
|
}
|
||||||
})
|
|
||||||
|
// Check if this window is focused - compare as numbers
|
||||||
|
var isFocused = window.id == NiriService.focusedWindowId
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
"type": "window",
|
||||||
|
"appId": window.app_id || "",
|
||||||
|
"windowId": window.id || -1,
|
||||||
|
"windowTitle": title,
|
||||||
|
"workspaceId": window.workspace_id || -1,
|
||||||
|
"isPinned": false,
|
||||||
|
"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,8 +156,12 @@ Item {
|
|||||||
function onWindowOpenedOrChanged() {
|
function onWindowOpenedOrChanged() {
|
||||||
dockModel.updateModel()
|
dockModel.updateModel()
|
||||||
}
|
}
|
||||||
|
function onFocusedWindowIdChanged() {
|
||||||
|
dockModel.updateModel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
function onPinnedAppsChanged() {
|
function onPinnedAppsChanged() {
|
||||||
|
|||||||
@@ -1,97 +1,108 @@
|
|||||||
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool compactMode: SettingsData.focusedWindowCompactMode
|
property bool compactMode: SettingsData.focusedWindowCompactMode
|
||||||
property int availableWidth: 400
|
property int availableWidth: 400
|
||||||
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
|
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
|
||||||
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,
|
height: 30
|
||||||
maxNormalWidth)
|
radius: Theme.cornerRadius
|
||||||
height: 30
|
color: {
|
||||||
radius: Theme.cornerRadius
|
if (!NiriService.focusedWindowTitle)
|
||||||
color: {
|
return "transparent";
|
||||||
if (!FocusedWindowService.focusedAppName
|
|
||||||
&& !FocusedWindowService.focusedWindowTitle)
|
|
||||||
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: NiriService.niriAvailable && NiriService.focusedWindowTitle
|
||||||
visible: FocusedWindowService.niriAvailable
|
|
||||||
&& (FocusedWindowService.focusedAppName
|
|
||||||
|| FocusedWindowService.focusedWindowTitle)
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: contentRow
|
id: contentRow
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: appText
|
id: appText
|
||||||
|
|
||||||
|
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.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
width: Math.min(implicitWidth, compactMode ? 80 : 180)
|
||||||
|
visible: !compactMode && text.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "•"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.outlineButton
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: !compactMode && appText.text && titleText.text
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: titleText
|
||||||
|
|
||||||
|
text: NiriService.focusedWindowTitle || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
width: Math.min(implicitWidth, compactMode ? 280 : 250)
|
||||||
|
visible: text.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
text: FocusedWindowService.focusedAppName || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
width: Math.min(implicitWidth, compactMode ? 80 : 180)
|
|
||||||
visible: !compactMode && text.length > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
MouseArea {
|
||||||
text: "•"
|
id: mouseArea
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.outlineButton
|
anchors.fill: parent
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
hoverEnabled: true
|
||||||
visible: !compactMode && appText.text && titleText.text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
Behavior on color {
|
||||||
id: titleText
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
|
||||||
text: FocusedWindowService.focusedWindowTitle || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
width: Math.min(implicitWidth, compactMode ? 280 : 250)
|
|
||||||
visible: text.length > 0
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
Behavior on width {
|
||||||
id: mouseArea
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,16 @@ import qs.Widgets
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string section: "left"
|
property string section: "left"
|
||||||
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
|
||||||
|
|
||||||
width: calculatedWidth
|
width: calculatedWidth
|
||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -27,135 +25,148 @@ 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
|
||||||
height: 24
|
height: 24
|
||||||
|
|
||||||
Rectangle {
|
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 {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
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
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status === Image.Ready
|
visible: status === Image.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback text if no icon found
|
// Fallback text if no icon found
|
||||||
Text {
|
Text {
|
||||||
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
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user