diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index a8d29d3f..4bdfc20c 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -405,6 +405,9 @@ Singleton { property int barMaxVisibleApps: 0 property int barMaxVisibleRunningApps: 0 property bool barShowOverflowBadge: true + property bool trayAutoOverflow: true + property bool trayPopupSingleLine: true + property int trayMaxVisibleItems: 0 property bool appsDockHideIndicators: false property bool appsDockColorizeActive: false property string appsDockActiveColorMode: "primary" diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index cf6a0ce4..1c8240c6 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -164,6 +164,9 @@ var SPEC = { barMaxVisibleApps: { def: 0 }, barMaxVisibleRunningApps: { def: 0 }, barShowOverflowBadge: { def: true }, + trayAutoOverflow: { def: true }, + trayPopupSingleLine: { def: true }, + trayMaxVisibleItems: { def: 0 }, appsDockHideIndicators: { def: false }, appsDockColorizeActive: { def: false }, appsDockActiveColorMode: { def: "primary" }, diff --git a/quickshell/Modules/DankBar/CenterSection.qml b/quickshell/Modules/DankBar/CenterSection.qml index df86d4d3..928f2cd8 100644 --- a/quickshell/Modules/DankBar/CenterSection.qml +++ b/quickshell/Modules/DankBar/CenterSection.qml @@ -15,6 +15,7 @@ Item { property real barSpacing: 4 property var barConfig: null property var blurBarWindow: null + property real sectionAvailablePrimarySize: 0 property bool overrideAxisLayout: false property bool forceVerticalLayout: false @@ -359,6 +360,7 @@ Item { barSpacing: root.barSpacing barConfig: root.barConfig blurBarWindow: root.blurBarWindow + sectionAvailablePrimarySize: root.sectionAvailablePrimarySize isFirst: index === 0 isLast: index === centerRepeater.count - 1 sectionSpacing: parent.itemSpacing diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index ef3c914d..a3421d4b 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -497,6 +497,7 @@ Item { widgetThickness: barWindow.widgetThickness barThickness: barWindow.effectiveBarThickness barSpacing: barConfig?.spacing ?? 4 + sectionAvailablePrimarySize: Math.max(1, hCenterSection.x > 0 ? hCenterSection.x : parent.width / 3) } Binding { @@ -529,6 +530,7 @@ Item { widgetThickness: barWindow.widgetThickness barThickness: barWindow.effectiveBarThickness barSpacing: barConfig?.spacing ?? 4 + sectionAvailablePrimarySize: Math.max(1, hCenterSection.x > 0 ? parent.width - (hCenterSection.x + hCenterSection.width) : parent.width / 3) } Binding { @@ -561,6 +563,7 @@ Item { widgetThickness: barWindow.widgetThickness barThickness: barWindow.effectiveBarThickness barSpacing: barConfig?.spacing ?? 4 + sectionAvailablePrimarySize: Math.max(1, hRightSection.x > 0 ? hRightSection.x - (hLeftSection.x + hLeftSection.width) : parent.width / 3) } Binding { @@ -600,6 +603,7 @@ Item { widgetThickness: barWindow.widgetThickness barThickness: barWindow.effectiveBarThickness barSpacing: barConfig?.spacing ?? 4 + sectionAvailablePrimarySize: Math.max(1, vCenterSection.y > 0 ? vCenterSection.y : parent.height / 3) } Binding { @@ -633,6 +637,7 @@ Item { widgetThickness: barWindow.widgetThickness barThickness: barWindow.effectiveBarThickness barSpacing: barConfig?.spacing ?? 4 + sectionAvailablePrimarySize: Math.max(1, vRightSection.y > 0 ? vRightSection.y - (vLeftSection.y + vLeftSection.height) : parent.height / 3) } Binding { @@ -667,6 +672,7 @@ Item { widgetThickness: barWindow.widgetThickness barThickness: barWindow.effectiveBarThickness barSpacing: barConfig?.spacing ?? 4 + sectionAvailablePrimarySize: Math.max(1, vCenterSection.y > 0 ? parent.height - (vCenterSection.y + vCenterSection.height) : parent.height / 3) } Binding { diff --git a/quickshell/Modules/DankBar/LeftSection.qml b/quickshell/Modules/DankBar/LeftSection.qml index d7219db2..50091e77 100644 --- a/quickshell/Modules/DankBar/LeftSection.qml +++ b/quickshell/Modules/DankBar/LeftSection.qml @@ -14,6 +14,7 @@ Item { property real barSpacing: 4 property var barConfig: null property var blurBarWindow: null + property real sectionAvailablePrimarySize: 0 property bool overrideAxisLayout: false property bool forceVerticalLayout: false @@ -61,6 +62,7 @@ Item { barSpacing: root.barSpacing barConfig: root.barConfig blurBarWindow: root.blurBarWindow + sectionAvailablePrimarySize: root.sectionAvailablePrimarySize isFirst: index === 0 isLast: index === rowRepeater.count - 1 sectionSpacing: parent.rowSpacing @@ -106,6 +108,7 @@ Item { barSpacing: root.barSpacing barConfig: root.barConfig blurBarWindow: root.blurBarWindow + sectionAvailablePrimarySize: root.sectionAvailablePrimarySize isFirst: index === 0 isLast: index === columnRepeater.count - 1 sectionSpacing: parent.columnSpacing diff --git a/quickshell/Modules/DankBar/RightSection.qml b/quickshell/Modules/DankBar/RightSection.qml index 58844bc0..2f19dff0 100644 --- a/quickshell/Modules/DankBar/RightSection.qml +++ b/quickshell/Modules/DankBar/RightSection.qml @@ -14,6 +14,7 @@ Item { property real barSpacing: 4 property var barConfig: null property var blurBarWindow: null + property real sectionAvailablePrimarySize: 0 property bool overrideAxisLayout: false property bool forceVerticalLayout: false @@ -63,6 +64,7 @@ Item { barSpacing: root.barSpacing barConfig: root.barConfig blurBarWindow: root.blurBarWindow + sectionAvailablePrimarySize: root.sectionAvailablePrimarySize isFirst: index === 0 isLast: index === rowRepeater.count - 1 sectionSpacing: parent.rowSpacing @@ -108,6 +110,7 @@ Item { barSpacing: root.barSpacing barConfig: root.barConfig blurBarWindow: root.blurBarWindow + sectionAvailablePrimarySize: root.sectionAvailablePrimarySize isFirst: index === 0 isLast: index === columnRepeater.count - 1 sectionSpacing: parent.columnSpacing diff --git a/quickshell/Modules/DankBar/WidgetHost.qml b/quickshell/Modules/DankBar/WidgetHost.qml index 4b23054a..98497161 100644 --- a/quickshell/Modules/DankBar/WidgetHost.qml +++ b/quickshell/Modules/DankBar/WidgetHost.qml @@ -17,6 +17,7 @@ Loader { property real barSpacing: 4 property var barConfig: null property var blurBarWindow: null + property real sectionAvailablePrimarySize: 0 property bool isFirst: false property bool isLast: false property real sectionSpacing: 0 @@ -141,6 +142,14 @@ Loader { restoreMode: Binding.RestoreNone } + Binding { + target: root.item + when: root.item && "sectionAvailablePrimarySize" in root.item + property: "sectionAvailablePrimarySize" + value: root.sectionAvailablePrimarySize + restoreMode: Binding.RestoreNone + } + Binding { target: root.item when: root.item && "isLeftBarEdge" in root.item diff --git a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml index 6fd746fb..a7493de1 100644 --- a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml +++ b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml @@ -22,6 +22,10 @@ BasePill { property bool isAtBottom: false property bool isAutoHideBar: false property bool useOverflowPopup: !widgetData?.trayUseInlineExpansion + property bool useSingleLineOverflowPopup: widgetData?.trayPopupSingleLine ?? SettingsData.trayPopupSingleLine + property bool useAutomaticOverflow: widgetData?.trayAutoOverflow ?? SettingsData.trayAutoOverflow + property int configuredMaxVisibleItems: widgetData?.trayMaxVisibleItems ?? SettingsData.trayMaxVisibleItems + property real sectionAvailablePrimarySize: 0 readonly property var hiddenTrayIds: { const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || ""; return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : []; @@ -146,12 +150,32 @@ BasePill { readonly property var allSortedTrayItems: sortByPreferredOrder(allTrayItems, _trayOrderTrigger) readonly property var allSortedTrayItemKeys: allSortedTrayItems.map(item => getTrayItemKey(item)) - readonly property var mainBarItemsRaw: allSortedTrayItems.filter(item => !SessionData.isHiddenTrayId(root.getTrayItemKey(item))) + readonly property var visibleSortedTrayItems: allSortedTrayItems.filter(item => !SessionData.isHiddenTrayId(root.getTrayItemKey(item))) + readonly property int automaticVisibleItemLimit: { + if (!root.useAutomaticOverflow) + return root.visibleSortedTrayItems.length; + + const explicitLimit = Number(root.configuredMaxVisibleItems || 0); + if (explicitLimit > 0) + return Math.max(1, Math.min(root.visibleSortedTrayItems.length, explicitLimit)); + + const scale = (typeof CompositorService !== "undefined" && CompositorService.getScreenScale) ? Math.max(1, CompositorService.getScreenScale(root.parentScreen)) : 1; + const sectionPrimary = root.sectionAvailablePrimarySize > 0 ? root.sectionAvailablePrimarySize : (root.isVerticalOrientation ? (root.parentScreen?.height || 0) : (root.parentScreen?.width || 0)); + const logicalPrimary = sectionPrimary > 0 ? (sectionPrimary / scale) : 640; + const maxTrayShare = root.isVerticalOrientation ? 0.55 : 0.50; + const itemSize = Math.max(1, root.trayItemSize); + const slots = Math.floor((logicalPrimary * maxTrayShare) / itemSize); + return Math.max(2, Math.min(10, Math.min(root.visibleSortedTrayItems.length, slots))); + } + readonly property var mainBarItemsRaw: visibleSortedTrayItems.slice(0, automaticVisibleItemLimit) readonly property var mainBarItems: mainBarItemsRaw.map((item, idx) => ({ key: getTrayItemKey(item), item: item })) - readonly property var hiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item))) + readonly property var autoOverflowBarItems: visibleSortedTrayItems.slice(automaticVisibleItemLimit) + readonly property var manualHiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item))) + readonly property var hiddenBarItemKeys: manualHiddenBarItems.concat(autoOverflowBarItems).map(item => root.getTrayItemKey(item)) + readonly property var hiddenBarItems: allSortedTrayItems.filter(item => hiddenBarItemKeys.indexOf(root.getTrayItemKey(item)) !== -1) readonly property string trayIconTintMode: { const configuredMode = SettingsData.systemTrayIconTintMode || "none"; switch (configuredMode) { @@ -219,6 +243,10 @@ BasePill { const fromKey = mainBarItems[visibleFromIndex]?.key ?? null; const toKey = mainBarItems[visibleToIndex]?.key ?? null; + moveTrayItemKeyInFullOrder(fromKey, toKey); + } + + function moveTrayItemKeyInFullOrder(fromKey, toKey) { if (!fromKey || !toKey) return; @@ -233,10 +261,103 @@ BasePill { SessionData.setTrayItemOrder(fullOrder); } + function promoteTrayItemToBar(item) { + const itemKey = getTrayItemKey(item); + if (!itemKey) + return; + if (SessionData.isHiddenTrayId(itemKey)) { + SessionData.showTrayId(itemKey); + return; + } + + const fullOrder = [...allSortedTrayItemKeys]; + const fromIndex = fullOrder.indexOf(itemKey); + if (fromIndex < 0) + return; + const movedKey = fullOrder.splice(fromIndex, 1)[0]; + const targetIndex = Math.max(0, Math.min(root.automaticVisibleItemLimit - 1, fullOrder.length)); + fullOrder.splice(targetIndex, 0, movedKey); + SessionData.setTrayItemOrder(fullOrder); + } + + function isManualHiddenTrayItem(item) { + return SessionData.isHiddenTrayId(getTrayItemKey(item)); + } + + function isAutoOverflowTrayItem(item) { + const key = getTrayItemKey(item); + return key && !isManualHiddenTrayItem(item) && root.autoOverflowBarItems.some(overflowItem => getTrayItemKey(overflowItem) === key); + } + + function dragShiftOffset(index, draggedIndex, dropTargetIndex, shiftAmount) { + if (draggedIndex < 0 || index === draggedIndex || dropTargetIndex < 0) + return 0; + if (draggedIndex < dropTargetIndex && index > draggedIndex && index <= dropTargetIndex) + return -shiftAmount; + if (draggedIndex > dropTargetIndex && index >= dropTargetIndex && index < draggedIndex) + return shiftAmount; + return 0; + } + + function beginMainDrag(visualIndex, reversed) { + root.draggedIndex = reversed ? (root.mainBarItems.length - 1 - visualIndex) : visualIndex; + root.dropTargetIndex = root.draggedIndex; + } + + function updateMainDrag(axisOffset, visualIndex, reversed) { + const itemSize = root.trayItemSize; + const slotOffset = Math.round(axisOffset / itemSize); + const visualTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, visualIndex + slotOffset)); + const newTargetIndex = reversed ? (root.mainBarItems.length - 1 - visualTargetIndex) : visualTargetIndex; + if (newTargetIndex !== root.dropTargetIndex) + root.dropTargetIndex = newTargetIndex; + } + + function finishMainDrag() { + const didReorder = root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex; + if (didReorder) { + root.suppressShiftAnimation = true; + root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex); + Qt.callLater(() => root.suppressShiftAnimation = false); + } + root.draggedIndex = -1; + root.dropTargetIndex = -1; + return didReorder; + } + + function beginPopupDrag(index) { + root.popupDraggedIndex = index; + root.popupDropTargetIndex = index; + } + + function updatePopupDrag(axisOffset, index) { + const itemSize = root.trayItemSize + 6; + const slotOffset = Math.round(axisOffset / itemSize); + const newTargetIndex = Math.max(0, Math.min(root.hiddenBarItems.length - 1, index + slotOffset)); + if (newTargetIndex !== root.popupDropTargetIndex) + root.popupDropTargetIndex = newTargetIndex; + } + + function finishPopupDrag() { + const didReorder = root.popupDropTargetIndex >= 0 && root.popupDropTargetIndex !== root.popupDraggedIndex; + if (didReorder) { + const fromItem = root.hiddenBarItems[root.popupDraggedIndex]; + const toItem = root.hiddenBarItems[root.popupDropTargetIndex]; + root.suppressShiftAnimation = true; + root.moveTrayItemKeyInFullOrder(root.getTrayItemKey(fromItem), root.getTrayItemKey(toItem)); + Qt.callLater(() => root.suppressShiftAnimation = false); + } + root.popupDraggedIndex = -1; + root.popupDropTargetIndex = -1; + return didReorder; + } + property int draggedIndex: -1 property int dropTargetIndex: -1 + property int popupDraggedIndex: -1 + property int popupDropTargetIndex: -1 property bool suppressShiftAnimation: false - readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length + readonly property bool hasHiddenItems: hiddenBarItems.length > 0 readonly property bool inlineExpanded: hasHiddenItems && !useOverflowPopup && menuOpen visible: allTrayItems.length > 0 opacity: allTrayItems.length > 0 ? 1 : 0 @@ -351,22 +472,7 @@ BasePill { height: root.barThickness z: dragHandler.dragging ? 100 : 0 - property real shiftOffset: { - if (root.draggedIndex < 0) - return 0; - if (index === root.draggedIndex) - return 0; - const dragIdx = root.draggedIndex; - const dropIdx = root.dropTargetIndex; - const shiftAmount = root.trayItemSize; - if (dropIdx < 0) - return 0; - if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx) - return -shiftAmount; - if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx) - return shiftAmount; - return 0; - } + property real shiftOffset: root.dragShiftOffset(index, root.draggedIndex, root.dropTargetIndex, root.trayItemSize) transform: Translate { x: delegateRoot.shiftOffset @@ -466,19 +572,12 @@ BasePill { onReleased: mouse => { longPressTimer.stop(); const wasDragging = dragHandler.dragging; - const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex; - - if (didReorder) { - root.suppressShiftAnimation = true; - root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex); - Qt.callLater(() => root.suppressShiftAnimation = false); - } + if (wasDragging) + root.finishMainDrag(); dragHandler.longPressing = false; dragHandler.dragging = false; dragHandler.dragAxisOffset = 0; - root.draggedIndex = -1; - root.dropTargetIndex = -1; if (wasDragging || mouse.button !== Qt.LeftButton) return; @@ -501,8 +600,7 @@ BasePill { const distance = Math.abs(mouse.x - dragHandler.dragStartPos.x); if (distance > 5) { dragHandler.dragging = true; - root.draggedIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - index) : index; - root.dropTargetIndex = root.draggedIndex; + root.beginMainDrag(index, root.reverseInlineHorizontal); } } if (!dragHandler.dragging) @@ -510,13 +608,7 @@ BasePill { const axisOffset = mouse.x - dragHandler.dragStartPos.x; dragHandler.dragAxisOffset = axisOffset; - const itemSize = root.trayItemSize; - const slotOffset = Math.round(axisOffset / itemSize); - const visualTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset)); - const newTargetIndex = root.reverseInlineHorizontal ? (root.mainBarItems.length - 1 - visualTargetIndex) : visualTargetIndex; - if (newTargetIndex !== root.dropTargetIndex) { - root.dropTargetIndex = newTargetIndex; - } + root.updateMainDrag(axisOffset, index, root.reverseInlineHorizontal); } onClicked: mouse => { @@ -706,22 +798,7 @@ BasePill { height: root.trayItemSize z: dragHandler.dragging ? 100 : 0 - property real shiftOffset: { - if (root.draggedIndex < 0) - return 0; - if (index === root.draggedIndex) - return 0; - const dragIdx = root.draggedIndex; - const dropIdx = root.dropTargetIndex; - const shiftAmount = root.trayItemSize; - if (dropIdx < 0) - return 0; - if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx) - return -shiftAmount; - if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx) - return shiftAmount; - return 0; - } + property real shiftOffset: root.dragShiftOffset(index, root.draggedIndex, root.dropTargetIndex, root.trayItemSize) transform: Translate { y: shiftOffset @@ -821,19 +898,12 @@ BasePill { onReleased: mouse => { longPressTimer.stop(); const wasDragging = dragHandler.dragging; - const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex; - - if (didReorder) { - root.suppressShiftAnimation = true; - root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex); - Qt.callLater(() => root.suppressShiftAnimation = false); - } + if (wasDragging) + root.finishMainDrag(); dragHandler.longPressing = false; dragHandler.dragging = false; dragHandler.dragAxisOffset = 0; - root.draggedIndex = -1; - root.dropTargetIndex = -1; if (wasDragging || mouse.button !== Qt.LeftButton) return; @@ -856,8 +926,7 @@ BasePill { const distance = Math.abs(mouse.y - dragHandler.dragStartPos.y); if (distance > 5) { dragHandler.dragging = true; - root.draggedIndex = index; - root.dropTargetIndex = root.draggedIndex; + root.beginMainDrag(index, false); } } if (!dragHandler.dragging) @@ -865,12 +934,7 @@ BasePill { const axisOffset = mouse.y - dragHandler.dragStartPos.y; dragHandler.dragAxisOffset = axisOffset; - const itemSize = root.trayItemSize; - const slotOffset = Math.round(axisOffset / itemSize); - const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset)); - if (newTargetIndex !== root.dropTargetIndex) { - root.dropTargetIndex = newTargetIndex; - } + root.updateMainDrag(axisOffset, index, false); } onClicked: mouse => { @@ -1136,20 +1200,38 @@ BasePill { id: menuContainer objectName: "overflowMenuContainer" + readonly property bool popupUsesVerticalLine: root.useSingleLineOverflowPopup && root.isVerticalOrientation + readonly property real popupPadding: Theme.spacingS + (popupUsesVerticalLine ? 3 : 0) + readonly property real rawWidth: { const itemCount = root.hiddenBarItems.length; - const cols = Math.min(5, itemCount); + if (itemCount === 0) + return 0; + if (popupUsesVerticalLine) + return root.trayItemSize + 4 + popupPadding * 2; + const cols = root.useSingleLineOverflowPopup ? itemCount : Math.min(5, itemCount); const itemSize = root.trayItemSize + 4; const spacing = 2; - return cols * itemSize + (cols - 1) * spacing + Theme.spacingS * 2; + const desiredWidth = cols * itemSize + (cols - 1) * spacing + popupPadding * 2; + if (!root.useSingleLineOverflowPopup) + return desiredWidth; + const maxWidth = Math.max(itemSize + popupPadding * 2, overflowMenu.maskWidth - 20); + return Math.min(desiredWidth, maxWidth); } readonly property real rawHeight: { const itemCount = root.hiddenBarItems.length; - const cols = Math.min(5, itemCount); - const rows = Math.ceil(itemCount / cols); + if (itemCount === 0) + return 0; const itemSize = root.trayItemSize + 4; const spacing = 2; - return rows * itemSize + (rows - 1) * spacing + Theme.spacingS * 2; + if (popupUsesVerticalLine) { + const desiredHeight = itemCount * itemSize + (itemCount - 1) * spacing + popupPadding * 2; + const maxHeight = Math.max(itemSize + popupPadding * 2, overflowMenu.maskHeight - 20); + return Math.min(desiredHeight, maxHeight); + } + const cols = root.useSingleLineOverflowPopup ? itemCount : Math.min(5, itemCount); + const rows = Math.ceil(itemCount / cols); + return rows * itemSize + (rows - 1) * spacing + popupPadding * 2; } readonly property real alignedWidth: Theme.px(rawWidth, overflowMenu.dpr) @@ -1230,76 +1312,161 @@ BasePill { z: 100 } - Grid { - id: menuGrid + Flickable { anchors.centerIn: parent - columns: Math.min(5, root.hiddenBarItems.length) - spacing: 2 - rowSpacing: 2 + width: parent.width - menuContainer.popupPadding * 2 + height: parent.height - menuContainer.popupPadding * 2 + contentWidth: menuGrid.implicitWidth + contentHeight: menuGrid.implicitHeight + boundsBehavior: Flickable.StopAtBounds + clip: true + interactive: root.useSingleLineOverflowPopup && (menuContainer.popupUsesVerticalLine ? contentHeight > height : contentWidth > width) - Repeater { - model: root.hiddenBarItems + Grid { + id: menuGrid + anchors.verticalCenter: menuContainer.popupUsesVerticalLine ? undefined : parent.verticalCenter + anchors.horizontalCenter: menuContainer.popupUsesVerticalLine ? parent.horizontalCenter : undefined + columns: menuContainer.popupUsesVerticalLine ? 1 : (root.useSingleLineOverflowPopup ? root.hiddenBarItems.length : Math.min(5, root.hiddenBarItems.length)) + spacing: 2 + rowSpacing: 2 - delegate: Rectangle { - property var trayItem: modelData - property string iconSource: root.trayIconSourceFor(trayItem) + Repeater { + model: root.hiddenBarItems - width: root.trayItemSize + 4 - height: root.trayItemSize + 4 - radius: Theme.cornerRadius - color: itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0) + delegate: Rectangle { + id: overflowItemRoot + property var trayItem: modelData + property string itemKey: root.getTrayItemKey(trayItem) + property string iconSource: root.trayIconSourceFor(trayItem) - IconImage { - id: menuIconImg - anchors.centerIn: parent - width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) - height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) - source: parent.iconSource - asynchronous: true - smooth: true - mipmap: true - visible: status === Image.Ready - layer.enabled: root.trayIconTintEnabled - layer.effect: MultiEffect { - saturation: root.trayIconSaturation - colorization: root.trayIconColorization - colorizationColor: root.trayIconTintColor - } - } + width: root.trayItemSize + 4 + height: root.trayItemSize + 4 + z: popupDragHandler.dragging ? 100 : 0 + radius: Theme.cornerRadius + color: itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0) + border.width: popupDragHandler.dragging ? 2 : 0 + border.color: Theme.primary + opacity: popupDragHandler.dragging ? 0.8 : 1.0 - StyledText { - anchors.centerIn: parent - visible: !menuIconImg.visible - text: { - const itemId = trayItem?.id || ""; - if (!itemId) - return "?"; - return itemId.charAt(0).toUpperCase(); - } - font.pixelSize: 10 - color: Theme.widgetTextColor - } + property real shiftOffset: root.dragShiftOffset(index, root.popupDraggedIndex, root.popupDropTargetIndex, root.trayItemSize + 6) - MouseArea { - id: itemArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton - cursorShape: Qt.PointingHandCursor - onClicked: mouse => { - if (!trayItem) - return; - if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) { - trayItem.activate(); - root.menuOpen = false; - return; + transform: Translate { + x: !menuContainer.popupUsesVerticalLine ? overflowItemRoot.shiftOffset + (popupDragHandler.dragging ? popupDragHandler.dragAxisOffset : 0) : 0 + y: menuContainer.popupUsesVerticalLine ? overflowItemRoot.shiftOffset + (popupDragHandler.dragging ? popupDragHandler.dragAxisOffset : 0) : 0 + Behavior on x { + enabled: !root.suppressShiftAnimation && !menuContainer.popupUsesVerticalLine + NumberAnimation { + duration: 150 + easing.type: Easing.OutCubic + } } - if (!trayItem.hasMenu) { - const gp = itemArea.mapToGlobal(mouse.x, mouse.y); - root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y)); - return; + Behavior on y { + enabled: !root.suppressShiftAnimation && menuContainer.popupUsesVerticalLine + NumberAnimation { + duration: 150 + easing.type: Easing.OutCubic + } + } + } + + Item { + id: popupDragHandler + anchors.fill: parent + property bool dragging: false + property point dragStartPos: Qt.point(0, 0) + property real dragAxisOffset: 0 + property bool longPressing: false + + Timer { + id: popupLongPressTimer + interval: 400 + repeat: false + onTriggered: popupDragHandler.longPressing = true + } + } + + IconImage { + id: menuIconImg + anchors.centerIn: parent + width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) + height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) + source: parent.iconSource + asynchronous: true + smooth: true + mipmap: true + visible: status === Image.Ready + layer.enabled: root.trayIconTintEnabled + layer.effect: MultiEffect { + saturation: root.trayIconSaturation + colorization: root.trayIconColorization + colorizationColor: root.trayIconTintColor + } + } + + StyledText { + anchors.centerIn: parent + visible: !menuIconImg.visible + text: { + const itemId = trayItem?.id || ""; + if (!itemId) + return "?"; + return itemId.charAt(0).toUpperCase(); + } + font.pixelSize: 10 + color: Theme.widgetTextColor + } + + MouseArea { + id: itemArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + cursorShape: popupDragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor + onPressed: mouse => { + if (mouse.button === Qt.LeftButton) { + popupDragHandler.dragStartPos = Qt.point(mouse.x, mouse.y); + popupLongPressTimer.start(); + } + } + onReleased: mouse => { + popupLongPressTimer.stop(); + const wasDragging = popupDragHandler.dragging; + if (wasDragging) + root.finishPopupDrag(); + + popupDragHandler.longPressing = false; + popupDragHandler.dragging = false; + popupDragHandler.dragAxisOffset = 0; + } + onPositionChanged: mouse => { + const axisDelta = menuContainer.popupUsesVerticalLine ? (mouse.y - popupDragHandler.dragStartPos.y) : (mouse.x - popupDragHandler.dragStartPos.x); + if (popupDragHandler.longPressing && !popupDragHandler.dragging && Math.abs(axisDelta) > 5) { + popupDragHandler.dragging = true; + root.beginPopupDrag(index); + } + if (!popupDragHandler.dragging) + return; + + popupDragHandler.dragAxisOffset = axisDelta; + root.updatePopupDrag(axisDelta, index); + } + onClicked: mouse => { + if (popupDragHandler.dragging) + return; + if (!trayItem) + return; + if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) { + trayItem.activate(); + root.menuOpen = false; + return; + } + if (!trayItem.hasMenu) { + const gp = itemArea.mapToGlobal(mouse.x, mouse.y); + root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y)); + return; + } + root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis); } - root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis); } } } @@ -1695,7 +1862,12 @@ BasePill { anchors.left: parent.left anchors.leftMargin: Theme.spacingS anchors.verticalCenter: parent.verticalCenter - text: menuRoot.trayItem?.id || "Unknown" + text: { + const itemId = menuRoot.trayItem?.id || "Unknown"; + if (root.isAutoOverflowTrayItem(menuRoot.trayItem)) + return itemId + " ยท " + I18n.tr("Keep in Bar"); + return itemId; + } font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceTextMedium elide: Text.ElideMiddle @@ -1706,7 +1878,11 @@ BasePill { anchors.right: parent.right anchors.rightMargin: Theme.spacingS anchors.verticalCenter: parent.verticalCenter - name: SessionData.isHiddenTrayId(root.getTrayItemKey(menuRoot.trayItem)) ? "visibility" : "visibility_off" + name: { + if (root.isAutoOverflowTrayItem(menuRoot.trayItem)) + return "push_pin"; + return root.isManualHiddenTrayItem(menuRoot.trayItem) ? "visibility" : "visibility_off"; + } size: 16 color: Theme.widgetTextColor } @@ -1720,7 +1896,9 @@ BasePill { const itemKey = root.getTrayItemKey(menuRoot.trayItem); if (!itemKey) return; - if (SessionData.isHiddenTrayId(itemKey)) { + if (root.isAutoOverflowTrayItem(menuRoot.trayItem)) { + root.promoteTrayItemToBar(menuRoot.trayItem); + } else if (root.isManualHiddenTrayItem(menuRoot.trayItem)) { SessionData.showTrayId(itemKey); } else { SessionData.hideTrayId(itemKey); diff --git a/quickshell/Modules/Settings/WidgetsTab.qml b/quickshell/Modules/Settings/WidgetsTab.qml index 26058ace..1b611486 100644 --- a/quickshell/Modules/Settings/WidgetsTab.qml +++ b/quickshell/Modules/Settings/WidgetsTab.qml @@ -460,7 +460,7 @@ Item { "id": widget.id, "enabled": widget.enabled }; - var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowSize", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "keyboardLayoutNameShowIcon", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "showIdleInhibitorIcon", "showDoNotDisturbIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion", "hideWhenIdle"]; + var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowSize", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "keyboardLayoutNameShowIcon", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "showIdleInhibitorIcon", "showDoNotDisturbIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion", "trayPopupSingleLine", "trayAutoOverflow", "trayMaxVisibleItems", "hideWhenIdle"]; for (var i = 0; i < keys.length; i++) { if (widget[keys[i]] !== undefined) result[keys[i]] = widget[keys[i]]; @@ -803,6 +803,12 @@ Item { item.barShowOverflowBadge = widget.barShowOverflowBadge; if (widget.trayUseInlineExpansion !== undefined) item.trayUseInlineExpansion = widget.trayUseInlineExpansion; + if (widget.trayPopupSingleLine !== undefined) + item.trayPopupSingleLine = widget.trayPopupSingleLine; + if (widget.trayAutoOverflow !== undefined) + item.trayAutoOverflow = widget.trayAutoOverflow; + if (widget.trayMaxVisibleItems !== undefined) + item.trayMaxVisibleItems = widget.trayMaxVisibleItems; if (widget.hideWhenIdle !== undefined) item.hideWhenIdle = widget.hideWhenIdle; } diff --git a/quickshell/Modules/Settings/WidgetsTabSection.qml b/quickshell/Modules/Settings/WidgetsTabSection.qml index 6a4386e3..a6af96da 100644 --- a/quickshell/Modules/Settings/WidgetsTabSection.qml +++ b/quickshell/Modules/Settings/WidgetsTabSection.qml @@ -43,7 +43,7 @@ Column { "id": widget.id, "enabled": widget.enabled }; - var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowSize", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "keyboardLayoutNameShowIcon", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "showIdleInhibitorIcon", "showDoNotDisturbIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion"]; + var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowSize", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "keyboardLayoutNameShowIcon", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "showIdleInhibitorIcon", "showDoNotDisturbIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge", "trayUseInlineExpansion", "trayPopupSingleLine", "trayAutoOverflow", "trayMaxVisibleItems"]; for (var i = 0; i < keys.length; i++) { if (widget[keys[i]] !== undefined) result[keys[i]] = widget[keys[i]]; @@ -1126,6 +1126,188 @@ Column { } } } + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: trayPopupLineArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + opacity: (trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false) ? 0.55 : 1 + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "view_week" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Single-Line Popup") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Normal + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: trayPopupLineToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: trayContextMenu.currentWidgetData?.trayPopupSingleLine ?? SettingsData.trayPopupSingleLine + enabled: !(trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false) + } + + MouseArea { + id: trayPopupLineArea + anchors.fill: parent + hoverEnabled: true + cursorShape: (trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false) ? Qt.ArrowCursor : Qt.PointingHandCursor + onClicked: { + if (trayContextMenu.currentWidgetData?.trayUseInlineExpansion ?? false) + return; + const newValue = !(trayContextMenu.currentWidgetData?.trayPopupSingleLine ?? SettingsData.trayPopupSingleLine); + root.overflowSettingChanged(trayContextMenu.sectionId, trayContextMenu.widgetIndex, "trayPopupSingleLine", newValue); + } + } + } + + Rectangle { + width: parent.width + height: 32 + radius: Theme.cornerRadius + color: trayAutoOverflowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "responsive_layout" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Auto Overflow") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Normal + anchors.verticalCenter: parent.verticalCenter + } + } + + DankToggle { + id: trayAutoOverflowToggle + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 20 + checked: trayContextMenu.currentWidgetData?.trayAutoOverflow ?? SettingsData.trayAutoOverflow + } + + MouseArea { + id: trayAutoOverflowArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + const newValue = !(trayContextMenu.currentWidgetData?.trayAutoOverflow ?? SettingsData.trayAutoOverflow); + root.overflowSettingChanged(trayContextMenu.sectionId, trayContextMenu.widgetIndex, "trayAutoOverflow", newValue); + } + } + } + + Rectangle { + width: parent.width + height: 36 + radius: Theme.cornerRadius + color: trayMaxVisibleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + opacity: (trayContextMenu.currentWidgetData?.trayAutoOverflow ?? SettingsData.trayAutoOverflow) ? 1 : 0.55 + + Row { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "low_priority" + size: 16 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Max Visible") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Normal + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: { + const value = trayContextMenu.currentWidgetData?.trayMaxVisibleItems ?? SettingsData.trayMaxVisibleItems; + return value > 0 ? String(value) : I18n.tr("Auto"); + } + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceTextMedium + anchors.verticalCenter: parent.verticalCenter + } + } + + Row { + anchors.right: parent.right + anchors.rightMargin: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + + DankActionButton { + buttonSize: 28 + iconName: "remove" + iconSize: 16 + iconColor: Theme.surfaceText + enabled: trayContextMenu.currentWidgetData?.trayAutoOverflow ?? SettingsData.trayAutoOverflow + onClicked: { + const current = trayContextMenu.currentWidgetData?.trayMaxVisibleItems ?? SettingsData.trayMaxVisibleItems; + root.overflowSettingChanged(trayContextMenu.sectionId, trayContextMenu.widgetIndex, "trayMaxVisibleItems", Math.max(0, current - 1)); + } + } + + DankActionButton { + buttonSize: 28 + iconName: "add" + iconSize: 16 + iconColor: Theme.surfaceText + enabled: trayContextMenu.currentWidgetData?.trayAutoOverflow ?? SettingsData.trayAutoOverflow + onClicked: { + const current = trayContextMenu.currentWidgetData?.trayMaxVisibleItems ?? SettingsData.trayMaxVisibleItems; + root.overflowSettingChanged(trayContextMenu.sectionId, trayContextMenu.widgetIndex, "trayMaxVisibleItems", Math.min(20, current + 1)); + } + } + } + + MouseArea { + id: trayMaxVisibleArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + } + } } } }