diff --git a/Commons/Settings.qml b/Commons/Settings.qml index ed6b8499..3f909c53 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -177,6 +177,9 @@ Singleton { MatugenService.init() + // Ensure wallpapers are restored after settings have been loaded + WallpaperService.init() + FontService.init() HooksService.init() diff --git a/Modules/IPC/IPCManager.qml b/Modules/IPC/IPCManager.qml index be88ff0c..ba644c75 100644 --- a/Modules/IPC/IPCManager.qml +++ b/Modules/IPC/IPCManager.qml @@ -127,6 +127,12 @@ Item { // Wallpaper IPC: trigger a new random wallpaper IpcHandler { target: "wallpaper" + function toggle() { + if (Settings.data.wallpaper.enabled) { + wallpaperSelector.toggle() + } + } + function random() { if (Settings.data.wallpaper.enabled) { WallpaperService.setRandomWallpaper() diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 887954d3..d04aea82 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -21,6 +21,8 @@ NPanel { panelKeyboardFocus: true + draggable: true + // Tabs enumeration, order is NOT relevant enum Tab { About, @@ -36,8 +38,7 @@ NPanel { Network, Notification, ScreenRecorder, - Wallpaper, - WallpaperSelector + Wallpaper } property int requestedTab: SettingsPanel.Tab.General @@ -45,13 +46,6 @@ NPanel { property var tabsModel: [] property var activeScrollView: null - Connections { - target: Settings.data.wallpaper - function onEnabledChanged() { - updateTabsModel() - } - } - Component.onCompleted: { updateTabsModel() } @@ -92,10 +86,7 @@ NPanel { id: wallpaperTab Tabs.WallpaperTab {} } - Component { - id: wallpaperSelectorTab - Tabs.WallpaperSelectorTab {} - } + Component { id: screenRecorderTab Tabs.ScreenRecorderTab {} @@ -174,35 +165,23 @@ NPanel { "label": "Wallpaper", "icon": "settings-wallpaper", "source": wallpaperTab + }, { + "id": SettingsPanel.Tab.ScreenRecorder, + "label": "Screen Recorder", + "icon": "settings-screen-recorder", + "source": screenRecorderTab + }, { + "id": SettingsPanel.Tab.Hooks, + "label": "Hooks", + "icon": "settings-hooks", + "source": hooksTab + }, { + "id": SettingsPanel.Tab.About, + "label": "About", + "icon": "settings-about", + "source": aboutTab }] - // Only add the Wallpaper Selector tab if the feature is enabled - if (Settings.data.wallpaper.enabled) { - newTabs.push({ - "id": SettingsPanel.Tab.WallpaperSelector, - "label": "Wallpaper Selector", - "icon": "settings-wallpaper-selector", - "source": wallpaperSelectorTab - }) - } - - newTabs.push({ - "id": SettingsPanel.Tab.ScreenRecorder, - "label": "Screen Recorder", - "icon": "settings-screen-recorder", - "source": screenRecorderTab - }, { - "id": SettingsPanel.Tab.Hooks, - "label": "Hooks", - "icon": "settings-hooks", - "source": hooksTab - }, { - "id": SettingsPanel.Tab.About, - "label": "About", - "icon": "settings-about", - "source": aboutTab - }) - root.tabsModel = newTabs // Assign the generated list to the model } // When the panel opens, choose the appropriate tab diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml deleted file mode 100644 index a6d340ac..00000000 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ /dev/null @@ -1,268 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Qt.labs.folderlistmodel -import qs.Commons -import qs.Services -import qs.Widgets - -ColumnLayout { - id: root - width: parent.width - spacing: Style.marginL * scaling - - property list wallpapersList: [] - property string currentWallpaper: "" - - Component.onCompleted: { - wallpapersList = screen ? WallpaperService.getWallpapersList(screen.name) : [] - currentWallpaper = screen ? WallpaperService.getWallpaper(screen.name) : "" - } - - Connections { - target: WallpaperService - function onWallpaperChanged(screenName, path) { - if (screenName === screen.name) { - currentWallpaper = WallpaperService.getWallpaper(screen.name) - } - } - function onWallpaperDirectoryChanged(screenName, directory) { - if (screenName === screen.name) { - wallpapersList = WallpaperService.getWallpapersList(screen.name) - currentWallpaper = WallpaperService.getWallpaper(screen.name) - } - } - function onWallpaperListChanged(screenName, count) { - if (screenName === screen.name) { - wallpapersList = WallpaperService.getWallpapersList(screen.name) - currentWallpaper = WallpaperService.getWallpaper(screen.name) - } - } - } - - // Current wallpaper display - NHeader { - label: "Current Wallpaper" - description: "Preview and manage your desktop background." - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 140 * scaling - radius: Style.radiusM * scaling - color: Color.transparent - - NImageRounded { - anchors.fill: parent - anchors.margins: Style.marginXS * scaling - imagePath: currentWallpaper - fallbackIcon: "image" - imageRadius: Style.radiusM * scaling - borderColor: Color.mSecondary - borderWidth: Style.borderL * 2 * scaling - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } - - // Wallpaper selector - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - Layout.fillWidth: true - - // Wallpaper grid - NHeader { - label: "Wallpaper Selector" - description: "Click on a wallpaper to set it as your current wallpaper." - } - } - - NIconButton { - icon: "refresh" - tooltipText: "Refresh wallpaper list" - onClicked: { - WallpaperService.refreshWallpapersList() - } - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } - } - - NToggle { - label: "Apply to all monitors" - description: "Apply selected wallpaper to all monitors at once." - checked: Settings.data.wallpaper.setWallpaperOnAllMonitors - onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked - visible: (wallpapersList.length > 0) - } - - // Wallpaper grid container - Item { - visible: !WallpaperService.scanning - Layout.fillWidth: true - Layout.preferredHeight: { - return Math.ceil(wallpapersList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight - } - - GridView { - id: wallpaperGridView - anchors.fill: parent - model: wallpapersList - - interactive: false - clip: true - - property int columns: 4 - property int itemSize: Math.floor((width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns) - - cellWidth: Math.floor((width - leftMargin - rightMargin) / columns) - cellHeight: Math.floor(itemSize * 0.67) + Style.marginS * scaling - - leftMargin: Style.marginS * scaling - rightMargin: Style.marginS * scaling - topMargin: Style.marginS * scaling - bottomMargin: Style.marginS * scaling - - delegate: Rectangle { - id: wallpaperItem - - property string wallpaperPath: modelData - property bool isSelected: screen ? (wallpaperPath === currentWallpaper) : false - - width: wallpaperGridView.itemSize - height: Math.round(wallpaperGridView.itemSize * 0.67) - color: Color.transparent - - // NImageCached relies on the image being visible to work properly. - // MultiEffect relies on the image being invisible to apply effects. - // That's why we don't have rounded corners here, as we don't want to bring back qt5compat. - NImageCached { - id: img - imagePath: wallpaperPath - anchors.fill: parent - } - - // Borders on top - Rectangle { - anchors.fill: parent - color: Color.transparent - border.color: isSelected ? Color.mSecondary : Color.mSurface - border.width: Math.max(1, Style.borderL * 1.5 * scaling) - } - - // Selection tick-mark - Rectangle { - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: Style.marginS * scaling - width: 28 * scaling - height: 28 * scaling - radius: width / 2 - color: Color.mSecondary - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: isSelected - - NIcon { - icon: "check" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSecondary - anchors.centerIn: parent - } - } - - // Hover effect - Rectangle { - anchors.fill: parent - color: Color.mSurface - opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3 - radius: parent.radius - - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - } - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - acceptedButtons: Qt.LeftButton - hoverEnabled: true - onPressed: { - if (Settings.data.wallpaper.setWallpaperOnAllMonitors) { - WallpaperService.changeWallpaper(wallpaperPath, undefined) - } else if (screen) { - WallpaperService.changeWallpaper(wallpaperPath, screen.name) - } - } - } - } - } - } - - // Empty state - Rectangle { - color: Color.mSurface - radius: Style.radiusM * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: wallpapersList.length === 0 || WallpaperService.scanning - Layout.fillWidth: true - Layout.preferredHeight: 130 * scaling - - ColumnLayout { - anchors.fill: parent - visible: WallpaperService.scanning - NBusyIndicator { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } - } - - ColumnLayout { - anchors.fill: parent - visible: wallpapersList.length === 0 && !WallpaperService.scanning - Item { - Layout.fillHeight: true - } - - NIcon { - icon: "folder-open" - font.pointSize: Style.fontSizeXXL * scaling - color: Color.mOnSurface - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "No wallpaper found." - color: Color.mOnSurface - font.weight: Style.fontWeightBold - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: "Make sure your wallpaper directory is configured and contains image files." - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.alignment: Qt.AlignHCenter - } - - Item { - Layout.fillHeight: true - } - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } -} diff --git a/Modules/SidePanel/Cards/UtilitiesCard.qml b/Modules/SidePanel/Cards/UtilitiesCard.qml index fd23adf0..bed94308 100644 --- a/Modules/SidePanel/Cards/UtilitiesCard.qml +++ b/Modules/SidePanel/Cards/UtilitiesCard.qml @@ -55,14 +55,8 @@ NBox { visible: Settings.data.wallpaper.enabled icon: "wallpaper-selector" tooltipText: "Left click: Open wallpaper selector.\nRight click: Set random wallpaper." - onClicked: { - var settingsPanel = PanelService.getPanel("settingsPanel") - settingsPanel.requestedTab = SettingsPanel.Tab.WallpaperSelector - settingsPanel.open() - } - onRightClicked: { - WallpaperService.setRandomWallpaper() - } + onClicked: PanelService.getPanel("wallpaperSelector")?.toggle(this) + onRightClicked: WallpaperService.setRandomWallpaper() } Item { diff --git a/Modules/WallpaperSelector/WallpaperSelector.qml b/Modules/WallpaperSelector/WallpaperSelector.qml new file mode 100644 index 00000000..cd855352 --- /dev/null +++ b/Modules/WallpaperSelector/WallpaperSelector.qml @@ -0,0 +1,353 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services +import qs.Widgets +import "../../Helpers/FuzzySort.js" as FuzzySort + +NPanel { + id: root + + preferredWidth: 640 + preferredHeight: 480 + preferredWidthRatio: 0.4 + preferredHeightRatio: 0.52 + panelAnchorHorizontalCenter: true + panelAnchorVerticalCenter: true + panelKeyboardFocus: true + draggable: true + + panelContent: Rectangle { + // Local reactive state + property list wallpapersList: [] + property string currentWallpaper: "" + property string filterText: "" + property list filteredWallpapers: [] + + Component.onCompleted: { + refreshWallpaperScreenData() + } + + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screen !== null && screenName === screen.name) { + currentWallpaper = WallpaperService.getWallpaper(screen.name) + } + } + function onWallpaperDirectoryChanged(screenName, directory) { + if (screen !== null && screenName === screen.name) { + refreshWallpaperScreenData() + } + } + function onWallpaperListChanged(screenName, count) { + if (screen !== null && screenName === screen.name) { + refreshWallpaperScreenData() + } + } + } + + function refreshWallpaperScreenData() { + if (screen === null) { + return + } + wallpapersList = WallpaperService.getWallpapersList(screen.name) + currentWallpaper = WallpaperService.getWallpaper(screen.name) + updateFiltered() + } + + function updateFiltered() { + if (!filterText || filterText.trim().length === 0) { + filteredWallpapers = wallpapersList + return + } + // Build objects with basename for ranking + const items = wallpapersList.map(function (p) { + return { + "path": p, + "name": p.split('/').pop() + } + }) + const results = FuzzySort.go(filterText.trim(), items, { + "key": 'name', + "limit": 200 + }) + // Map back to path list + filteredWallpapers = results.map(function (r) { + return r.obj.path + }) + } + + color: Color.transparent + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL * scaling + spacing: Style.marginM * scaling + + // Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM * scaling + + NIcon { + icon: "settings-wallpaper-selector" + font.pointSize: Style.fontSizeXXL * scaling + color: Color.mPrimary + } + + NText { + text: "Wallpaper Selector" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "refresh" + tooltipText: "Refresh wallpaper list." + baseSize: Style.baseWidgetSize * 0.8 + onClicked: WallpaperService.refreshWallpapersList() + } + + NIconButton { + icon: "close" + tooltipText: "Close." + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.close() + } + } + + NDivider { + Layout.fillWidth: true + } + + NToggle { + label: "Apply to all monitors" + description: "Apply selected wallpaper to all monitors at once." + checked: Settings.data.wallpaper.setWallpaperOnAllMonitors + onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked + visible: (wallpapersList.length > 0) + Layout.fillWidth: true + } + + // Filter input + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM * scaling + + NText { + text: "Search:" + color: Color.mOnSurface + font.pointSize: Style.fontSizeM * scaling + Layout.preferredWidth: implicitWidth + } + + NTextInput { + id: searchInput + placeholderText: "Type to filter wallpapers..." + text: filterText + onTextChanged: { + filterText = text + updateFiltered() + } + Layout.fillWidth: true + Component.onCompleted: { + if (searchInput.inputItem && searchInput.inputItem.visible) { + searchInput.inputItem.forceActiveFocus() + } + } + } + } + + // Scroll container for wallpaper grid only + Flickable { + Layout.fillWidth: true + Layout.fillHeight: true + pressDelay: 200 + + NScrollView { + id: scrollView + anchors.fill: parent + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + padding: Style.marginL * 0 * scaling + clip: true + + ColumnLayout { + width: scrollView.availableWidth + spacing: Style.marginM * scaling + + // Grid container + Item { + visible: !WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: Math.ceil(filteredWallpapers.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight + + GridView { + id: wallpaperGridView + anchors.fill: parent + model: filteredWallpapers + interactive: false + + property int columns: 4 + property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns) + + cellWidth: Math.floor((width - leftMargin - rightMargin) / columns) + cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS * scaling + Style.fontSizeXS * scaling + Style.marginM * scaling + + leftMargin: Style.marginS * scaling + rightMargin: Style.marginS * scaling + topMargin: Style.marginS * scaling + bottomMargin: Style.marginS * scaling + + delegate: ColumnLayout { + id: wallpaperItem + + property string wallpaperPath: modelData + property bool isSelected: (wallpaperPath === currentWallpaper) + property string filename: wallpaperPath.split('/').pop() + + width: wallpaperGridView.itemSize + spacing: Style.marginXS * scaling + + Rectangle { + id: imageContainer + Layout.fillWidth: true + Layout.preferredHeight: Math.round(wallpaperGridView.itemSize * 0.67) + color: Color.transparent + + NImageCached { + id: img + imagePath: wallpaperPath + anchors.fill: parent + } + + Rectangle { + anchors.fill: parent + color: Color.transparent + border.color: isSelected ? Color.mSecondary : Color.mSurface + border.width: Math.max(1, Style.borderL * 1.5 * scaling) + } + + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS * scaling + width: 28 * scaling + height: 28 * scaling + radius: width / 2 + color: Color.mSecondary + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: isSelected + + NIcon { + icon: "check" + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSecondary + anchors.centerIn: parent + } + } + + Rectangle { + anchors.fill: parent + color: Color.mSurface + opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3 + radius: parent.radius + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onPressed: { + if (Settings.data.wallpaper.setWallpaperOnAllMonitors) { + WallpaperService.changeWallpaper(wallpaperPath, undefined) + } else { + WallpaperService.changeWallpaper(wallpaperPath, Screen.name) + } + } + } + } + + NText { + text: filename + color: Color.mOnSurfaceVariant + opacity: 0.5 + font.pointSize: Style.fontSizeXS * scaling + Layout.fillWidth: true + Layout.leftMargin: Style.marginS * scaling + Layout.rightMargin: Style.marginS * scaling + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + } + } + } + + // Empty / scanning state + Rectangle { + color: Color.mSurface + radius: Style.radiusM * scaling + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: (filteredWallpapers.length === 0 && !WallpaperService.scanning) || WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: 130 * scaling + + ColumnLayout { + anchors.fill: parent + visible: WallpaperService.scanning + NBusyIndicator { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + } + + ColumnLayout { + anchors.fill: parent + visible: filteredWallpapers.length === 0 && !WallpaperService.scanning + Item { + Layout.fillHeight: true + } + NIcon { + icon: "folder-open" + font.pointSize: Style.fontSizeXXL * scaling + color: Color.mOnSurface + Layout.alignment: Qt.AlignHCenter + } + NText { + text: (filterText && filterText.length > 0) ? "No match found." : "No wallpaper found." + color: Color.mOnSurface + font.weight: Style.fontWeightBold + Layout.alignment: Qt.AlignHCenter + } + NText { + text: (filterText && filterText.length > 0) ? "Try a different search query." : "Configure your wallpaper directory with images." + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + } + Item { + Layout.fillHeight: true + } + } + } + } + } + } + } + } +} diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index 3d090c18..c2ebcd53 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -47,6 +47,11 @@ Singleton { // Generate colors using current wallpaper and settings function generateFromWallpaper() { + if (!Settings.isLoaded) { + Logger.log("Matugen", "Settings not loaded yet, skipping wallpaper color generation") + return + } + Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name) var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''") if (wp === "") { diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 3a0756e5..b09a7839 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -9,6 +9,20 @@ import qs.Commons Singleton { id: root + // Public init to rehydrate cache after Settings load + function init() { + // Rebuild cache from persisted settings + var monitors = Settings.data.wallpaper.monitors || [] + currentWallpapers = ({}) + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name && monitors[i].wallpaper) { + currentWallpapers[monitors[i].name] = monitors[i].wallpaper + // Notify listeners so Background updates immediately after settings load + root.wallpaperChanged(monitors[i].name, monitors[i].wallpaper) + } + } + } + Component.onCompleted: { Logger.log("Wallpaper", "Service started") diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 5b4b898a..158e86b4 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -16,6 +16,7 @@ Loader { property real preferredWidthRatio property real preferredHeightRatio property color panelBackgroundColor: Color.mSurface + property bool draggable: false property bool panelAnchorHorizontalCenter: false property bool panelAnchorVerticalCenter: false @@ -215,6 +216,11 @@ Loader { radius: Style.radiusL * scaling border.color: Color.mOutline border.width: Math.max(1, Style.borderS * scaling) + // Dragging support + property bool draggable: root.draggable + property bool isDragged: false + property real manualX: 0 + property real manualY: 0 width: { var w if (preferredWidthRatio !== undefined) { @@ -239,8 +245,8 @@ Loader { scale: root.scaleValue opacity: root.opacityValue - x: calculatedX - y: calculatedY + x: isDragged ? manualX : calculatedX + y: isDragged ? manualY : calculatedY // --------------------------------------------- // Does not account for corners are they are negligible and helps keep the code clean. @@ -373,6 +379,14 @@ Loader { root.opacityValue = 1.0 } + // Reset drag position when panel closes + Connections { + target: root + function onClosed() { + panelBackground.isDragged = false + } + } + // Prevent closing when clicking in the panel bg MouseArea { anchors.fill: parent @@ -398,6 +412,79 @@ Loader { anchors.fill: parent sourceComponent: root.panelContent } + + // Handle drag move on the whole panel area + DragHandler { + id: dragHandler + target: null + enabled: panelBackground.draggable + property real dragStartX: 0 + property real dragStartY: 0 + onActiveChanged: { + if (active) { + // Capture current position into manual coordinates BEFORE toggling isDragged + panelBackground.manualX = panelBackground.x + panelBackground.manualY = panelBackground.y + dragStartX = panelBackground.x + dragStartY = panelBackground.y + panelBackground.isDragged = true + if (root.enableBackgroundClick) + root.disableBackgroundClick() + } else { + // Keep isDragged true so we continue using the manual x/y after release + if (root.enableBackgroundClick) + root.enableBackgroundClick() + } + } + onTranslationChanged: { + // Proposed new coordinates from fixed drag origin + var nx = dragStartX + translation.x + var ny = dragStartY + translation.y + + // Calculate gaps so we never overlap the bar on any side + var baseGap = Style.marginS * scaling + var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL * scaling : 0 + var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL * scaling : 0 + + var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight * scaling + floatExtraH) : 0) + var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight * scaling + floatExtraH) : 0) + var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight * scaling + floatExtraV) : 0) + var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight * scaling + floatExtraV) : 0) + + // Clamp within screen bounds accounting for insets + var maxX = panelWindow.width - panelBackground.width - insetRight + var minX = insetLeft + var maxY = panelWindow.height - panelBackground.height - insetBottom + var minY = insetTop + + panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX))) + panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY))) + } + } + + // Drag indicator border + Rectangle { + anchors.fill: parent + anchors.margins: 0 + color: Color.transparent + border.color: Color.mPrimary + border.width: Math.max(2, Style.borderL * scaling) + radius: parent.radius + visible: panelBackground.isDragged && dragHandler.active + opacity: 0.8 + z: 3000 + + // Subtle glow effect + Rectangle { + anchors.fill: parent + anchors.margins: 0 + color: Color.transparent + border.color: Color.mPrimary + border.width: Math.max(1, Style.borderS * scaling) + radius: parent.radius + opacity: 0.3 + } + } } } } diff --git a/shell.qml b/shell.qml index 24544f5f..22f4f521 100644 --- a/shell.qml +++ b/shell.qml @@ -28,6 +28,7 @@ import qs.Modules.PowerPanel import qs.Modules.SidePanel import qs.Modules.Toast import qs.Modules.WiFiPanel +import qs.Modules.WallpaperSelector import qs.Services import qs.Widgets @@ -94,6 +95,11 @@ ShellRoot { objectName: "bluetoothPanel" } + WallpaperSelector { + id: wallpaperSelector + objectName: "wallpaperSelector" + } + Component.onCompleted: { // Save a ref. to our lockScreen so we can access it easily PanelService.lockScreen = lockScreen