summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockApps.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockApps.qml')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockApps.qml646
1 files changed, 646 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockApps.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockApps.qml
new file mode 100644
index 0000000..dbdc517
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Dock/DockApps.qml
@@ -0,0 +1,646 @@
+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
+
+ readonly property real baseImplicitWidth: isVertical ? baseAppHeight : baseAppWidth
+ readonly property real baseImplicitHeight: isVertical ? baseAppWidth : baseAppHeight
+ readonly property real baseAppWidth: {
+ let count = 0;
+ const items = repeater.dockItems;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ if (item.isInOverflow)
+ continue;
+ if (item.type === "separator") {
+ count += 8 / (iconSize * 1.2);
+ } else {
+ count += 1;
+ }
+ }
+ return count * (iconSize * 1.2) + Math.max(0, count - 1) * layoutFlow.spacing;
+ }
+ readonly property real baseAppHeight: iconSize
+
+ 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;
+ return dockIndex < launcherPos ? dockIndex : dockIndex - 1;
+ }
+
+ function movePinnedApp(fromDockIndex, toDockIndex) {
+ const fromPinnedIndex = dockIndexToPinnedIndex(fromDockIndex);
+ const toPinnedIndex = dockIndexToPinnedIndex(toDockIndex);
+
+ if (fromPinnedIndex === toPinnedIndex)
+ return;
+
+ const currentPinned = [...(SessionData.pinnedApps || [])];
+ if (fromPinnedIndex < 0 || fromPinnedIndex >= currentPinned.length || toPinnedIndex < 0 || toPinnedIndex >= currentPinned.length)
+ return;
+
+ const movedApp = currentPinned.splice(fromPinnedIndex, 1)[0];
+ currentPinned.splice(toPinnedIndex, 0, movedApp);
+ SessionData.setPinnedApps(currentPinned);
+ }
+
+ Item {
+ id: appLayout
+ width: layoutFlow.width
+ height: layoutFlow.height
+
+ Behavior on width {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Easing.OutCubic
+ }
+ }
+
+ Behavior on height {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Easing.OutCubic
+ }
+ }
+ 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
+
+ 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++) {
+ 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 buildBaseItems() {
+ 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;
+
+ if (root.groupByApp) {
+ return buildGroupedItems(pinnedApps, sortedToplevels);
+ }
+ return buildUngroupedItems(pinnedApps, sortedToplevels);
+ }
+
+ function buildGroupedItems(pinnedApps, sortedToplevels) {
+ const items = [];
+ 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" || rawAppId === "com.danklinux.dms") {
+ 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 = [];
+
+ appGroups.forEach((group, appId) => {
+ 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,
+ isInOverflow: false
+ };
+ (group.isPinned ? pinnedGroups : unpinnedGroups).push(item);
+ });
+
+ pinnedGroups.forEach(item => items.push(item));
+ insertLauncher(items);
+
+ if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
+ items.push(createSeparator("separator_grouped"));
+ }
+ unpinnedGroups.forEach(item => items.push(item));
+
+ root.pinnedAppCount = pinnedGroups.length + (SettingsData.dockLauncherEnabled ? 1 : 0);
+ return {
+ items,
+ pinnedCount: pinnedGroups.length,
+ runningCount: unpinnedGroups.length
+ };
+ }
+
+ function buildUngroupedItems(pinnedApps, sortedToplevels) {
+ const items = [];
+ const runningAppIds = new Set();
+ const windowItems = [];
+
+ 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" || rawAppId === "com.danklinux.dms") {
+ 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,
+ isInOverflow: false
+ });
+ runningAppIds.add(finalAppId);
+ });
+
+ 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;
+ windowItem.uniqueKey = "pinned_" + appId;
+ 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,
+ isInOverflow: false
+ });
+ }
+ });
+
+ root.pinnedAppCount = pinnedApps.length + (SettingsData.dockLauncherEnabled ? 1 : 0);
+ insertLauncher(items);
+
+ if (pinnedApps.length > 0 && remainingWindowItems.length > 0) {
+ items.push(createSeparator("separator_ungrouped"));
+ }
+ remainingWindowItems.forEach(item => items.push(item));
+
+ return {
+ items,
+ pinnedCount: pinnedApps.length,
+ runningCount: remainingWindowItems.length
+ };
+ }
+
+ 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 createSeparator(key) {
+ return {
+ uniqueKey: key,
+ type: "separator",
+ appId: "__SEPARATOR__",
+ toplevel: null,
+ isPinned: false,
+ isRunning: false
+ };
+ }
+
+ function markAsOverflow(item) {
+ return {
+ 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
+ };
+ }
+
+ function markAsVisible(item) {
+ return {
+ 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: false
+ };
+ }
+
+ function applyOverflow(baseResult) {
+ const {
+ items
+ } = baseResult;
+ const maxPinned = root.maxVisibleApps;
+ const maxRunning = root.maxVisibleRunningApps;
+
+ const pinnedItems = items.filter(i => (i.type === "pinned" || i.type === "grouped" || i.type === "window") && i.isPinned && i.appId !== "__LAUNCHER__");
+ const runningItems = items.filter(i => (i.type === "window" || i.type === "grouped") && i.isRunning && !i.isPinned);
+
+ const pinnedOverflow = maxPinned > 0 && pinnedItems.length > maxPinned;
+ const runningOverflow = maxRunning > 0 && runningItems.length > maxRunning;
+
+ if (!pinnedOverflow && !runningOverflow) {
+ root.overflowItemCount = 0;
+ return items.map(i => markAsVisible(i));
+ }
+
+ const visiblePinnedKeys = new Set(pinnedOverflow ? pinnedItems.slice(0, maxPinned).map(i => i.uniqueKey) : pinnedItems.map(i => i.uniqueKey));
+ const visibleRunningKeys = new Set(runningOverflow ? runningItems.slice(0, maxRunning).map(i => i.uniqueKey) : runningItems.map(i => i.uniqueKey));
+
+ const overflowPinnedCount = pinnedOverflow ? pinnedItems.length - maxPinned : 0;
+ const overflowRunningCount = runningOverflow ? runningItems.length - maxRunning : 0;
+ const totalOverflow = overflowPinnedCount + overflowRunningCount;
+ root.overflowItemCount = totalOverflow;
+
+ const finalItems = [];
+ let addedSeparator = false;
+
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ switch (item.type) {
+ case "launcher":
+ finalItems.push(item);
+ break;
+ case "separator":
+ break;
+ case "pinned":
+ case "grouped":
+ case "window":
+ if (item.isPinned && item.appId !== "__LAUNCHER__") {
+ if (visiblePinnedKeys.has(item.uniqueKey)) {
+ finalItems.push(markAsVisible(item));
+ } else {
+ finalItems.push(markAsOverflow(item));
+ }
+ } else if (item.isRunning && !item.isPinned) {
+ if (!addedSeparator && finalItems.length > 0) {
+ finalItems.push(createSeparator("separator_overflow"));
+ addedSeparator = true;
+ }
+ if (visibleRunningKeys.has(item.uniqueKey)) {
+ finalItems.push(markAsVisible(item));
+ } else {
+ finalItems.push(markAsOverflow(item));
+ }
+ }
+ break;
+ }
+ }
+
+ if (totalOverflow > 0) {
+ const toggleIndex = finalItems.findIndex(i => i.type === "separator");
+ const insertPos = toggleIndex >= 0 ? toggleIndex : finalItems.length;
+ finalItems.splice(insertPos, 0, {
+ uniqueKey: "overflow_toggle",
+ type: "overflow-toggle",
+ appId: "__OVERFLOW_TOGGLE__",
+ toplevel: null,
+ isPinned: false,
+ isRunning: false,
+ overflowCount: totalOverflow
+ });
+ }
+
+ return finalItems;
+ }
+
+ function updateModel() {
+ const baseResult = buildBaseItems();
+ dockItems = applyOverflow(baseResult);
+ }
+
+ 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
+ visible: !isInOverflow || root.overflowExpanded
+ opacity: (isInOverflow && !root.overflowExpanded) ? 0 : 1
+ scale: (isInOverflow && !root.overflowExpanded) ? 0.8 : 1
+
+ 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))
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Easing.OutCubic
+ }
+ }
+
+ Behavior on scale {
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Easing.OutCubic
+ }
+ }
+
+ property real shiftOffset: {
+ if (root.draggedIndex < 0 || !itemData.isPinned || itemData.type === "separator")
+ return 0;
+ if (model.index === root.draggedIndex)
+ return 0;
+
+ const dragIdx = root.draggedIndex;
+ const dropIdx = root.dropTargetIndex;
+ const myIdx = model.index;
+ 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;
+ repeater.updateModel();
+ Qt.callLater(() => {
+ root.suppressShiftAnimation = false;
+ });
+ }
+ function onDockLauncherPositionChanged() {
+ root.suppressShiftAnimation = true;
+ root.draggedIndex = -1;
+ root.dropTargetIndex = -1;
+ repeater.updateModel();
+ Qt.callLater(() => {
+ root.suppressShiftAnimation = false;
+ });
+ }
+ }
+
+ 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;
+ });
+ }
+ function onDockMaxVisibleAppsChanged() {
+ repeater.updateModel();
+ }
+ function onDockMaxVisibleRunningAppsChanged() {
+ repeater.updateModel();
+ }
+ }
+
+ onGroupByAppChanged: repeater.updateModel()
+}