mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-27 15:02:50 -05:00
806 lines
36 KiB
QML
806 lines
36 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Hyprland
|
|
import qs.Common
|
|
import qs.Services
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property var contextMenu: null
|
|
property bool requestDockShow: false
|
|
property int pinnedAppCount: 0
|
|
property bool groupByApp: false
|
|
property bool isVertical: false
|
|
property var dockScreen: null
|
|
property real iconSize: 40
|
|
property int draggedIndex: -1
|
|
property int dropTargetIndex: -1
|
|
property bool suppressShiftAnimation: false
|
|
property int maxVisibleApps: SettingsData.dockMaxVisibleApps
|
|
property int maxVisibleRunningApps: SettingsData.dockMaxVisibleRunningApps
|
|
property bool overflowExpanded: false
|
|
property int overflowItemCount: 0
|
|
property bool draggingPinned: false
|
|
property real maxAvailableLength: 0
|
|
|
|
// Calculate if scrolling is needed (use childrenRect for accurate measurement)
|
|
readonly property bool canScroll: {
|
|
if (!root.isVertical) {
|
|
return layoutFlow.childrenRect.width > availableScreenWidth;
|
|
} else {
|
|
return layoutFlow.childrenRect.height > availableScreenHeight;
|
|
}
|
|
}
|
|
|
|
// Available space calculations - 10% padding for nice margins when scrolling
|
|
readonly property real availableScreenWidth: {
|
|
if (!root.isVertical && maxAvailableLength > 0) {
|
|
return maxAvailableLength * 0.9; // 10% padding on sides
|
|
}
|
|
if (dockScreen && dockScreen.geometry && dockScreen.geometry.width) {
|
|
return dockScreen.geometry.width - 200;
|
|
}
|
|
return 1720;
|
|
}
|
|
|
|
readonly property real availableScreenHeight: {
|
|
if (root.isVertical && maxAvailableLength > 0) {
|
|
return maxAvailableLength * 0.9; // 10% padding
|
|
}
|
|
if (dockScreen && dockScreen.geometry && dockScreen.geometry.height) {
|
|
return dockScreen.geometry.height - 100;
|
|
}
|
|
return 980;
|
|
}
|
|
|
|
clip: false
|
|
implicitWidth: isVertical ? appLayout.height : appLayout.width
|
|
implicitHeight: isVertical ? appLayout.width : appLayout.height
|
|
|
|
function dockIndexToPinnedIndex(dockIndex) {
|
|
if (!SettingsData.dockLauncherEnabled) {
|
|
return dockIndex;
|
|
}
|
|
|
|
const launcherPos = SessionData.dockLauncherPosition;
|
|
if (dockIndex < launcherPos) {
|
|
return dockIndex;
|
|
} else {
|
|
return dockIndex - 1;
|
|
}
|
|
}
|
|
|
|
function movePinnedApp(fromDockIndex, toDockIndex) {
|
|
console.warn("movePinnedApp: dock", fromDockIndex, "->", toDockIndex);
|
|
|
|
const fromPinnedIndex = dockIndexToPinnedIndex(fromDockIndex);
|
|
const toPinnedIndex = dockIndexToPinnedIndex(toDockIndex);
|
|
|
|
console.warn(" Converted to pinned indices:", fromPinnedIndex, "->", toPinnedIndex);
|
|
|
|
if (fromPinnedIndex === toPinnedIndex) {
|
|
console.warn(" Same pinned index, skipping");
|
|
return;
|
|
}
|
|
|
|
const currentPinned = [...(SessionData.pinnedApps || [])];
|
|
console.warn(" Current pinned count:", currentPinned.length);
|
|
|
|
if (fromPinnedIndex < 0 || fromPinnedIndex >= currentPinned.length || toPinnedIndex < 0 || toPinnedIndex >= currentPinned.length) {
|
|
console.warn(" Invalid pinned indices! from:", fromPinnedIndex, "to:", toPinnedIndex, "length:", currentPinned.length);
|
|
return;
|
|
}
|
|
|
|
const movedApp = currentPinned.splice(fromPinnedIndex, 1)[0];
|
|
console.warn(" Moving app:", movedApp);
|
|
currentPinned.splice(toPinnedIndex, 0, movedApp);
|
|
|
|
SessionData.setPinnedApps(currentPinned);
|
|
console.warn(" Move complete");
|
|
}
|
|
|
|
function movePinnedAppByPinnedIndex(fromPinnedIndex, toPinnedIndex) {
|
|
console.warn("movePinnedAppByPinnedIndex:", fromPinnedIndex, "->", toPinnedIndex);
|
|
|
|
if (fromPinnedIndex === toPinnedIndex) {
|
|
console.warn(" Same index, skipping");
|
|
return;
|
|
}
|
|
|
|
const currentPinned = [...(SessionData.pinnedApps || [])];
|
|
console.warn(" Current pinned count:", currentPinned.length);
|
|
|
|
if (fromPinnedIndex < 0 || fromPinnedIndex >= currentPinned.length || toPinnedIndex < 0 || toPinnedIndex >= currentPinned.length) {
|
|
console.warn(" Invalid indices! from:", fromPinnedIndex, "to:", toPinnedIndex, "length:", currentPinned.length);
|
|
return;
|
|
}
|
|
|
|
const movedApp = currentPinned.splice(fromPinnedIndex, 1)[0];
|
|
console.warn(" Moving app:", movedApp);
|
|
currentPinned.splice(toPinnedIndex, 0, movedApp);
|
|
|
|
SessionData.setPinnedApps(currentPinned);
|
|
console.warn(" Move complete");
|
|
}
|
|
|
|
function pinnedIndexForDockIndex(dockIndex) {
|
|
const items = repeater.dockItems || [];
|
|
let pinnedIndex = 0;
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i];
|
|
|
|
if (item.type === "separator" || item.type === "overflow-toggle" || item.type === "launcher") {
|
|
continue;
|
|
}
|
|
if (!(item.type === "pinned" || item.type === "grouped") || !item.isPinned) {
|
|
continue;
|
|
}
|
|
|
|
if (i === dockIndex) {
|
|
return pinnedIndex;
|
|
}
|
|
|
|
pinnedIndex++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
Item {
|
|
id: appLayout
|
|
width: layoutFlickable.width
|
|
height: layoutFlickable.height
|
|
|
|
// Clip when scrolling to prevent apps from overlapping dock bounds
|
|
clip: root.canScroll
|
|
|
|
// Anchoring for proper dock positioning
|
|
anchors.horizontalCenter: root.isVertical ? undefined : parent.horizontalCenter
|
|
anchors.verticalCenter: root.isVertical ? parent.verticalCenter : undefined
|
|
anchors.left: root.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined
|
|
anchors.right: root.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined
|
|
anchors.top: root.isVertical ? undefined : parent.top
|
|
|
|
Flickable {
|
|
id: layoutFlickable
|
|
width: {
|
|
if (!root.isVertical) {
|
|
const contentWidth = layoutFlow.childrenRect.width;
|
|
return Math.min(contentWidth, root.availableScreenWidth);
|
|
}
|
|
return layoutFlow.childrenRect.width;
|
|
}
|
|
height: {
|
|
if (root.isVertical) {
|
|
const contentHeight = layoutFlow.childrenRect.height;
|
|
return Math.min(contentHeight, root.availableScreenHeight);
|
|
}
|
|
return layoutFlow.childrenRect.height;
|
|
}
|
|
contentWidth: layoutFlow.childrenRect.width
|
|
contentHeight: layoutFlow.childrenRect.height
|
|
// Don't clip - let indicators extend beyond. Parent appLayout clips for scrolling.
|
|
clip: false
|
|
flickableDirection: root.isVertical ? Flickable.VerticalFlick : Flickable.HorizontalFlick
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
|
|
// Smooth scrolling
|
|
maximumFlickVelocity: 2500
|
|
flickDeceleration: 1500
|
|
|
|
Flow {
|
|
id: layoutFlow
|
|
flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
|
spacing: Math.min(8, Math.max(4, root.iconSize * 0.08))
|
|
|
|
Repeater {
|
|
id: repeater
|
|
|
|
property var dockItems: []
|
|
|
|
model: ScriptModel {
|
|
values: repeater.dockItems
|
|
objectProp: "uniqueKey"
|
|
}
|
|
|
|
Component.onCompleted: updateModel()
|
|
|
|
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++) {
|
|
const app = coreApps[i];
|
|
if (app.builtInPluginId === appId) {
|
|
return app;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getCoreAppDataByTitle(windowTitle) {
|
|
if (typeof AppSearchService === "undefined" || !windowTitle)
|
|
return null;
|
|
|
|
const coreApps = AppSearchService.coreApps || [];
|
|
for (let i = 0; i < coreApps.length; i++) {
|
|
const app = coreApps[i];
|
|
if (app.name === windowTitle) {
|
|
return app;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function insertLauncher(targetArray) {
|
|
if (!SettingsData.dockLauncherEnabled)
|
|
return;
|
|
|
|
const launcherItem = {
|
|
uniqueKey: "launcher_button",
|
|
type: "launcher",
|
|
appId: "__LAUNCHER__",
|
|
toplevel: null,
|
|
isPinned: true,
|
|
isRunning: false
|
|
};
|
|
|
|
const pos = Math.max(0, Math.min(SessionData.dockLauncherPosition, targetArray.length));
|
|
targetArray.splice(pos, 0, launcherItem);
|
|
}
|
|
|
|
function updateModel() {
|
|
const items = [];
|
|
const pinnedApps = [...(SessionData.pinnedApps || [])];
|
|
const allToplevels = CompositorService.sortedToplevels;
|
|
const sortedToplevels = (SettingsData.dockIsolateDisplays && root.dockScreen) ? allToplevels.filter(t => isOnScreen(t, root.dockScreen.name)) : allToplevels;
|
|
const runningAppIds = new Set();
|
|
const windowItems = [];
|
|
|
|
if (!root.groupByApp) {
|
|
sortedToplevels.forEach((toplevel, index) => {
|
|
let uniqueKey = "window_" + index;
|
|
if (CompositorService.isHyprland && Hyprland.toplevels) {
|
|
const hyprlandToplevels = Array.from(Hyprland.toplevels.values);
|
|
for (let i = 0; i < hyprlandToplevels.length; i++) {
|
|
if (hyprlandToplevels[i].wayland === toplevel) {
|
|
uniqueKey = "window_" + hyprlandToplevels[i].address;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const rawAppId = toplevel.appId || "unknown";
|
|
const moddedAppId = Paths.moddedAppId(rawAppId);
|
|
|
|
let coreAppData = null;
|
|
let isCoreApp = false;
|
|
if (rawAppId === "org.quickshell") {
|
|
coreAppData = getCoreAppDataByTitle(toplevel.title);
|
|
if (coreAppData) {
|
|
isCoreApp = true;
|
|
}
|
|
}
|
|
|
|
const finalAppId = isCoreApp ? coreAppData.builtInPluginId : moddedAppId;
|
|
|
|
windowItems.push({
|
|
uniqueKey: uniqueKey,
|
|
type: "window",
|
|
appId: finalAppId,
|
|
toplevel: toplevel,
|
|
isPinned: false,
|
|
isRunning: true,
|
|
isCoreApp: isCoreApp,
|
|
coreAppData: coreAppData
|
|
});
|
|
|
|
runningAppIds.add(finalAppId);
|
|
});
|
|
}
|
|
|
|
if (root.groupByApp) {
|
|
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
|
|
});
|
|
});
|
|
|
|
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));
|
|
|
|
insertLauncher(items);
|
|
|
|
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 + (SettingsData.dockLauncherEnabled ? 1 : 0);
|
|
} else {
|
|
const remainingWindowItems = windowItems.slice();
|
|
|
|
pinnedApps.forEach(rawAppId => {
|
|
const appId = Paths.moddedAppId(rawAppId);
|
|
const coreAppData = getCoreAppData(appId);
|
|
const matchIndex = remainingWindowItems.findIndex(item => item.appId === appId);
|
|
|
|
if (matchIndex !== -1) {
|
|
const windowItem = remainingWindowItems.splice(matchIndex, 1)[0];
|
|
windowItem.isPinned = true;
|
|
if (!windowItem.isCoreApp && coreAppData) {
|
|
windowItem.isCoreApp = true;
|
|
windowItem.coreAppData = coreAppData;
|
|
}
|
|
items.push(windowItem);
|
|
} else {
|
|
items.push({
|
|
uniqueKey: "pinned_" + appId,
|
|
type: "pinned",
|
|
appId: appId,
|
|
toplevel: null,
|
|
isPinned: true,
|
|
isRunning: runningAppIds.has(appId),
|
|
isCoreApp: coreAppData !== null,
|
|
coreAppData: coreAppData
|
|
});
|
|
}
|
|
});
|
|
|
|
root.pinnedAppCount = pinnedApps.length + (SettingsData.dockLauncherEnabled ? 1 : 0);
|
|
|
|
insertLauncher(items);
|
|
|
|
if (pinnedApps.length > 0 && remainingWindowItems.length > 0) {
|
|
items.push({
|
|
uniqueKey: "separator_ungrouped",
|
|
type: "separator",
|
|
appId: "__SEPARATOR__",
|
|
toplevel: null,
|
|
isPinned: false,
|
|
isRunning: false
|
|
});
|
|
}
|
|
remainingWindowItems.forEach(item => items.push(item));
|
|
}
|
|
|
|
// Overflow logic
|
|
const countableItems = items.filter(item => (item.type === "pinned" || item.type === "grouped" || item.type === "window") && item.isPinned && item.appId !== "__LAUNCHER__");
|
|
|
|
const hideRunningItems = root.maxVisibleRunningApps === 0;
|
|
let runningItems = [];
|
|
if (!hideRunningItems) {
|
|
if (root.groupByApp) {
|
|
runningItems = items.filter(item => item.type === "grouped" && item.isRunning && !item.isPinned);
|
|
} else {
|
|
runningItems = items.filter(item => item.type === "window" && item.isRunning && !item.isPinned);
|
|
}
|
|
}
|
|
|
|
let uniqueRunningItems = runningItems;
|
|
let duplicateRunningItems = [];
|
|
|
|
if (!root.groupByApp && runningItems.length > 0) {
|
|
const pinnedAppIds = new Set(items.filter(item => item.isPinned).map(item => item.appId));
|
|
const seenRunningIds = new Set();
|
|
uniqueRunningItems = [];
|
|
duplicateRunningItems = [];
|
|
|
|
for (let i = 0; i < runningItems.length; i++) {
|
|
const item = runningItems[i];
|
|
if (pinnedAppIds.has(item.appId) || seenRunningIds.has(item.appId)) {
|
|
duplicateRunningItems.push(item);
|
|
continue;
|
|
}
|
|
seenRunningIds.add(item.appId);
|
|
uniqueRunningItems.push(item);
|
|
}
|
|
}
|
|
|
|
const pinnedOverflowNeeded = root.maxVisibleApps > 0 && countableItems.length > root.maxVisibleApps;
|
|
const runningOverflowNeeded = !hideRunningItems && root.maxVisibleRunningApps > 0 && uniqueRunningItems.length > root.maxVisibleRunningApps;
|
|
const overflowNeeded = pinnedOverflowNeeded || runningOverflowNeeded;
|
|
|
|
if (overflowNeeded) {
|
|
const visibleCountable = pinnedOverflowNeeded ? countableItems.slice(0, root.maxVisibleApps) : countableItems.slice(0, countableItems.length);
|
|
const overflowCountable = pinnedOverflowNeeded ? countableItems.slice(root.maxVisibleApps) : [];
|
|
|
|
const visibleRunning = !hideRunningItems ? uniqueRunningItems.slice(0, root.maxVisibleRunningApps) : [];
|
|
const overflowRunning = !hideRunningItems ? uniqueRunningItems.slice(root.maxVisibleRunningApps) : [];
|
|
const combinedOverflowRunning = duplicateRunningItems.concat(overflowRunning);
|
|
|
|
const finalItems = [];
|
|
|
|
// Add items in order, preserving launcher position
|
|
let visibleIndex = 0;
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i];
|
|
|
|
if (item.type === "launcher") {
|
|
finalItems.push(item);
|
|
} else if (visibleIndex < visibleCountable.length && item.uniqueKey === visibleCountable[visibleIndex].uniqueKey) {
|
|
finalItems.push(item);
|
|
visibleIndex++;
|
|
}
|
|
}
|
|
|
|
const totalOverflowCount = overflowCountable.length + combinedOverflowRunning.length;
|
|
const hasSeparator = items.some(item => item.type === "separator");
|
|
const shouldShowSeparator = hasSeparator && (visibleRunning.length > 0 || totalOverflowCount > 0);
|
|
|
|
if (shouldShowSeparator) {
|
|
finalItems.push({
|
|
uniqueKey: "separator",
|
|
type: "separator",
|
|
appId: "__SEPARATOR__",
|
|
toplevel: null,
|
|
isPinned: false,
|
|
isRunning: false
|
|
});
|
|
}
|
|
|
|
for (let i = 0; i < visibleRunning.length; i++) {
|
|
finalItems.push(visibleRunning[i]);
|
|
}
|
|
|
|
if (totalOverflowCount > 0) {
|
|
finalItems.push({
|
|
uniqueKey: "overflow_toggle",
|
|
type: "overflow-toggle",
|
|
appId: "__OVERFLOW_TOGGLE__",
|
|
toplevel: null,
|
|
isPinned: false,
|
|
isRunning: false,
|
|
overflowCount: totalOverflowCount
|
|
});
|
|
}
|
|
|
|
for (let i = 0; i < overflowCountable.length; i++) {
|
|
const item = overflowCountable[i];
|
|
const overflowItem = {
|
|
uniqueKey: item.uniqueKey,
|
|
type: item.type,
|
|
appId: item.appId,
|
|
toplevel: item.toplevel,
|
|
isPinned: item.isPinned,
|
|
isRunning: item.isRunning,
|
|
windowCount: item.windowCount,
|
|
allWindows: item.allWindows,
|
|
isCoreApp: item.isCoreApp,
|
|
coreAppData: item.coreAppData,
|
|
isInOverflow: true
|
|
};
|
|
finalItems.push(overflowItem);
|
|
}
|
|
|
|
for (let i = 0; i < combinedOverflowRunning.length; i++) {
|
|
const item = combinedOverflowRunning[i];
|
|
const overflowItem = {
|
|
uniqueKey: item.uniqueKey,
|
|
type: item.type,
|
|
appId: item.appId,
|
|
toplevel: item.toplevel,
|
|
isPinned: false,
|
|
isRunning: true,
|
|
windowCount: item.windowCount,
|
|
allWindows: item.allWindows,
|
|
isCoreApp: item.isCoreApp,
|
|
coreAppData: item.coreAppData,
|
|
isInOverflow: true
|
|
};
|
|
finalItems.push(overflowItem);
|
|
}
|
|
|
|
root.overflowItemCount = totalOverflowCount;
|
|
root.pinnedAppCount = countableItems.length;
|
|
dockItems = finalItems;
|
|
} else {
|
|
root.overflowItemCount = 0;
|
|
root.pinnedAppCount = countableItems.length;
|
|
|
|
if (hideRunningItems) {
|
|
const filteredItems = items.filter(item => !((item.type === "window" || item.type === "grouped") && item.isRunning && !item.isPinned) && item.type !== "separator");
|
|
dockItems = filteredItems;
|
|
} else {
|
|
dockItems = items;
|
|
}
|
|
}
|
|
}
|
|
|
|
delegate: Item {
|
|
id: delegateItem
|
|
property var dockButton: itemData.type === "launcher" ? launcherButton : button
|
|
property var itemData: modelData
|
|
readonly property bool isOverflowToggle: itemData.type === "overflow-toggle"
|
|
readonly property bool isInOverflow: itemData.isInOverflow === true
|
|
clip: false
|
|
z: (itemData.type === "launcher" ? launcherButton.dragging : button.dragging) ? 100 : 0
|
|
|
|
// Overflow items: hidden when collapsed, visible when expanded
|
|
visible: !isInOverflow || root.overflowExpanded
|
|
|
|
// Overflow items collapse to 0 size when hidden
|
|
width: (isInOverflow && !root.overflowExpanded) ? 0 :
|
|
(itemData.type === "separator" ? (root.isVertical ? root.iconSize : 8) :
|
|
(root.isVertical ? root.iconSize : root.iconSize * 1.2))
|
|
height: (isInOverflow && !root.overflowExpanded) ? 0 :
|
|
(itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) :
|
|
(root.isVertical ? root.iconSize * 1.2 : root.iconSize))
|
|
|
|
opacity: (isInOverflow && !root.overflowExpanded) ? 0 : 1
|
|
scale: (isInOverflow && !root.overflowExpanded) ? 0.8 : 1
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
|
|
// No Behavior on width/height - they must change immediately
|
|
// so Flow layout properly excludes hidden overflow items
|
|
// Visual smoothness comes from opacity and scale animations
|
|
|
|
property real shiftOffset: {
|
|
if (root.draggedIndex < 0 || !itemData.isPinned || itemData.type === "separator")
|
|
return 0;
|
|
const myIdx = root.draggingPinned ? root.pinnedIndexForDockIndex(model.index) : model.index;
|
|
if (myIdx < 0)
|
|
return 0;
|
|
if (myIdx === root.draggedIndex)
|
|
return 0;
|
|
|
|
const dragIdx = root.draggedIndex;
|
|
const dropIdx = root.dropTargetIndex;
|
|
const shiftAmount = root.iconSize * 1.2 + layoutFlow.spacing;
|
|
|
|
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: itemData.type === "separator"
|
|
width: root.isVertical ? root.iconSize * 0.5 : 2
|
|
height: root.isVertical ? 2 : root.iconSize * 0.5
|
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
|
radius: 1
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
DockOverflowButton {
|
|
id: overflowButton
|
|
visible: isOverflowToggle
|
|
anchors.centerIn: parent
|
|
width: delegateItem.width
|
|
height: delegateItem.height
|
|
actualIconSize: root.iconSize
|
|
overflowCount: itemData.overflowCount || 0
|
|
overflowExpanded: root.overflowExpanded
|
|
isVertical: root.isVertical
|
|
onClicked: root.overflowExpanded = !root.overflowExpanded
|
|
}
|
|
|
|
DockLauncherButton {
|
|
id: launcherButton
|
|
visible: itemData.type === "launcher"
|
|
anchors.centerIn: parent
|
|
|
|
width: delegateItem.width
|
|
height: delegateItem.height
|
|
actualIconSize: root.iconSize
|
|
|
|
dockApps: root
|
|
index: model.index
|
|
}
|
|
|
|
DockAppButton {
|
|
id: button
|
|
visible: !isOverflowToggle && itemData.type !== "separator" && itemData.type !== "launcher"
|
|
anchors.centerIn: parent
|
|
|
|
width: delegateItem.width
|
|
height: delegateItem.height
|
|
actualIconSize: root.iconSize
|
|
|
|
appData: itemData
|
|
contextMenu: root.contextMenu
|
|
dockApps: root
|
|
index: model.index
|
|
parentDockScreen: root.dockScreen
|
|
|
|
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
|
windowTitle: {
|
|
const title = itemData?.toplevel?.title || "(Unnamed)";
|
|
return title.length > 50 ? title.substring(0, 47) + "..." : title;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: CompositorService
|
|
function onToplevelsChanged() {
|
|
repeater.updateModel();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: SessionData
|
|
function onPinnedAppsChanged() {
|
|
root.suppressShiftAnimation = true;
|
|
root.draggedIndex = -1;
|
|
root.dropTargetIndex = -1;
|
|
root.draggingPinned = false;
|
|
repeater.updateModel();
|
|
Qt.callLater(() => {
|
|
root.suppressShiftAnimation = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
onGroupByAppChanged: repeater.updateModel()
|
|
// Don't rebuild model on overflow toggle - delegates react to overflowExpanded via bindings
|
|
// Model structure doesn't change, only delegate visibility changes
|
|
|
|
Connections {
|
|
target: SettingsData
|
|
function onDockIsolateDisplaysChanged() {
|
|
repeater.updateModel();
|
|
}
|
|
function onDockLauncherEnabledChanged() {
|
|
root.suppressShiftAnimation = true;
|
|
root.draggedIndex = -1;
|
|
root.dropTargetIndex = -1;
|
|
repeater.updateModel();
|
|
Qt.callLater(() => {
|
|
root.suppressShiftAnimation = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: SessionData
|
|
function onDockLauncherPositionChanged() {
|
|
root.suppressShiftAnimation = true;
|
|
root.draggedIndex = -1;
|
|
root.dropTargetIndex = -1;
|
|
repeater.updateModel();
|
|
Qt.callLater(() => {
|
|
root.suppressShiftAnimation = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: SettingsData
|
|
function onDockMaxVisibleAppsChanged() {
|
|
repeater.updateModel();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: SettingsData
|
|
function onDockMaxVisibleRunningAppsChanged() {
|
|
repeater.updateModel();
|
|
}
|
|
}
|
|
}
|