From c371140a97210a48384229c892faca55acd90d3f Mon Sep 17 00:00:00 2001 From: purian23 Date: Thu, 29 Jan 2026 18:12:56 -0500 Subject: [PATCH] feat: Clipboard widget context quick menu --- quickshell/Modules/DankBar/DankBarContent.qml | 1 + .../DankBar/Widgets/ClipboardButton.qml | 282 ++++++++++++++++++ 2 files changed, 283 insertions(+) diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index 56524ee0..a78dcd0b 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -560,6 +560,7 @@ Item { axis: barWindow.axis section: topBarContent.getWidgetSection(parent) parentScreen: barWindow.screen + clipboardHistoryModal: PopoutService.clipboardHistoryModal onClicked: { clipboardHistoryModalPopup.toggle(); } diff --git a/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml b/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml index 475dc423..62337b14 100644 --- a/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml +++ b/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml @@ -1,13 +1,79 @@ import QtQuick +import Quickshell +import Quickshell.Wayland import qs.Common import qs.Modules.Plugins import qs.Widgets +import qs.Services BasePill { id: root property bool isActive: false property var clipboardHistoryModal: null + property var parentScreen: null + property Item windowRoot: (Window.window ? Window.window.contentItem : null) + property bool isAutoHideBar: false + + readonly property real minTooltipY: { + if (!parentScreen || !(axis?.isVertical ?? false)) { + return 0; + } + + if (isAutoHideBar) { + return 0; + } + + if (parentScreen.y > 0) { + return barThickness + barSpacing; + } + + return 0; + } + + function clamp(value, minValue, maxValue) { + return Math.max(minValue, Math.min(maxValue, value)); + } + + function openContextMenu() { + const screen = root.parentScreen || Screen; + const screenX = screen.x || 0; + const screenY = screen.y || 0; + const isVertical = root.axis?.isVertical ?? false; + const edge = root.axis?.edge ?? "top"; + const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS); + + const globalPos = root.mapToGlobal(root.width / 2, root.height / 2); + const relativeX = globalPos.x - screenX; + const relativeY = globalPos.y - screenY; + + let anchorX = relativeX; + let anchorY = relativeY; + + if (isVertical) { + anchorX = edge === "left" + ? (root.barThickness + root.barSpacing + gap) + : (screen.width - (root.barThickness + root.barSpacing + gap)); + anchorY = relativeY + root.minTooltipY; + } else { + anchorX = relativeX; + anchorY = edge === "bottom" + ? (screen.height - (root.barThickness + root.barSpacing + gap)) + : (root.barThickness + root.barSpacing + gap); + } + + contextMenuWindow.showAt(anchorX, anchorY, isVertical, edge, screen); + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: function(mouse) { + if (mouse.button === Qt.RightButton) { + openContextMenu(); + } + } + } content: Component { Item { @@ -22,4 +88,220 @@ BasePill { } } } + + PanelWindow { + id: contextMenuWindow + + WlrLayershell.namespace: "dms:clipboard-context-menu" + + property bool isVertical: false + property string edge: "top" + property point anchorPos: Qt.point(0, 0) + + function showAt(x, y, vertical, barEdge, targetScreen) { + if (targetScreen) { + contextMenuWindow.screen = targetScreen; + } + + anchorPos = Qt.point(x, y); + isVertical = vertical ?? false; + edge = barEdge ?? "top"; + + visible = true; + } + + function closeMenu() { + visible = false; + } + + screen: null + visible: false + WlrLayershell.layer: WlrLayershell.Overlay + WlrLayershell.exclusiveZone: -1 + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + color: "transparent" + anchors { + top: true + left: true + right: true + bottom: true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: contextMenuWindow.closeMenu() + } + + Rectangle { + id: menuContainer + + x: { + if (contextMenuWindow.isVertical) { + if (contextMenuWindow.edge === "left") { + return Math.min(contextMenuWindow.width - width - 10, contextMenuWindow.anchorPos.x); + } + return Math.max(10, contextMenuWindow.anchorPos.x - width); + } + const left = 10; + const right = contextMenuWindow.width - width - 10; + const want = contextMenuWindow.anchorPos.x - width / 2; + return Math.max(left, Math.min(right, want)); + } + y: { + if (contextMenuWindow.isVertical) { + const top = 10; + const bottom = contextMenuWindow.height - height - 10; + const want = contextMenuWindow.anchorPos.y - height / 2; + return Math.max(top, Math.min(bottom, want)); + } + if (contextMenuWindow.edge === "top") { + return Math.min(contextMenuWindow.height - height - 10, contextMenuWindow.anchorPos.y); + } + return Math.max(10, contextMenuWindow.anchorPos.y - height); + } + + width: Math.min(240, Math.max(170, menuColumn.implicitWidth + Theme.spacingS * 2)) + height: Math.max(64, menuColumn.implicitHeight + Theme.spacingS * 2) + color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + radius: Theme.cornerRadius + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) + border.width: 1 + + opacity: contextMenuWindow.visible ? 1 : 0 + visible: opacity > 0 + + Behavior on opacity { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Theme.emphasizedEasing + } + } + + Rectangle { + anchors.fill: parent + anchors.topMargin: 4 + anchors.leftMargin: 2 + anchors.rightMargin: -2 + anchors.bottomMargin: -4 + radius: parent.radius + color: Qt.rgba(0, 0, 0, 0.15) + z: -1 + } + + Column { + id: menuColumn + width: parent.width - Theme.spacingS * 2 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: Theme.spacingS + spacing: 1 + + Rectangle { + id: clearAllItem + width: parent.width + height: 30 + radius: Theme.cornerRadius + color: clearAllArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + + Row { + anchors.fill: parent + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + spacing: Theme.spacingS + + DankIcon { + anchors.verticalCenter: parent.verticalCenter + name: "delete_sweep" + size: 16 + color: Theme.surfaceText + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("Clear All") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + } + } + + MouseArea { + id: clearAllArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + contextMenuWindow.closeMenu(); + if (root.clipboardHistoryModal && root.clipboardHistoryModal.confirmDialog) { + const hasPinned = root.clipboardHistoryModal.pinnedCount > 0; + const message = hasPinned + ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(root.clipboardHistoryModal.pinnedCount) + : I18n.tr("This will permanently delete all clipboard history."); + + root.clipboardHistoryModal.confirmDialog.show( + I18n.tr("Clear History?"), + message, + function () { + if (root.clipboardHistoryModal && typeof root.clipboardHistoryModal.clearAll === "function") { + root.clipboardHistoryModal.clearAll(); + } + }, + function () {} + ); + } + } + } + } + + Rectangle { + id: savedItemsItem + width: parent.width + height: 30 + radius: Theme.cornerRadius + color: savedItemsArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + + Row { + anchors.fill: parent + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + spacing: Theme.spacingS + + DankIcon { + anchors.verticalCenter: parent.verticalCenter + name: "push_pin" + size: 16 + color: Theme.surfaceText + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: I18n.tr("Show Saved Items") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + } + } + + MouseArea { + id: savedItemsArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + contextMenuWindow.closeMenu(); + if (root.clipboardHistoryModal) { + if (typeof root.clipboardHistoryModal.show === "function") { + root.clipboardHistoryModal.show(); + } + Qt.callLater(function () { + if (root.clipboardHistoryModal) { + root.clipboardHistoryModal.activeTab = "saved"; + } + }); + } + } + } + } + } + } + } }