mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
587 lines
24 KiB
QML
587 lines
24 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Widgets
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
clip: false
|
|
property var appData
|
|
property var contextMenu: null
|
|
property var dockApps: null
|
|
property int index: -1
|
|
property var parentDockScreen: null
|
|
property bool longPressing: false
|
|
property bool dragging: false
|
|
property point dragStartPos: Qt.point(0, 0)
|
|
property point dragOffset: Qt.point(0, 0)
|
|
property int targetIndex: -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 var cachedDesktopEntry: null
|
|
property real actualIconSize: 40
|
|
|
|
function updateDesktopEntry() {
|
|
if (!appData || appData.appId === "__SEPARATOR__") {
|
|
cachedDesktopEntry = null
|
|
return
|
|
}
|
|
const moddedId = Paths.moddedAppId(appData.appId)
|
|
cachedDesktopEntry = DesktopEntries.heuristicLookup(moddedId)
|
|
}
|
|
|
|
Component.onCompleted: updateDesktopEntry()
|
|
|
|
onAppDataChanged: updateDesktopEntry()
|
|
|
|
Connections {
|
|
target: DesktopEntries
|
|
function onApplicationsChanged() {
|
|
updateDesktopEntry()
|
|
}
|
|
}
|
|
property bool isWindowFocused: {
|
|
if (!appData) {
|
|
return false
|
|
}
|
|
|
|
if (appData.type === "window") {
|
|
const toplevel = getToplevelObject()
|
|
if (!toplevel) {
|
|
return false
|
|
}
|
|
return toplevel.activated
|
|
} else if (appData.type === "grouped") {
|
|
// For grouped apps, check if any window is focused
|
|
const allToplevels = ToplevelManager.toplevels.values
|
|
for (let i = 0; i < allToplevels.length; i++) {
|
|
const toplevel = allToplevels[i]
|
|
if (toplevel.appId === appData.appId && toplevel.activated) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
property string tooltipText: {
|
|
if (!appData) {
|
|
return ""
|
|
}
|
|
|
|
if ((appData.type === "window" && showWindowTitle) || (appData.type === "grouped" && appData.windowTitle)) {
|
|
const appName = cachedDesktopEntry && cachedDesktopEntry.name ? cachedDesktopEntry.name : appData.appId
|
|
const title = appData.type === "window" ? windowTitle : appData.windowTitle
|
|
return appName + (title ? " • " + title : "")
|
|
}
|
|
|
|
if (!appData.appId) {
|
|
return ""
|
|
}
|
|
|
|
return cachedDesktopEntry && cachedDesktopEntry.name ? cachedDesktopEntry.name : appData.appId
|
|
}
|
|
|
|
function getToplevelObject() {
|
|
if (!appData) {
|
|
return null
|
|
}
|
|
|
|
const sortedToplevels = CompositorService.sortedToplevels
|
|
if (!sortedToplevels) {
|
|
return null
|
|
}
|
|
|
|
if (appData.type === "window") {
|
|
if (appData.uniqueId) {
|
|
for (var i = 0; i < sortedToplevels.length; i++) {
|
|
const toplevel = sortedToplevels[i]
|
|
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
|
|
if (checkId === appData.uniqueId) {
|
|
return toplevel
|
|
}
|
|
}
|
|
}
|
|
|
|
if (appData.windowId !== undefined && appData.windowId !== null && appData.windowId >= 0) {
|
|
if (appData.windowId < sortedToplevels.length) {
|
|
return sortedToplevels[appData.windowId]
|
|
}
|
|
}
|
|
} else if (appData.type === "grouped") {
|
|
if (appData.windowId !== undefined && appData.windowId !== null && appData.windowId >= 0) {
|
|
if (appData.windowId < sortedToplevels.length) {
|
|
return sortedToplevels[appData.windowId]
|
|
}
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function getGroupedToplevels() {
|
|
if (!appData || appData.type !== "grouped") {
|
|
return []
|
|
}
|
|
|
|
const toplevels = []
|
|
const allToplevels = ToplevelManager.toplevels.values
|
|
for (let i = 0; i < allToplevels.length; i++) {
|
|
const toplevel = allToplevels[i]
|
|
if (toplevel.appId === appData.appId) {
|
|
toplevels.push(toplevel)
|
|
}
|
|
}
|
|
return toplevels
|
|
}
|
|
onIsHoveredChanged: {
|
|
if (mouseArea.pressed) return
|
|
|
|
if (isHovered) {
|
|
exitAnimation.stop()
|
|
if (!bounceAnimation.running) {
|
|
bounceAnimation.restart()
|
|
}
|
|
} else {
|
|
bounceAnimation.stop()
|
|
exitAnimation.restart()
|
|
}
|
|
}
|
|
|
|
readonly property bool animateX: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
|
readonly property real animationDistance: actualIconSize
|
|
readonly property real animationDirection: {
|
|
if (SettingsData.dockPosition === SettingsData.Position.Bottom) return -1
|
|
if (SettingsData.dockPosition === SettingsData.Position.Top) return 1
|
|
if (SettingsData.dockPosition === SettingsData.Position.Right) return -1
|
|
if (SettingsData.dockPosition === SettingsData.Position.Left) return 1
|
|
return -1
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: bounceAnimation
|
|
|
|
running: false
|
|
|
|
NumberAnimation {
|
|
target: iconTransform
|
|
property: animateX ? "x" : "y"
|
|
to: animationDirection * animationDistance * 0.25
|
|
duration: Anims.durShort
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Anims.emphasizedAccel
|
|
}
|
|
|
|
NumberAnimation {
|
|
target: iconTransform
|
|
property: animateX ? "x" : "y"
|
|
to: animationDirection * animationDistance * 0.2
|
|
duration: Anims.durShort
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Anims.emphasizedDecel
|
|
}
|
|
}
|
|
|
|
NumberAnimation {
|
|
id: exitAnimation
|
|
|
|
running: false
|
|
target: iconTransform
|
|
property: animateX ? "x" : "y"
|
|
to: 0
|
|
duration: Anims.durShort
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Anims.emphasizedDecel
|
|
}
|
|
|
|
Timer {
|
|
id: longPressTimer
|
|
|
|
interval: 500
|
|
repeat: false
|
|
onTriggered: {
|
|
if (appData && appData.isPinned) {
|
|
longPressing = true
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: mouseArea
|
|
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
enabled: true
|
|
preventStealing: 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) {
|
|
const 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) {
|
|
const threshold = actualIconSize
|
|
let 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) {
|
|
if (appData.type === "pinned") {
|
|
if (appData && appData.appId) {
|
|
const desktopEntry = cachedDesktopEntry
|
|
if (desktopEntry) {
|
|
AppUsageHistoryData.addAppUsage({
|
|
"id": appData.appId,
|
|
"name": desktopEntry.name || appData.appId,
|
|
"icon": desktopEntry.icon || "",
|
|
"exec": desktopEntry.exec || "",
|
|
"comment": desktopEntry.comment || ""
|
|
})
|
|
}
|
|
SessionService.launchDesktopEntry(desktopEntry)
|
|
}
|
|
} else if (appData.type === "window") {
|
|
const toplevel = getToplevelObject()
|
|
if (toplevel) {
|
|
toplevel.activate()
|
|
}
|
|
} else if (appData.type === "grouped") {
|
|
if (appData.windowCount === 0) {
|
|
if (appData && appData.appId) {
|
|
const desktopEntry = cachedDesktopEntry
|
|
if (desktopEntry) {
|
|
AppUsageHistoryData.addAppUsage({
|
|
"id": appData.appId,
|
|
"name": desktopEntry.name || appData.appId,
|
|
"icon": desktopEntry.icon || "",
|
|
"exec": desktopEntry.exec || "",
|
|
"comment": desktopEntry.comment || ""
|
|
})
|
|
}
|
|
SessionService.launchDesktopEntry(desktopEntry)
|
|
}
|
|
} else if (appData.windowCount === 1) {
|
|
// For single window, activate directly
|
|
const toplevel = getToplevelObject()
|
|
if (toplevel) {
|
|
console.log("Activating grouped app window:", appData.windowTitle)
|
|
toplevel.activate()
|
|
} else {
|
|
console.warn("No toplevel found for grouped app")
|
|
}
|
|
} else {
|
|
if (contextMenu) {
|
|
contextMenu.showForButton(root, appData, root.height + 25, true, cachedDesktopEntry, parentDockScreen)
|
|
}
|
|
}
|
|
}
|
|
} else if (mouse.button === Qt.MiddleButton) {
|
|
if (appData && appData.type === "window") {
|
|
const sortedToplevels = CompositorService.sortedToplevels
|
|
for (var i = 0; i < sortedToplevels.length; i++) {
|
|
const toplevel = sortedToplevels[i]
|
|
const checkId = toplevel.title + "|" + (toplevel.appId || "") + "|" + i
|
|
if (checkId === appData.uniqueId) {
|
|
toplevel.close()
|
|
break
|
|
}
|
|
}
|
|
} else if (appData && appData.type === "grouped") {
|
|
if (contextMenu) {
|
|
contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen)
|
|
}
|
|
} else if (appData && appData.appId) {
|
|
const desktopEntry = cachedDesktopEntry
|
|
if (desktopEntry) {
|
|
AppUsageHistoryData.addAppUsage({
|
|
"id": appData.appId,
|
|
"name": desktopEntry.name || appData.appId,
|
|
"icon": desktopEntry.icon || "",
|
|
"exec": desktopEntry.exec || "",
|
|
"comment": desktopEntry.comment || ""
|
|
})
|
|
}
|
|
SessionService.launchDesktopEntry(desktopEntry)
|
|
}
|
|
} else if (mouse.button === Qt.RightButton) {
|
|
if (contextMenu && appData) {
|
|
contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen)
|
|
} else {
|
|
console.warn("No context menu or appData available")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: visualContent
|
|
anchors.fill: parent
|
|
|
|
transform: Translate {
|
|
id: iconTransform
|
|
x: 0
|
|
y: 0
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
IconImage {
|
|
id: iconImg
|
|
|
|
anchors.centerIn: parent
|
|
implicitSize: actualIconSize
|
|
source: {
|
|
if (appData.appId === "__SEPARATOR__") {
|
|
return ""
|
|
}
|
|
const moddedId = Paths.moddedAppId(appData.appId)
|
|
if (moddedId.toLowerCase().includes("steam_app")) {
|
|
return ""
|
|
}
|
|
return cachedDesktopEntry && cachedDesktopEntry.icon ? Quickshell.iconPath(cachedDesktopEntry.icon, true) : ""
|
|
}
|
|
mipmap: true
|
|
smooth: true
|
|
asynchronous: true
|
|
visible: status === Image.Ready
|
|
}
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
size: actualIconSize
|
|
name: "sports_esports"
|
|
color: Theme.surfaceText
|
|
visible: {
|
|
if (!appData || !appData.appId || appData.appId === "__SEPARATOR__") {
|
|
return false
|
|
}
|
|
const moddedId = Paths.moddedAppId(appData.appId)
|
|
return moddedId.toLowerCase().includes("steam_app")
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: actualIconSize
|
|
height: actualIconSize
|
|
anchors.centerIn: parent
|
|
visible: iconImg.status !== Image.Ready
|
|
color: Theme.surfaceLight
|
|
radius: Theme.cornerRadius
|
|
border.width: 1
|
|
border.color: Theme.primarySelected
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: {
|
|
if (!appData || !appData.appId) {
|
|
return "?"
|
|
}
|
|
|
|
const desktopEntry = cachedDesktopEntry
|
|
if (desktopEntry && desktopEntry.name) {
|
|
return desktopEntry.name.charAt(0).toUpperCase()
|
|
}
|
|
|
|
return appData.appId.charAt(0).toUpperCase()
|
|
}
|
|
font.pixelSize: Math.max(8, parent.width * 0.35)
|
|
color: Theme.primary
|
|
font.weight: Font.Bold
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
anchors.horizontalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.horizontalCenter
|
|
anchors.verticalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? parent.verticalCenter : undefined
|
|
anchors.bottom: SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined
|
|
anchors.top: SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined
|
|
anchors.left: SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
|
|
anchors.right: SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
|
|
anchors.bottomMargin: SettingsData.dockPosition === SettingsData.Position.Bottom ? -(SettingsData.dockSpacing / 2) : 0
|
|
anchors.topMargin: SettingsData.dockPosition === SettingsData.Position.Top ? -(SettingsData.dockSpacing / 2) : 0
|
|
anchors.leftMargin: SettingsData.dockPosition === SettingsData.Position.Left ? -(SettingsData.dockSpacing / 2) : 0
|
|
anchors.rightMargin: SettingsData.dockPosition === SettingsData.Position.Right ? -(SettingsData.dockSpacing / 2) : 0
|
|
|
|
sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator
|
|
|
|
visible: {
|
|
if (!appData) return false
|
|
if (appData.type === "window") return true
|
|
if (appData.type === "grouped") return appData.windowCount > 0
|
|
return appData.isRunning
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: rowIndicator
|
|
|
|
Row {
|
|
spacing: 2
|
|
|
|
Repeater {
|
|
model: {
|
|
if (!appData) return 0
|
|
if (appData.type === "grouped") {
|
|
return Math.min(appData.windowCount, 4)
|
|
} else if (appData.type === "window" || appData.isRunning) {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
Rectangle {
|
|
width: {
|
|
if (SettingsData.dockIndicatorStyle === "circle") {
|
|
return Math.max(4, actualIconSize * 0.1)
|
|
}
|
|
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
|
|
}
|
|
height: {
|
|
if (SettingsData.dockIndicatorStyle === "circle") {
|
|
return Math.max(4, actualIconSize * 0.1)
|
|
}
|
|
return Math.max(2, actualIconSize * 0.05)
|
|
}
|
|
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
|
|
color: {
|
|
if (!appData) {
|
|
return "transparent"
|
|
}
|
|
|
|
if (appData.type !== "grouped" || appData.windowCount === 1) {
|
|
if (isWindowFocused) {
|
|
return Theme.primary
|
|
}
|
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
}
|
|
|
|
if (appData.type === "grouped" && appData.windowCount > 1) {
|
|
const groupToplevels = getGroupedToplevels()
|
|
if (index < groupToplevels.length && groupToplevels[index].activated) {
|
|
return Theme.primary
|
|
}
|
|
}
|
|
|
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: columnIndicator
|
|
|
|
Column {
|
|
spacing: 2
|
|
|
|
Repeater {
|
|
model: {
|
|
if (!appData) return 0
|
|
if (appData.type === "grouped") {
|
|
return Math.min(appData.windowCount, 4)
|
|
} else if (appData.type === "window" || appData.isRunning) {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
Rectangle {
|
|
width: {
|
|
if (SettingsData.dockIndicatorStyle === "circle") {
|
|
return Math.max(4, actualIconSize * 0.1)
|
|
}
|
|
return Math.max(2, actualIconSize * 0.05)
|
|
}
|
|
height: {
|
|
if (SettingsData.dockIndicatorStyle === "circle") {
|
|
return Math.max(4, actualIconSize * 0.1)
|
|
}
|
|
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
|
|
}
|
|
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
|
|
color: {
|
|
if (!appData) {
|
|
return "transparent"
|
|
}
|
|
|
|
if (appData.type !== "grouped" || appData.windowCount === 1) {
|
|
if (isWindowFocused) {
|
|
return Theme.primary
|
|
}
|
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
}
|
|
|
|
if (appData.type === "grouped" && appData.windowCount > 1) {
|
|
const groupToplevels = getGroupedToplevels()
|
|
if (index < groupToplevels.length && groupToplevels[index].activated) {
|
|
return Theme.primary
|
|
}
|
|
}
|
|
|
|
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|