1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00
Files
DankMaterialShell/quickshell/Modules/DankBar/Widgets/AppsDock.qml
purian23 808ee66e11 feat: AppsDock Widget on the Dankbar
- Pinnable apps independent from the main dock
- Drag & Drop support
2026-01-23 11:49:45 -05:00

868 lines
36 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property var widgetData: null
property var barConfig: null
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "left"
property var parentScreen
property var hoveredItem: null
property var topBar: null
property real widgetThickness: 30
property real barThickness: 48
property real barSpacing: 4
property bool isAutoHideBar: false
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
property int draggedIndex: -1
property int dropTargetIndex: -1
property bool suppressShiftAnimation: false
property int pinnedAppCount: 0
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 || !isVertical) {
return 0;
}
if (isAutoHideBar) {
return 0;
}
if (parentScreen.y > 0) {
return effectiveBarThickness;
}
return 0;
}
// --- Dock Logic Helpers ---
function movePinnedApp(fromDockIndex, toDockIndex) {
if (fromDockIndex === toDockIndex)
return;
const currentPinned = [...(SessionData.barPinnedApps || [])];
if (fromDockIndex < 0 || fromDockIndex >= currentPinned.length || toDockIndex < 0 || toDockIndex >= currentPinned.length) {
return;
}
const movedApp = currentPinned.splice(fromDockIndex, 1)[0];
currentPinned.splice(toDockIndex, 0, movedApp);
SessionData.setBarPinnedApps(currentPinned);
}
property int _desktopEntriesUpdateTrigger: 0
property int _toplevelsUpdateTrigger: 0
property int _appIdSubstitutionsTrigger: 0
Connections {
target: CompositorService
function onToplevelsChanged() {
_toplevelsUpdateTrigger++;
updateModel();
}
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {
_desktopEntriesUpdateTrigger++;
}
}
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
_appIdSubstitutionsTrigger++;
updateModel();
}
function onRunningAppsCurrentWorkspaceChanged() {
updateModel();
}
}
Connections {
target: SessionData
function onBarPinnedAppsChanged() {
root.suppressShiftAnimation = true;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
updateModel();
Qt.callLater(() => {
root.suppressShiftAnimation = false;
});
}
}
property var dockItems: []
function isOnScreen(toplevel, screenName) {
if (!toplevel.screens)
return false;
for (let i = 0; i < toplevel.screens.length; i++) {
if (toplevel.screens[i]?.name === screenName)
return true;
}
return false;
}
function getCoreAppData(appId) {
if (typeof AppSearchService === "undefined")
return null;
const coreApps = AppSearchService.coreApps || [];
for (let i = 0; i < coreApps.length; i++) {
if (coreApps[i].builtInPluginId === appId)
return coreApps[i];
}
return null;
}
function getCoreAppDataByTitle(windowTitle) {
if (typeof AppSearchService === "undefined" || !windowTitle)
return null;
const coreApps = AppSearchService.coreApps || [];
for (let i = 0; i < coreApps.length; i++) {
if (coreApps[i].name === windowTitle)
return coreApps[i];
}
return null;
}
function updateModel() {
const items = [];
const pinnedApps = [...(SessionData.barPinnedApps || [])];
_toplevelsUpdateTrigger;
const allToplevels = CompositorService.sortedToplevels;
let sortedToplevels = allToplevels;
if (SettingsData.runningAppsCurrentWorkspace && parentScreen) {
sortedToplevels = CompositorService.filterCurrentWorkspace(allToplevels, parentScreen.name) || [];
}
const appGroups = new Map();
pinnedApps.forEach(rawAppId => {
const appId = Paths.moddedAppId(rawAppId);
const coreAppData = getCoreAppData(appId);
appGroups.set(appId, {
appId: appId,
isPinned: true,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
});
sortedToplevels.forEach((toplevel, index) => {
const rawAppId = toplevel.appId || "unknown";
let appId = Paths.moddedAppId(rawAppId);
let coreAppData = null;
if (rawAppId === "org.quickshell") {
coreAppData = getCoreAppDataByTitle(toplevel.title);
if (coreAppData) {
appId = coreAppData.builtInPluginId;
}
}
if (!appGroups.has(appId)) {
appGroups.set(appId, {
appId: appId,
isPinned: false,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
}
appGroups.get(appId).windows.push({
toplevel: toplevel,
index: index,
windowTitle: toplevel.title
});
});
const pinnedGroups = [];
const unpinnedGroups = [];
Array.from(appGroups.entries()).forEach(([appId, group]) => {
const firstWindow = group.windows.length > 0 ? group.windows[0] : null;
const item = {
uniqueKey: "grouped_" + appId,
type: "grouped",
appId: appId,
toplevel: firstWindow ? firstWindow.toplevel : null,
isPinned: group.isPinned,
isRunning: group.windows.length > 0,
windowCount: group.windows.length,
allWindows: group.windows,
isCoreApp: group.isCoreApp || false,
coreAppData: group.coreAppData || null
};
if (group.isPinned) {
pinnedGroups.push(item);
} else {
unpinnedGroups.push(item);
}
});
pinnedGroups.forEach(item => items.push(item));
if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
items.push({
uniqueKey: "separator_grouped",
type: "separator",
appId: "__SEPARATOR__",
toplevel: null,
isPinned: false,
isRunning: false
});
}
unpinnedGroups.forEach(item => items.push(item));
root.pinnedAppCount = pinnedGroups.length;
dockItems = items;
}
Component.onCompleted: updateModel()
readonly property int calculatedSize: {
const count = dockItems.length;
if (count === 0)
return 0;
if (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) {
return count * 24 + (count - 1) * Theme.spacingXS + horizontalPadding * 2;
} else {
return count * (24 + Theme.spacingXS + 120) + (count - 1) * Theme.spacingXS + horizontalPadding * 2;
}
}
readonly property real realCalculatedSize: {
let total = horizontalPadding * 2;
const compact = (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode);
for (let i = 0; i < dockItems.length; i++) {
const item = dockItems[i];
let itemSize = 0;
if (item.type === "separator") {
itemSize = 8;
} else {
itemSize = compact ? 24 : (24 + Theme.spacingXS + 120);
}
total += itemSize;
if (i < dockItems.length - 1)
total += Theme.spacingXS;
}
return total;
}
width: dockItems.length > 0 ? (isVertical ? barThickness : realCalculatedSize) : 0
height: dockItems.length > 0 ? (isVertical ? realCalculatedSize : barThickness) : 0
visible: dockItems.length > 0
Item {
id: visualBackground
width: root.isVertical ? root.widgetThickness : root.realCalculatedSize
height: root.isVertical ? root.realCalculatedSize : root.widgetThickness
anchors.centerIn: parent
clip: false
Rectangle {
id: outline
anchors.centerIn: parent
width: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.width + borderWidth * 2;
}
height: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.height + borderWidth * 2;
}
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: "transparent"
border.width: (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0
border.color: {
if (!(barConfig?.widgetOutlineEnabled ?? false)) {
return "transparent";
}
const colorOption = barConfig?.widgetOutlineColor || "primary";
const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
switch (colorOption) {
case "surfaceText":
return Theme.withAlpha(Theme.surfaceText, opacity);
case "secondary":
return Theme.withAlpha(Theme.secondary, opacity);
case "primary":
return Theme.withAlpha(Theme.primary, opacity);
default:
return Theme.withAlpha(Theme.primary, opacity);
}
}
}
Rectangle {
id: background
anchors.fill: parent
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: {
if (dockItems.length === 0)
return "transparent";
if ((barConfig?.noBackground ?? false))
return "transparent";
const baseColor = Theme.widgetBaseBackgroundColor;
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
if (Theme.widgetBackgroundHasAlpha) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
}
return Theme.withAlpha(baseColor, transparency);
}
}
}
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVertical ? columnLayout : rowLayout
}
Component {
id: rowLayout
Row {
spacing: Theme.spacingXS
Repeater {
id: repeater
model: ScriptModel {
values: root.dockItems
objectProp: "uniqueKey"
}
delegate: dockDelegate
}
}
}
Component {
id: columnLayout
Column {
spacing: Theme.spacingXS
Repeater {
model: ScriptModel {
values: root.dockItems
objectProp: "uniqueKey"
}
delegate: dockDelegate
}
}
}
Loader {
id: tooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Component {
id: dockDelegate
Item {
id: delegateItem
property bool isSeparator: modelData.type === "separator"
readonly property real visualSize: isSeparator ? 8 : ((widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? 24 : (24 + Theme.spacingXS + 120))
readonly property real visualWidth: root.isVertical ? root.barThickness : visualSize
readonly property real visualHeight: root.isVertical ? visualSize : root.barThickness
width: visualWidth
height: visualHeight
z: (dragHandler.dragging) ? 100 : 0
// --- Drag and Drop Shift Animation Logic ---
property real shiftOffset: {
if (root.draggedIndex < 0 || !modelData.isPinned || isSeparator)
return 0;
if (index === root.draggedIndex)
return 0;
const dragIdx = root.draggedIndex;
const dropIdx = root.dropTargetIndex;
const myIdx = index;
const shiftAmount = visualSize + Theme.spacingXS;
if (dropIdx < 0)
return 0;
if (dragIdx < dropIdx && myIdx > dragIdx && myIdx <= dropIdx)
return -shiftAmount;
if (dragIdx > dropIdx && myIdx >= dropIdx && myIdx < dragIdx)
return shiftAmount;
return 0;
}
transform: Translate {
x: root.isVertical ? 0 : delegateItem.shiftOffset
y: root.isVertical ? delegateItem.shiftOffset : 0
Behavior on x {
enabled: !root.suppressShiftAnimation
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
Behavior on y {
enabled: !root.suppressShiftAnimation
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
}
Rectangle {
visible: isSeparator
width: root.isVertical ? root.barThickness * 0.6 : 2
height: root.isVertical ? 2 : root.barThickness * 0.6
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
radius: 1
anchors.centerIn: parent
}
Item {
id: appItem
visible: !isSeparator
anchors.fill: parent
property bool isFocused: {
if (modelData.type === "grouped") {
return modelData.allWindows.some(w => w.toplevel && w.toplevel.activated);
}
return modelData.toplevel ? modelData.toplevel.activated : false;
}
property var appId: modelData.appId
property int windowCount: modelData.windowCount || (modelData.isRunning ? 1 : 0)
property string windowTitle: {
if (modelData.type === "grouped") {
const active = modelData.allWindows.find(w => w.toplevel && w.toplevel.activated);
if (active)
return active.windowTitle || "(Unnamed)";
if (modelData.allWindows.length > 0)
return modelData.allWindows[0].windowTitle || "(Unnamed)";
return "";
}
return modelData.toplevel ? (modelData.toplevel.title || "(Unnamed)") : "";
}
property string tooltipText: {
root._desktopEntriesUpdateTrigger;
const moddedId = Paths.moddedAppId(appId);
const desktopEntry = moddedId ? DesktopEntries.heuristicLookup(moddedId) : null;
const appName = appId ? Paths.getAppName(appId, desktopEntry) : "Unknown";
if (modelData.type === "grouped" && windowCount > 1) {
return appName + " (" + windowCount + " windows)";
}
return appName + (windowTitle ? " • " + windowTitle : "");
}
transform: Translate {
x: (dragHandler.dragging && !root.isVertical) ? dragHandler.dragAxisOffset : 0
y: (dragHandler.dragging && root.isVertical) ? dragHandler.dragAxisOffset : 0
}
Rectangle {
id: visualContent
width: root.isVertical ? 24 : delegateItem.visualSize
height: root.isVertical ? delegateItem.visualSize : 24
anchors.centerIn: parent
radius: Theme.cornerRadius
color: {
if (appItem.isFocused) {
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
}
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
}
border.width: dragHandler.dragging ? 2 : 0
border.color: Theme.primary
opacity: dragHandler.dragging ? 0.8 : 1.0
AppIconRenderer {
id: coreIcon
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined
iconSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
materialIconSizeAdjustment: 0
iconValue: {
if (!modelData || !modelData.isCoreApp || !modelData.coreAppData)
return "";
const appId = modelData.coreAppData.id || modelData.coreAppData.builtInPluginId;
if ((appId === "dms_settings" || appId === "dms_notepad" || appId === "dms_sysmon") && modelData.coreAppData.cornerIcon) {
return "material:" + modelData.coreAppData.cornerIcon;
}
return modelData.coreAppData.icon || "";
}
colorOverride: Theme.widgetIconColor
fallbackText: "?"
visible: iconValue !== ""
z: 2
}
IconImage {
id: iconImg
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
source: {
root._desktopEntriesUpdateTrigger;
root._appIdSubstitutionsTrigger;
if (!appItem.appId)
return "";
if (modelData.isCoreApp)
return ""; // Explicitly skip if core app to avoid flickering or wrong look ups
const moddedId = Paths.moddedAppId(appItem.appId);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
return Paths.getAppIcon(appItem.appId, desktopEntry);
}
smooth: true
mipmap: true
asynchronous: true
visible: status === Image.Ready && !coreIcon.visible
layer.enabled: appItem.appId === "org.quickshell"
layer.smooth: true
layer.mipmap: true
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.primary
}
z: 2
}
DankIcon {
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
name: "sports_esports"
color: Theme.widgetTextColor
visible: !iconImg.visible && !coreIcon.visible && Paths.isSteamApp(appItem.appId)
}
Text {
anchors.centerIn: parent
visible: !iconImg.visible && !coreIcon.visible && !Paths.isSteamApp(appItem.appId)
text: {
root._desktopEntriesUpdateTrigger;
if (!appItem.appId)
return "?";
const moddedId = Paths.moddedAppId(appItem.appId);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
const appName = Paths.getAppName(appItem.appId, 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: modelData.type === "grouped" && appItem.windowCount > 1
z: 10
StyledText {
anchors.centerIn: parent
text: appItem.windowCount > 9 ? "9+" : appItem.windowCount
font.pixelSize: 9
color: Theme.surface
}
}
StyledText {
visible: !root.isVertical && !(widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: iconImg.right
anchors.leftMargin: Theme.spacingXS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: appItem.windowTitle || appItem.appId
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
color: Theme.widgetTextColor
elide: Text.ElideRight
maximumLineCount: 1
}
Rectangle {
visible: modelData.isRunning
width: root.isVertical ? 2 : 20
height: root.isVertical ? 20 : 2
radius: 1
color: appItem.isFocused ? Theme.primary : Theme.surfaceText
opacity: appItem.isFocused ? 1 : 0.5
anchors.bottom: root.isVertical ? undefined : parent.bottom
anchors.right: root.isVertical ? parent.right : undefined
anchors.horizontalCenter: root.isVertical ? undefined : parent.horizontalCenter
anchors.verticalCenter: root.isVertical ? parent.verticalCenter : undefined
anchors.margins: 0
z: 5
}
}
}
// Handler for Drag Logic
Item {
id: dragHandler
anchors.fill: parent
property bool dragging: false
property point dragStartPos: Qt.point(0, 0)
property real dragAxisOffset: 0
property bool longPressing: false
Timer {
id: longPressTimer
interval: 500
repeat: false
onTriggered: {
if (modelData.isPinned) {
dragHandler.longPressing = true;
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: mouse => {
if (mouse.button === Qt.LeftButton && modelData.isPinned) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start();
}
}
onReleased: mouse => {
longPressTimer.stop();
const wasDragging = dragHandler.dragging;
const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;
if (didReorder) {
root.movePinnedApp(root.draggedIndex, root.dropTargetIndex);
}
dragHandler.longPressing = false;
dragHandler.dragging = false;
dragHandler.dragAxisOffset = 0;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
if (wasDragging || mouse.button !== Qt.LeftButton)
return;
if (wasDragging || mouse.button !== Qt.LeftButton)
return;
if (modelData.type === "grouped") {
if (modelData.windowCount === 0) {
if (modelData.isCoreApp && modelData.coreAppData) {
AppSearchService.executeCoreApp(modelData.coreAppData);
} else {
const moddedId = Paths.moddedAppId(modelData.appId);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
if (desktopEntry)
SessionService.launchDesktopEntry(desktopEntry);
}
} else if (modelData.windowCount === 1) {
if (modelData.allWindows[0].toplevel)
modelData.allWindows[0].toplevel.activate();
} else {
let currentIndex = -1;
for (var i = 0; i < modelData.allWindows.length; i++) {
if (modelData.allWindows[i].toplevel.activated) {
currentIndex = i;
break;
}
}
const nextIndex = (currentIndex + 1) % modelData.allWindows.length;
modelData.allWindows[nextIndex].toplevel.activate();
}
}
}
onPositionChanged: mouse => {
if (dragHandler.longPressing && !dragHandler.dragging) {
const distance = Math.sqrt(Math.pow(mouse.x - dragHandler.dragStartPos.x, 2) + Math.pow(mouse.y - dragHandler.dragStartPos.y, 2));
if (distance > 5) {
dragHandler.dragging = true;
root.draggedIndex = index;
root.dropTargetIndex = index;
}
}
if (!dragHandler.dragging)
return;
const axisOffset = root.isVertical ? (mouse.y - dragHandler.dragStartPos.y) : (mouse.x - dragHandler.dragStartPos.x);
dragHandler.dragAxisOffset = axisOffset;
const itemSize = (root.isVertical ? delegateItem.height : delegateItem.width) + Theme.spacingXS;
const slotOffset = Math.round(axisOffset / itemSize);
const newTargetIndex = Math.max(0, Math.min(root.pinnedAppCount - 1, index + slotOffset));
if (newTargetIndex !== root.dropTargetIndex) {
root.dropTargetIndex = newTargetIndex;
}
}
onEntered: {
root.hoveredItem = delegateItem;
if (isSeparator)
return;
tooltipLoader.active = true;
if (tooltipLoader.item) {
if (root.isVertical) {
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" ? (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS) : (root.parentScreen.width - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS);
const isLeft = root.axis?.edge === "left";
const adjustedY = relativeY + root.minTooltipY;
const finalX = screenX + tooltipX;
tooltipLoader.item.show(appItem.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 - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS - 35) : (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS);
tooltipLoader.item.show(appItem.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;
}
}
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (tooltipLoader.item) {
tooltipLoader.item.hide();
}
tooltipLoader.active = false;
contextMenuLoader.active = true;
if (contextMenuLoader.item) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const isBarVertical = root.axis?.isVertical ?? false;
const barEdge = root.axis?.edge ?? "top";
let x = globalPos.x;
let y = globalPos.y;
if (barEdge === "bottom") {
y = (root.parentScreen ? root.parentScreen.height : Screen.height) - root.effectiveBarThickness;
} else if (barEdge === "top") {
y = root.effectiveBarThickness;
} else if (barEdge === "left") {
x = root.effectiveBarThickness;
} else if (barEdge === "right") {
x = (root.parentScreen ? root.parentScreen.width : Screen.width) - root.effectiveBarThickness;
}
const shouldHidePin = modelData.appId === "org.quickshell";
const moddedId = Paths.moddedAppId(modelData.appId);
const desktopEntry = moddedId ? DesktopEntries.heuristicLookup(moddedId) : null;
contextMenuLoader.item.showAt(x, y, isBarVertical, barEdge, modelData, shouldHidePin, desktopEntry, root.parentScreen);
}
}
}
}
}
}
}
Loader {
id: contextMenuLoader
active: false
source: "AppsDockContextMenu.qml"
}
}