From 4982ea53dd9069631646a820511b096daa621b61 Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 22 Dec 2025 13:18:37 -0500 Subject: [PATCH] window: add support for startSystemMove, resize, maximize to floating windows --- .../Modals/FileBrowser/FileBrowserContent.qml | 20 +- .../Modals/FileBrowser/FileBrowserModal.qml | 7 + quickshell/Modals/PolkitAuthModal.qml | 42 ++- quickshell/Modals/ProcessListModal.qml | 351 ++++++++++-------- quickshell/Modals/Settings/SettingsModal.qml | 36 +- quickshell/Modals/WifiPasswordModal.qml | 93 +++-- .../Modules/Plugins/DesktopPluginWrapper.qml | 2 +- .../Modules/Settings/DesktopWidgetBrowser.qml | 38 +- quickshell/Modules/Settings/PluginBrowser.qml | 36 +- quickshell/Modules/Settings/ThemeBrowser.qml | 31 +- .../Modules/Settings/WidgetSelectionPopup.qml | 36 +- quickshell/Widgets/FloatingWindowControls.qml | 116 ++++++ 12 files changed, 578 insertions(+), 230 deletions(-) create mode 100644 quickshell/Widgets/FloatingWindowControls.qml diff --git a/quickshell/Modals/FileBrowser/FileBrowserContent.qml b/quickshell/Modals/FileBrowser/FileBrowserContent.qml index 7ea4ee9f..f828161e 100644 --- a/quickshell/Modals/FileBrowser/FileBrowserContent.qml +++ b/quickshell/Modals/FileBrowser/FileBrowserContent.qml @@ -47,6 +47,7 @@ FocusScope { property int actualGridColumns: 5 property bool _initialized: false property bool closeOnEscape: true + property var windowControls: null signal fileSelected(string path) signal closeRequested @@ -155,7 +156,6 @@ FocusScope { const lastSlash = path.lastIndexOf('/'); if (lastSlash <= 0) return; - const newPath = path.substring(0, lastSlash); if (newPath.length < homeDir.length) { currentPath = homeDir; @@ -534,6 +534,14 @@ FocusScope { width: parent.width height: 48 + MouseArea { + anchors.fill: parent + onPressed: if (windowControls) + windowControls.tryStartMove() + onDoubleClicked: if (windowControls) + windowControls.tryToggleMaximize() + } + Row { spacing: Theme.spacingM anchors.verticalCenter: parent.verticalCenter @@ -595,6 +603,16 @@ FocusScope { onClicked: root.showKeyboardHints = !root.showKeyboardHints } + DankActionButton { + visible: windowControls?.supported ?? false + circular: false + iconName: windowControls?.targetWindow?.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: if (windowControls) + windowControls.tryToggleMaximize() + } + DankActionButton { circular: false iconName: "close" diff --git a/quickshell/Modals/FileBrowser/FileBrowserModal.qml b/quickshell/Modals/FileBrowser/FileBrowserModal.qml index 4b13b51b..441cdcaa 100644 --- a/quickshell/Modals/FileBrowser/FileBrowserModal.qml +++ b/quickshell/Modals/FileBrowser/FileBrowserModal.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell import qs.Common +import qs.Widgets FloatingWindow { id: fileBrowserModal @@ -60,6 +61,7 @@ FloatingWindow { anchors.fill: parent focus: true closeOnEscape: false + windowControls: windowControls browserTitle: fileBrowserModal.browserTitle browserIcon: fileBrowserModal.browserIcon @@ -74,4 +76,9 @@ FloatingWindow { onFileSelected: path => fileBrowserModal.fileSelected(path) onCloseRequested: fileBrowserModal.close() } + + FloatingWindowControls { + id: windowControls + targetWindow: fileBrowserModal + } } diff --git a/quickshell/Modals/PolkitAuthModal.qml b/quickshell/Modals/PolkitAuthModal.qml index 93079d41..467aa7fd 100644 --- a/quickshell/Modals/PolkitAuthModal.qml +++ b/quickshell/Modals/PolkitAuthModal.qml @@ -107,6 +107,15 @@ FloatingWindow { event.accepted = true; } + MouseArea { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: headerRow.height + Theme.spacingM + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() + } + Row { id: headerRow anchors.left: parent.left @@ -117,7 +126,7 @@ FloatingWindow { anchors.topMargin: Theme.spacingM Column { - width: parent.width - 40 + width: parent.width - 60 spacing: Theme.spacingXS StyledText { @@ -151,13 +160,25 @@ FloatingWindow { } } - DankActionButton { - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - enabled: !isLoading - opacity: enabled ? 1 : 0.5 - onClicked: cancelAuth() + Row { + spacing: Theme.spacingXS + + DankActionButton { + visible: windowControls.supported + iconName: root.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: windowControls.tryToggleMaximize() + } + + DankActionButton { + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + enabled: !isLoading + opacity: enabled ? 1 : 0.5 + onClicked: cancelAuth() + } } } @@ -314,4 +335,9 @@ FloatingWindow { } } } + + FloatingWindowControls { + id: windowControls + targetWindow: root + } } diff --git a/quickshell/Modals/ProcessListModal.qml b/quickshell/Modals/ProcessListModal.qml index 6a74678f..8dbb35ea 100644 --- a/quickshell/Modals/ProcessListModal.qml +++ b/quickshell/Modals/ProcessListModal.qml @@ -171,190 +171,237 @@ FloatingWindow { } } - ColumnLayout { + Column { anchors.fill: parent - anchors.margins: Theme.spacingL - spacing: Theme.spacingL + spacing: 0 visible: DgopService.dgopAvailable - RowLayout { - Layout.fillWidth: true - height: 40 + Item { + width: parent.width + height: 48 - StyledText { - text: I18n.tr("System Monitor") - font.pixelSize: Theme.fontSizeLarge + 4 - font.weight: Font.Bold - color: Theme.surfaceText - Layout.alignment: Qt.AlignVCenter + MouseArea { + anchors.fill: parent + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() } - Item { - Layout.fillWidth: true - } - - DankActionButton { - circular: false - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - onClicked: () => { - processListModal.hide(); - } - Layout.alignment: Qt.AlignVCenter - } - } - - Rectangle { - Layout.fillWidth: true - height: 52 - color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) - radius: Theme.cornerRadius - border.color: Theme.outlineLight - border.width: 1 - Row { - anchors.fill: parent - anchors.margins: 4 - spacing: 2 + anchors.left: parent.left + anchors.leftMargin: Theme.spacingL + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingM - Repeater { - model: tabNames + DankIcon { + name: "analytics" + size: Theme.iconSize + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } - Rectangle { - width: (parent.width - (tabNames.length - 1) * 2) / tabNames.length - height: 44 - radius: Theme.cornerRadius - color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent") - border.color: currentTab === index ? Theme.primary : "transparent" - border.width: currentTab === index ? 1 : 0 + StyledText { + text: I18n.tr("System Monitor") + font.pixelSize: Theme.fontSizeXLarge + font.weight: Font.Medium + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } - Row { - anchors.centerIn: parent - spacing: Theme.spacingXS + Row { + anchors.right: parent.right + anchors.rightMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingXS - DankIcon { - name: { - const tabIcons = ["list_alt", "analytics", "settings"]; - return tabIcons[index] || "tab"; - } - size: Theme.iconSize - 2 - color: currentTab === index ? Theme.primary : Theme.surfaceText - opacity: currentTab === index ? 1 : 0.7 - anchors.verticalCenter: parent.verticalCenter + DankActionButton { + visible: windowControls.supported + circular: false + iconName: processListModal.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: windowControls.tryToggleMaximize() + } - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - } - } - } - - StyledText { - text: modelData - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.Medium - color: currentTab === index ? Theme.primary : Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: -1 - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - } - } - } - } - - MouseArea { - id: tabMouseArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: () => { - currentTab = index; - } - } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - } - } - - Behavior on border.color { - ColorAnimation { - duration: Theme.shortDuration - } - } - } + DankActionButton { + circular: false + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: processListModal.hide() } } } - Rectangle { - Layout.fillWidth: true - Layout.fillHeight: true - radius: Theme.cornerRadius - color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) - border.color: Theme.outlineLight - border.width: 1 - - Loader { - id: processesTab + Item { + width: parent.width + height: parent.height - 48 + ColumnLayout { anchors.fill: parent - anchors.margins: Theme.spacingS - active: processListModal.visible && currentTab === 0 - visible: currentTab === 0 - opacity: currentTab === 0 ? 1 : 0 - sourceComponent: processesTabComponent + anchors.leftMargin: Theme.spacingL + anchors.rightMargin: Theme.spacingL + anchors.bottomMargin: Theme.spacingL + anchors.topMargin: 0 + spacing: Theme.spacingL - Behavior on opacity { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.emphasizedEasing + Rectangle { + Layout.fillWidth: true + height: 52 + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + radius: Theme.cornerRadius + border.color: Theme.outlineLight + border.width: 1 + + Row { + anchors.fill: parent + anchors.margins: 4 + spacing: 2 + + Repeater { + model: tabNames + + Rectangle { + width: (parent.width - (tabNames.length - 1) * 2) / tabNames.length + height: 44 + radius: Theme.cornerRadius + color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent") + border.color: currentTab === index ? Theme.primary : "transparent" + border.width: currentTab === index ? 1 : 0 + + Row { + anchors.centerIn: parent + spacing: Theme.spacingXS + + DankIcon { + name: { + const tabIcons = ["list_alt", "analytics", "settings"]; + return tabIcons[index] || "tab"; + } + size: Theme.iconSize - 2 + color: currentTab === index ? Theme.primary : Theme.surfaceText + opacity: currentTab === index ? 1 : 0.7 + anchors.verticalCenter: parent.verticalCenter + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + } + } + } + + StyledText { + text: modelData + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: currentTab === index ? Theme.primary : Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -1 + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + } + } + } + } + + MouseArea { + id: tabMouseArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: () => { + currentTab = index; + } + } + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + } + } + + Behavior on border.color { + ColorAnimation { + duration: Theme.shortDuration + } + } + } + } } } - } - Loader { - id: performanceTab + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.color: Theme.outlineLight + border.width: 1 - anchors.fill: parent - anchors.margins: Theme.spacingS - active: processListModal.visible && currentTab === 1 - visible: currentTab === 1 - opacity: currentTab === 1 ? 1 : 0 - sourceComponent: performanceTabComponent + Loader { + id: processesTab - Behavior on opacity { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.emphasizedEasing + anchors.fill: parent + anchors.margins: Theme.spacingS + active: processListModal.visible && currentTab === 0 + visible: currentTab === 0 + opacity: currentTab === 0 ? 1 : 0 + sourceComponent: processesTabComponent + + Behavior on opacity { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.emphasizedEasing + } + } } - } - } - Loader { - id: systemTab + Loader { + id: performanceTab - anchors.fill: parent - anchors.margins: Theme.spacingS - active: processListModal.visible && currentTab === 2 - visible: currentTab === 2 - opacity: currentTab === 2 ? 1 : 0 - sourceComponent: systemTabComponent + anchors.fill: parent + anchors.margins: Theme.spacingS + active: processListModal.visible && currentTab === 1 + visible: currentTab === 1 + opacity: currentTab === 1 ? 1 : 0 + sourceComponent: performanceTabComponent - Behavior on opacity { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.emphasizedEasing + Behavior on opacity { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.emphasizedEasing + } + } + } + + Loader { + id: systemTab + + anchors.fill: parent + anchors.margins: Theme.spacingS + active: processListModal.visible && currentTab === 2 + visible: currentTab === 2 + opacity: currentTab === 2 ? 1 : 0 + sourceComponent: systemTabComponent + + Behavior on opacity { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.emphasizedEasing + } + } } } } } } } + + FloatingWindowControls { + id: windowControls + targetWindow: processListModal + } } diff --git a/quickshell/Modals/Settings/SettingsModal.qml b/quickshell/Modals/Settings/SettingsModal.qml index 3588c67c..70598e08 100644 --- a/quickshell/Modals/Settings/SettingsModal.qml +++ b/quickshell/Modals/Settings/SettingsModal.qml @@ -163,6 +163,12 @@ FloatingWindow { height: 48 z: 10 + MouseArea { + anchors.fill: parent + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() + } + Rectangle { anchors.fill: parent color: Theme.surfaceContainer @@ -203,17 +209,28 @@ FloatingWindow { } } - DankActionButton { + Row { anchors.right: parent.right anchors.rightMargin: Theme.spacingM anchors.top: parent.top anchors.topMargin: Theme.spacingM - circular: false - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - onClicked: () => { - settingsModal.hide(); + spacing: Theme.spacingXS + + DankActionButton { + visible: windowControls.supported + circular: false + iconName: settingsModal.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: windowControls.tryToggleMaximize() + } + + DankActionButton { + circular: false + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: settingsModal.hide() } } } @@ -257,4 +274,9 @@ FloatingWindow { } } } + + FloatingWindowControls { + id: windowControls + targetWindow: settingsModal + } } diff --git a/quickshell/Modals/WifiPasswordModal.qml b/quickshell/Modals/WifiPasswordModal.qml index 4bd40689..a2c46c8e 100644 --- a/quickshell/Modals/WifiPasswordModal.qml +++ b/quickshell/Modals/WifiPasswordModal.qml @@ -208,7 +208,7 @@ FloatingWindow { usernameInput.text = ""; anonInput.text = ""; domainMatchInput.text = ""; - for (let i = 0; i < dynamicFieldsRepeater.count; i++) { + for (var i = 0; i < dynamicFieldsRepeater.count; i++) { const item = dynamicFieldsRepeater.itemAt(i); if (item?.children[0]) item.children[0].text = ""; @@ -248,51 +248,71 @@ FloatingWindow { Row { width: contentCol.width - Column { - width: parent.width - 40 - spacing: Theme.spacingXS - - StyledText { - text: isVpnPrompt ? I18n.tr("Connect to VPN") : I18n.tr("Connect to Wi-Fi") - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - font.weight: Font.Medium - } + MouseArea { + width: parent.width - 60 + height: headerCol.height + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() Column { + id: headerCol width: parent.width spacing: Theme.spacingXS StyledText { - text: { - if (fieldsInfo.length > 0) - return I18n.tr("Enter credentials for ") + wifiPasswordSSID; - if (isVpnPrompt) - return I18n.tr("Enter password for ") + wifiPasswordSSID; - const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for "); - return prefix + wifiPasswordSSID; - } - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceTextMedium - width: parent.width - elide: Text.ElideRight + text: isVpnPrompt ? I18n.tr("Connect to VPN") : I18n.tr("Connect to Wi-Fi") + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Medium } - StyledText { - visible: isPromptMode && promptReason === "wrong-password" - text: I18n.tr("Incorrect password") - font.pixelSize: Theme.fontSizeSmall - color: Theme.error + Column { width: parent.width + spacing: Theme.spacingXS + + StyledText { + text: { + if (fieldsInfo.length > 0) + return I18n.tr("Enter credentials for ") + wifiPasswordSSID; + if (isVpnPrompt) + return I18n.tr("Enter password for ") + wifiPasswordSSID; + const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for "); + return prefix + wifiPasswordSSID; + } + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceTextMedium + width: parent.width + elide: Text.ElideRight + } + + StyledText { + visible: isPromptMode && promptReason === "wrong-password" + text: I18n.tr("Incorrect password") + font.pixelSize: Theme.fontSizeSmall + color: Theme.error + width: parent.width + } } } } - DankActionButton { - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - onClicked: clearAndClose() + Row { + spacing: Theme.spacingXS + + DankActionButton { + visible: windowControls.supported + iconName: root.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: windowControls.tryToggleMaximize() + } + + DankActionButton { + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: clearAndClose() + } } } @@ -624,7 +644,7 @@ FloatingWindow { color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary enabled: { if (fieldsInfo.length > 0) { - for (let i = 0; i < fieldsInfo.length; i++) { + for (var i = 0; i < fieldsInfo.length; i++) { if (!fieldsInfo[i].isSecret) continue; const fieldName = fieldsInfo[i].name; @@ -668,4 +688,9 @@ FloatingWindow { } } } + + FloatingWindowControls { + id: windowControls + targetWindow: root + } } diff --git a/quickshell/Modules/Plugins/DesktopPluginWrapper.qml b/quickshell/Modules/Plugins/DesktopPluginWrapper.qml index 398ef1f9..fa9b2373 100644 --- a/quickshell/Modules/Plugins/DesktopPluginWrapper.qml +++ b/quickshell/Modules/Plugins/DesktopPluginWrapper.qml @@ -202,7 +202,7 @@ Item { color: "transparent" WlrLayershell.namespace: "quickshell:desktop-widget:" + root.pluginId + (root.instanceId ? ":" + root.instanceId : "") - WlrLayershell.layer: WlrLayer.Bottom + WlrLayershell.layer: root.isInteracting && !CompositorService.useHyprlandFocusGrab ? WlrLayer.Overlay : WlrLayer.Bottom WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.keyboardFocus: { if (!root.isInteracting) diff --git a/quickshell/Modules/Settings/DesktopWidgetBrowser.qml b/quickshell/Modules/Settings/DesktopWidgetBrowser.qml index ddcee06b..804b22d8 100644 --- a/quickshell/Modules/Settings/DesktopWidgetBrowser.qml +++ b/quickshell/Modules/Settings/DesktopWidgetBrowser.qml @@ -1,4 +1,4 @@ -pragma ComponentBehavior: Bound +pragma ComponentBehavior import QtQuick import Quickshell @@ -189,6 +189,12 @@ FloatingWindow { width: parent.width height: 48 + MouseArea { + anchors.fill: parent + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() + } + Rectangle { anchors.fill: parent color: Theme.withAlpha(Theme.surfaceContainer, 0.5) @@ -216,15 +222,28 @@ FloatingWindow { } } - DankActionButton { - circular: false - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText + Row { anchors.right: parent.right anchors.rightMargin: Theme.spacingM anchors.verticalCenter: parent.verticalCenter - onClicked: root.hide() + spacing: Theme.spacingXS + + DankActionButton { + visible: windowControls.supported + circular: false + iconName: root.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: windowControls.tryToggleMaximize() + } + + DankActionButton { + circular: false + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: root.hide() + } } } @@ -414,4 +433,9 @@ FloatingWindow { } } } + + FloatingWindowControls { + id: windowControls + targetWindow: root + } } diff --git a/quickshell/Modules/Settings/PluginBrowser.qml b/quickshell/Modules/Settings/PluginBrowser.qml index 3ab61e16..6a332ffe 100644 --- a/quickshell/Modules/Settings/PluginBrowser.qml +++ b/quickshell/Modules/Settings/PluginBrowser.qml @@ -29,7 +29,6 @@ FloatingWindow { if (!SessionData.showThirdPartyPlugins && !isFirstParty) continue; - if (typeFilter !== "") { var hasCapability = plugin.capabilities && plugin.capabilities.includes(typeFilter); if (!hasCapability) @@ -108,12 +107,12 @@ FloatingWindow { var pluginId = PopoutService.pendingPluginInstall; PopoutService.pendingPluginInstall = ""; urlInstallConfirm.showWithOptions({ - title: I18n.tr("Install Plugin", "plugin installation dialog title"), - message: I18n.tr("Install plugin '%1' from the DMS registry?", "plugin installation confirmation").arg(pluginId), - confirmText: I18n.tr("Install", "install action button"), - cancelText: I18n.tr("Cancel"), - onConfirm: () => installPlugin(pluginId, true), - onCancel: () => hide() + "title": I18n.tr("Install Plugin", "plugin installation dialog title"), + "message": I18n.tr("Install plugin '%1' from the DMS registry?", "plugin installation confirmation").arg(pluginId), + "confirmText": I18n.tr("Install", "install action button"), + "cancelText": I18n.tr("Cancel"), + "onConfirm": () => installPlugin(pluginId, true), + "onCancel": () => hide() }); } @@ -181,7 +180,9 @@ FloatingWindow { } var updated = root.allPlugins.map(p => { var isInstalled = pluginMap[p.name] || pluginMap[p.id] || false; - return Object.assign({}, p, { installed: isInstalled }); + return Object.assign({}, p, { + "installed": isInstalled + }); }); root.allPlugins = updated; root.updateFilteredPlugins(); @@ -227,6 +228,12 @@ FloatingWindow { anchors.top: parent.top height: Math.max(headerIcon.height, headerText.height, refreshButton.height, closeButton.height) + MouseArea { + anchors.fill: parent + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() + } + DankIcon { id: headerIcon name: "store" @@ -276,6 +283,14 @@ FloatingWindow { onClicked: root.refreshPlugins() } + DankActionButton { + visible: windowControls.supported + iconName: root.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 2 + iconColor: Theme.outline + onClicked: windowControls.tryToggleMaximize() + } + DankActionButton { id: closeButton iconName: "close" @@ -718,4 +733,9 @@ FloatingWindow { } } } + + FloatingWindowControls { + id: windowControls + targetWindow: root + } } diff --git a/quickshell/Modules/Settings/ThemeBrowser.qml b/quickshell/Modules/Settings/ThemeBrowser.qml index eebac12b..72906e10 100644 --- a/quickshell/Modules/Settings/ThemeBrowser.qml +++ b/quickshell/Modules/Settings/ThemeBrowser.qml @@ -114,12 +114,12 @@ FloatingWindow { var themeId = PopoutService.pendingThemeInstall; PopoutService.pendingThemeInstall = ""; urlInstallConfirm.showWithOptions({ - title: I18n.tr("Install Theme", "theme installation dialog title"), - message: I18n.tr("Install theme '%1' from the DMS registry?", "theme installation confirmation").arg(themeId), - confirmText: I18n.tr("Install", "install action button"), - cancelText: I18n.tr("Cancel"), - onConfirm: () => installTheme(themeId, themeId, true), - onCancel: () => hide() + "title": I18n.tr("Install Theme", "theme installation dialog title"), + "message": I18n.tr("Install theme '%1' from the DMS registry?", "theme installation confirmation").arg(themeId), + "confirmText": I18n.tr("Install", "install action button"), + "cancelText": I18n.tr("Cancel"), + "onConfirm": () => installTheme(themeId, themeId, true), + "onCancel": () => hide() }); } @@ -222,6 +222,12 @@ FloatingWindow { anchors.top: parent.top height: Math.max(headerIcon.height, headerText.height, refreshButton.height, closeButton.height) + MouseArea { + anchors.fill: parent + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() + } + DankIcon { id: headerIcon name: "palette" @@ -256,6 +262,14 @@ FloatingWindow { onClicked: root.refreshThemes() } + DankActionButton { + visible: windowControls.supported + iconName: root.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 2 + iconColor: Theme.outline + onClicked: windowControls.tryToggleMaximize() + } + DankActionButton { id: closeButton iconName: "close" @@ -569,4 +583,9 @@ FloatingWindow { } } } + + FloatingWindowControls { + id: windowControls + targetWindow: root + } } diff --git a/quickshell/Modules/Settings/WidgetSelectionPopup.qml b/quickshell/Modules/Settings/WidgetSelectionPopup.qml index 60b30188..4c449410 100644 --- a/quickshell/Modules/Settings/WidgetSelectionPopup.qml +++ b/quickshell/Modules/Settings/WidgetSelectionPopup.qml @@ -175,6 +175,12 @@ FloatingWindow { width: parent.width height: 48 + MouseArea { + anchors.fill: parent + onPressed: windowControls.tryStartMove() + onDoubleClicked: windowControls.tryToggleMaximize() + } + Rectangle { anchors.fill: parent color: Theme.surfaceContainer @@ -203,15 +209,28 @@ FloatingWindow { } } - DankActionButton { - circular: false - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText + Row { anchors.right: parent.right anchors.rightMargin: Theme.spacingM anchors.verticalCenter: parent.verticalCenter - onClicked: root.hide() + spacing: Theme.spacingXS + + DankActionButton { + visible: windowControls.supported + circular: false + iconName: root.maximized ? "fullscreen_exit" : "fullscreen" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: windowControls.tryToggleMaximize() + } + + DankActionButton { + circular: false + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + onClicked: root.hide() + } } } @@ -354,4 +373,9 @@ FloatingWindow { } } } + + FloatingWindowControls { + id: windowControls + targetWindow: root + } } diff --git a/quickshell/Widgets/FloatingWindowControls.qml b/quickshell/Widgets/FloatingWindowControls.qml new file mode 100644 index 00000000..2392eeab --- /dev/null +++ b/quickshell/Widgets/FloatingWindowControls.qml @@ -0,0 +1,116 @@ +import QtQuick + +Item { + id: root + + required property var targetWindow + property bool supported: typeof targetWindow.startSystemMove === "function" + + anchors.fill: parent + + function tryStartMove() { + if (!supported) + return; + targetWindow.startSystemMove(); + } + + function tryStartResize(edges) { + if (!supported) + return; + targetWindow.startSystemResize(edges); + } + + function tryToggleMaximize() { + if (!supported) + return; + targetWindow.maximized = !targetWindow.maximized; + } + + MouseArea { + visible: root.supported + height: 6 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.leftMargin: 6 + anchors.rightMargin: 6 + cursorShape: Qt.SizeVerCursor + onPressed: root.tryStartResize(Qt.TopEdge) + } + + MouseArea { + visible: root.supported + width: 6 + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.topMargin: 6 + anchors.bottomMargin: 6 + cursorShape: Qt.SizeHorCursor + onPressed: root.tryStartResize(Qt.LeftEdge) + } + + MouseArea { + visible: root.supported + width: 6 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.topMargin: 6 + anchors.bottomMargin: 6 + cursorShape: Qt.SizeHorCursor + onPressed: root.tryStartResize(Qt.RightEdge) + } + + MouseArea { + visible: root.supported + width: 6 + height: 6 + anchors.left: parent.left + anchors.top: parent.top + cursorShape: Qt.SizeFDiagCursor + onPressed: root.tryStartResize(Qt.LeftEdge | Qt.TopEdge) + } + + MouseArea { + visible: root.supported + width: 6 + height: 6 + anchors.right: parent.right + anchors.top: parent.top + cursorShape: Qt.SizeBDiagCursor + onPressed: root.tryStartResize(Qt.RightEdge | Qt.TopEdge) + } + + MouseArea { + visible: root.supported + height: 6 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.leftMargin: 6 + anchors.rightMargin: 6 + cursorShape: Qt.SizeVerCursor + onPressed: root.tryStartResize(Qt.BottomEdge) + } + + MouseArea { + visible: root.supported + width: 6 + height: 6 + anchors.left: parent.left + anchors.bottom: parent.bottom + cursorShape: Qt.SizeBDiagCursor + onPressed: root.tryStartResize(Qt.LeftEdge | Qt.BottomEdge) + } + + MouseArea { + visible: root.supported + width: 6 + height: 6 + anchors.right: parent.right + anchors.bottom: parent.bottom + cursorShape: Qt.SizeFDiagCursor + onPressed: root.tryStartResize(Qt.RightEdge | Qt.BottomEdge) + } +}