From e1846dc275359e67fea5c9105baafc303fe6ca69 Mon Sep 17 00:00:00 2001 From: Lemmy Date: Sun, 14 Dec 2025 13:20:37 -0500 Subject: [PATCH] DesktopWidgets: Simplified dragging --- Modules/DesktopWidgets/DesktopClock.qml | 136 +-------- Modules/DesktopWidgets/DesktopMediaPlayer.qml | 282 +++++------------- Modules/DesktopWidgets/DesktopWeather.qml | 138 +-------- Modules/DesktopWidgets/DesktopWidgets.qml | 2 +- .../DesktopWidgets/DraggableDesktopWidget.qml | 167 +++++++++++ 5 files changed, 245 insertions(+), 480 deletions(-) create mode 100644 Modules/DesktopWidgets/DraggableDesktopWidget.qml diff --git a/Modules/DesktopWidgets/DesktopClock.qml b/Modules/DesktopWidgets/DesktopClock.qml index f00e61012..dfc445295 100644 --- a/Modules/DesktopWidgets/DesktopClock.qml +++ b/Modules/DesktopWidgets/DesktopClock.qml @@ -1,20 +1,15 @@ import QtQuick -import QtQuick.Effects import QtQuick.Layouts import Quickshell import qs.Commons import qs.Widgets -Item { +DraggableDesktopWidget { id: root - property ShellScreen screen - property var widgetData: null - property int widgetIndex: -1 - readonly property var now: Time.now - property color textColor: { + property color clockTextColor: { var txtColor = widgetData && widgetData.textColor ? widgetData.textColor : ""; return (txtColor && txtColor !== "") ? txtColor : Color.mOnSurface; } @@ -26,145 +21,26 @@ Item { property bool showSeconds: (widgetData && widgetData.showSeconds !== undefined) ? widgetData.showSeconds : true property bool showDate: (widgetData && widgetData.showDate !== undefined) ? widgetData.showDate : true - property bool isDragging: false - property real dragOffsetX: 0 - property real dragOffsetY: 0 - property real baseX: (widgetData && widgetData.x !== undefined) ? widgetData.x : 100 - property real baseY: (widgetData && widgetData.y !== undefined) ? widgetData.y : 100 + textColor: clockTextColor implicitWidth: contentLayout.implicitWidth + Style.marginXL * 2 implicitHeight: contentLayout.implicitHeight + Style.marginXL * 2 width: implicitWidth height: implicitHeight - x: isDragging ? dragOffsetX : baseX - y: isDragging ? dragOffsetY : baseY - - // Update base position from widgetData when not dragging - onWidgetDataChanged: { - if (!isDragging) { - baseX = (widgetData && widgetData.x !== undefined) ? widgetData.x : 100; - baseY = (widgetData && widgetData.y !== undefined) ? widgetData.y : 100; - } - } - MouseArea { - id: dragArea - anchors.fill: parent - z: 1000 - enabled: Settings.data.desktopWidgets.editMode - cursorShape: enabled && isDragging ? Qt.ClosedHandCursor : (enabled ? Qt.OpenHandCursor : Qt.ArrowCursor) - hoverEnabled: true - acceptedButtons: Qt.LeftButton - - property point pressPos: Qt.point(0, 0) - - onPressed: mouse => { - pressPos = Qt.point(mouse.x, mouse.y); - dragOffsetX = root.x; - dragOffsetY = root.y; - isDragging = true; - // Update base position to current position when starting drag - baseX = root.x; - baseY = root.y; - } - - onPositionChanged: mouse => { - if (isDragging && pressed) { - var globalPos = mapToItem(root.parent, mouse.x, mouse.y); - var newX = globalPos.x - pressPos.x; - var newY = globalPos.y - pressPos.y; - - if (root.parent && root.width > 0 && root.height > 0) { - newX = Math.max(0, Math.min(newX, root.parent.width - root.width)); - newY = Math.max(0, Math.min(newY, root.parent.height - root.height)); - } - - if (root.parent && root.parent.checkCollision && root.parent.checkCollision(root, newX, newY)) { - return; - } - - dragOffsetX = newX; - dragOffsetY = newY; - } - } - - onReleased: mouse => { - if (isDragging && widgetIndex >= 0 && screen && screen.name) { - var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; - var newMonitorWidgets = monitorWidgets.slice(); - for (var i = 0; i < newMonitorWidgets.length; i++) { - if (newMonitorWidgets[i].name === screen.name) { - var widgets = (newMonitorWidgets[i].widgets || []).slice(); - if (widgetIndex < widgets.length) { - widgets[widgetIndex] = Object.assign({}, widgets[widgetIndex], { - "x": dragOffsetX, - "y": dragOffsetY - }); - newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { - "widgets": widgets - }); - Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; - } - break; - } - } - // Update base position to final position - baseX = dragOffsetX; - baseY = dragOffsetY; - isDragging = false; - } - } - - onCanceled: { - isDragging = false; - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Style.marginS - color: Settings.data.desktopWidgets.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent" - border.color: (Settings.data.desktopWidgets.editMode || isDragging) ? (isDragging ? Qt.rgba(textColor.r, textColor.g, textColor.b, 0.5) : Color.mPrimary) : "transparent" - border.width: Settings.data.desktopWidgets.editMode ? 3 : (isDragging ? 2 : 0) - radius: Style.radiusL + Style.marginS - z: -1 - } - - Rectangle { - id: container - anchors.fill: parent - radius: Style.radiusL - color: Color.mSurface - border { - width: 1 - color: Qt.alpha(Color.mOutline, 0.12) - } - clip: true - visible: (widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true - - layer.enabled: Settings.data.general.enableShadows && !root.isDragging && ((widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true) - layer.effect: MultiEffect { - shadowEnabled: true - shadowBlur: Style.shadowBlur * 1.5 - shadowOpacity: Style.shadowOpacity * 0.6 - shadowColor: Color.black - shadowHorizontalOffset: Settings.data.general.shadowOffsetX - shadowVerticalOffset: Settings.data.general.shadowOffsetY - blurMax: Style.shadowBlurMax - } - } - ColumnLayout { id: contentLayout anchors.centerIn: parent spacing: Style.marginL + z: 2 + NClock { id: clockDisplay Layout.alignment: Qt.AlignHCenter now: root.now clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital" backgroundColor: Color.transparent - clockColor: textColor + clockColor: clockTextColor progressColor: Color.mPrimary opacity: root.widgetOpacity height: Math.round(fontSize * 1.9) diff --git a/Modules/DesktopWidgets/DesktopMediaPlayer.qml b/Modules/DesktopWidgets/DesktopMediaPlayer.qml index b62c6dfa3..d1235b78b 100644 --- a/Modules/DesktopWidgets/DesktopMediaPlayer.qml +++ b/Modules/DesktopWidgets/DesktopMediaPlayer.qml @@ -7,18 +7,10 @@ import qs.Services.Media import qs.Widgets import qs.Widgets.AudioSpectrum -Item { +DraggableDesktopWidget { id: root - property ShellScreen screen - property var widgetData: null - property int widgetIndex: -1 - - property bool isDragging: false - property real dragOffsetX: 0 - property real dragOffsetY: 0 - property real baseX: (widgetData && widgetData.x !== undefined) ? widgetData.x : 100 - property real baseY: (widgetData && widgetData.y !== undefined) ? widgetData.y : 200 + defaultY: 200 readonly property bool showPrev: hasPlayer && MediaService.canGoPrevious readonly property bool showNext: hasPlayer && MediaService.canGoNext @@ -33,220 +25,84 @@ Item { width: implicitWidth height: implicitHeight - x: isDragging ? dragOffsetX : baseX - y: isDragging ? dragOffsetY : baseY - - // Update base position from widgetData when not dragging - onWidgetDataChanged: { - if (!isDragging) { - baseX = (widgetData && widgetData.x !== undefined) ? widgetData.x : 100; - baseY = (widgetData && widgetData.y !== undefined) ? widgetData.y : 200; - } - } - readonly property bool hasPlayer: MediaService.currentPlayer !== null readonly property bool isPlaying: MediaService.isPlaying - property color textColor: Color.mOnSurface - Rectangle { + // Visualizer overlay (needs to be inside container bounds with masking) + Item { anchors.fill: parent - anchors.margins: -Style.marginS - color: Settings.data.desktopWidgets.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent" - border.color: (Settings.data.desktopWidgets.editMode || isDragging) ? (isDragging ? Qt.rgba(textColor.r, textColor.g, textColor.b, 0.5) : Color.mPrimary) : "transparent" - border.width: Settings.data.desktopWidgets.editMode ? 3 : (isDragging ? 2 : 0) - radius: Style.radiusL + Style.marginS - z: -1 - } - - // Material 3 styled container with elevation - Rectangle { - id: container - anchors.fill: parent - radius: Style.radiusL - color: Color.mSurface - border { - width: 1 - color: Qt.alpha(Color.mOutline, 0.12) - } + anchors.margins: Style.marginXS + z: 0 clip: true - visible: (widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true - - Item { - anchors.fill: parent - anchors.margins: Style.marginXS - z: 0 - clip: true - layer.enabled: true - layer.smooth: true - layer.samples: 4 - layer.effect: MultiEffect { - maskEnabled: true - maskThresholdMin: 0.95 - maskSpreadAtMin: 0.0 - maskSource: ShaderEffectSource { - sourceItem: Rectangle { - width: container.width - Style.marginXS * 2 - height: container.height - Style.marginXS * 2 - radius: Math.max(0, Style.radiusL - Style.marginXS) - color: "white" - antialiasing: true - smooth: true - } - smooth: true - mipmap: true - } - } - - Loader { - anchors.fill: parent - active: (widgetData && widgetData.visualizerType) && widgetData.visualizerType !== "" && widgetData.visualizerType !== "none" - - sourceComponent: { - var visualizerType = (widgetData && widgetData.visualizerType) ? widgetData.visualizerType : ""; - switch (visualizerType) { - case "linear": - return linearComponent; - case "mirrored": - return mirroredComponent; - case "wave": - return waveComponent; - default: - return null; - } - } - - Component { - id: linearComponent - NLinearSpectrum { - anchors.fill: parent - values: CavaService.values - fillColor: Color.mPrimary - opacity: 0.6 - } - } - - Component { - id: mirroredComponent - NMirroredSpectrum { - anchors.fill: parent - values: CavaService.values - fillColor: Color.mPrimary - opacity: 0.6 - } - } - - Component { - id: waveComponent - NWaveSpectrum { - anchors.fill: parent - values: CavaService.values - fillColor: Color.mPrimary - opacity: 0.6 - } - } - } - } - - layer.enabled: Settings.data.general.enableShadows && !root.isDragging && ((widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true) + visible: root.showBackground + layer.enabled: true + layer.smooth: true + layer.samples: 4 layer.effect: MultiEffect { - shadowEnabled: true - shadowBlur: Style.shadowBlur * 1.5 - shadowOpacity: Style.shadowOpacity * 0.6 - shadowColor: Color.black - shadowHorizontalOffset: Settings.data.general.shadowOffsetX - shadowVerticalOffset: Settings.data.general.shadowOffsetY - blurMax: Style.shadowBlurMax + maskEnabled: true + maskThresholdMin: 0.95 + maskSpreadAtMin: 0.0 + maskSource: ShaderEffectSource { + sourceItem: Rectangle { + width: root.width - Style.marginXS * 2 + height: root.height - Style.marginXS * 2 + radius: Math.max(0, Style.radiusL - Style.marginXS) + color: "white" + antialiasing: true + smooth: true + } + smooth: true + mipmap: true + } } - } - MouseArea { - id: dragArea - anchors.fill: parent - z: 1 - enabled: Settings.data.desktopWidgets.editMode - cursorShape: enabled && isDragging ? Qt.ClosedHandCursor : (enabled ? Qt.OpenHandCursor : Qt.ArrowCursor) - hoverEnabled: true - acceptedButtons: Qt.LeftButton - propagateComposedEvents: true + Loader { + anchors.fill: parent + active: (widgetData && widgetData.visualizerType) && widgetData.visualizerType !== "" && widgetData.visualizerType !== "none" - property point pressPos: Qt.point(0, 0) - property bool isDraggingWidget: false + sourceComponent: { + var visualizerType = (widgetData && widgetData.visualizerType) ? widgetData.visualizerType : ""; + switch (visualizerType) { + case "linear": + return linearComponent; + case "mirrored": + return mirroredComponent; + case "wave": + return waveComponent; + default: + return null; + } + } - onPressed: mouse => { - // Don't start drag if clicking on control buttons - var clickX = mouse.x; - var clickY = mouse.y; + Component { + id: linearComponent + NLinearSpectrum { + anchors.fill: parent + values: CavaService.values + fillColor: Color.mPrimary + opacity: 0.6 + } + } - var buttonArea = controlsRow.mapToItem(root, 0, 0); - var buttonWidth = controlsRow.width; - var buttonHeight = controlsRow.height; + Component { + id: mirroredComponent + NMirroredSpectrum { + anchors.fill: parent + values: CavaService.values + fillColor: Color.mPrimary + opacity: 0.6 + } + } - if (clickX >= buttonArea.x && clickX <= buttonArea.x + buttonWidth && clickY >= buttonArea.y && clickY <= buttonArea.y + buttonHeight) { - mouse.accepted = false; - return; - } - - pressPos = Qt.point(mouse.x, mouse.y); - dragOffsetX = root.x; - dragOffsetY = root.y; - isDragging = true; - isDraggingWidget = true; - // Update base position to current position when starting drag - baseX = root.x; - baseY = root.y; - } - - onPositionChanged: mouse => { - if (isDragging && isDraggingWidget && pressed) { - var globalPos = mapToItem(root.parent, mouse.x, mouse.y); - var newX = globalPos.x - pressPos.x; - var newY = globalPos.y - pressPos.y; - - if (root.parent && root.width > 0 && root.height > 0) { - newX = Math.max(0, Math.min(newX, root.parent.width - root.width)); - newY = Math.max(0, Math.min(newY, root.parent.height - root.height)); - } - - if (root.parent && root.parent.checkCollision && root.parent.checkCollision(root, newX, newY)) { - return; - } - - dragOffsetX = newX; - dragOffsetY = newY; - } - } - - onReleased: mouse => { - if (isDragging && widgetIndex >= 0 && screen && screen.name) { - var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; - var newMonitorWidgets = monitorWidgets.slice(); - for (var i = 0; i < newMonitorWidgets.length; i++) { - if (newMonitorWidgets[i].name === screen.name) { - var widgets = (newMonitorWidgets[i].widgets || []).slice(); - if (widgetIndex < widgets.length) { - widgets[widgetIndex] = Object.assign({}, widgets[widgetIndex], { - "x": dragOffsetX, - "y": dragOffsetY - }); - newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { - "widgets": widgets - }); - Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; - } - break; - } - } - // Update base position to final position - baseX = dragOffsetX; - baseY = dragOffsetY; - isDragging = false; - isDraggingWidget = false; - } - } - - onCanceled: { - isDragging = false; - isDraggingWidget = false; + Component { + id: waveComponent + NWaveSpectrum { + anchors.fill: parent + values: CavaService.values + fillColor: Color.mPrimary + opacity: 0.6 + } + } } } @@ -255,7 +111,7 @@ Item { anchors.fill: parent anchors.margins: Style.marginM spacing: Style.marginS - z: 1 + z: 2 Item { Layout.preferredWidth: 64 * Style.uiScaleRatio diff --git a/Modules/DesktopWidgets/DesktopWeather.qml b/Modules/DesktopWidgets/DesktopWeather.qml index 851e9cfe4..857502d0b 100644 --- a/Modules/DesktopWidgets/DesktopWeather.qml +++ b/Modules/DesktopWidgets/DesktopWeather.qml @@ -1,24 +1,13 @@ import QtQuick -import QtQuick.Effects import QtQuick.Layouts import Quickshell import qs.Commons import qs.Services.Location import qs.Widgets -Item { +DraggableDesktopWidget { id: root - property ShellScreen screen - property var widgetData: null - property int widgetIndex: -1 - - property bool isDragging: false - property real dragOffsetX: 0 - property real dragOffsetY: 0 - property real baseX: (widgetData && widgetData.x !== undefined) ? widgetData.x : 100 - property real baseY: (widgetData && widgetData.y !== undefined) ? widgetData.y : 100 - readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0 readonly property real currentTemp: { @@ -59,135 +48,12 @@ Item { width: implicitWidth height: implicitHeight - x: isDragging ? dragOffsetX : baseX - y: isDragging ? dragOffsetY : baseY - - // Update base position from widgetData when not dragging - onWidgetDataChanged: { - if (!isDragging) { - baseX = (widgetData && widgetData.x !== undefined) ? widgetData.x : 100; - baseY = (widgetData && widgetData.y !== undefined) ? widgetData.y : 100; - } - } - - property color textColor: Color.mOnSurface - Rectangle { - anchors.fill: parent - anchors.margins: -Style.marginS - color: Settings.data.desktopWidgets.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent" - border.color: (Settings.data.desktopWidgets.editMode || isDragging) ? (isDragging ? Qt.rgba(textColor.r, textColor.g, textColor.b, 0.5) : Color.mPrimary) : "transparent" - border.width: Settings.data.desktopWidgets.editMode ? 3 : (isDragging ? 2 : 0) - radius: Style.radiusL + Style.marginS - z: -1 - } - - Rectangle { - id: container - anchors.fill: parent - radius: Style.radiusL - color: Color.mSurface - border { - width: 1 - color: Qt.alpha(Color.mOutline, 0.12) - } - clip: true - visible: (widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true - - layer.enabled: Settings.data.general.enableShadows && !root.isDragging && ((widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true) - layer.effect: MultiEffect { - shadowEnabled: true - shadowBlur: Style.shadowBlur * 1.5 - shadowOpacity: Style.shadowOpacity * 0.6 - shadowColor: Color.black - shadowHorizontalOffset: Settings.data.general.shadowOffsetX - shadowVerticalOffset: Settings.data.general.shadowOffsetY - blurMax: Style.shadowBlurMax - } - } - - MouseArea { - id: dragArea - anchors.fill: parent - z: 1 - enabled: Settings.data.desktopWidgets.editMode - cursorShape: enabled && isDragging ? Qt.ClosedHandCursor : (enabled ? Qt.OpenHandCursor : Qt.ArrowCursor) - hoverEnabled: true - acceptedButtons: Qt.LeftButton - propagateComposedEvents: true - - property point pressPos: Qt.point(0, 0) - property bool isDraggingWidget: false - - onPressed: mouse => { - pressPos = Qt.point(mouse.x, mouse.y); - dragOffsetX = root.x; - dragOffsetY = root.y; - isDragging = true; - isDraggingWidget = true; - // Update base position to current position when starting drag - baseX = root.x; - baseY = root.y; - } - - onPositionChanged: mouse => { - if (isDragging && isDraggingWidget && pressed) { - var globalPos = mapToItem(root.parent, mouse.x, mouse.y); - var newX = globalPos.x - pressPos.x; - var newY = globalPos.y - pressPos.y; - - if (root.parent && root.width > 0 && root.height > 0) { - newX = Math.max(0, Math.min(newX, root.parent.width - root.width)); - newY = Math.max(0, Math.min(newY, root.parent.height - root.height)); - } - - if (root.parent && root.parent.checkCollision && root.parent.checkCollision(root, newX, newY)) { - return; - } - - dragOffsetX = newX; - dragOffsetY = newY; - } - } - - onReleased: mouse => { - if (isDragging && widgetIndex >= 0 && screen && screen.name) { - var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; - var newMonitorWidgets = monitorWidgets.slice(); - for (var i = 0; i < newMonitorWidgets.length; i++) { - if (newMonitorWidgets[i].name === screen.name) { - var widgets = (newMonitorWidgets[i].widgets || []).slice(); - if (widgetIndex < widgets.length) { - widgets[widgetIndex] = Object.assign({}, widgets[widgetIndex], { - "x": dragOffsetX, - "y": dragOffsetY - }); - newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { - "widgets": widgets - }); - Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; - } - break; - } - } - // Update base position to final position - baseX = dragOffsetX; - baseY = dragOffsetY; - isDragging = false; - isDraggingWidget = false; - } - } - - onCanceled: { - isDragging = false; - isDraggingWidget = false; - } - } - RowLayout { id: contentLayout anchors.fill: parent anchors.margins: Style.marginM spacing: Style.marginM + z: 2 Item { Layout.preferredWidth: 64 * Style.uiScaleRatio diff --git a/Modules/DesktopWidgets/DesktopWidgets.qml b/Modules/DesktopWidgets/DesktopWidgets.qml index 66f0c9f98..ec87cb72b 100644 --- a/Modules/DesktopWidgets/DesktopWidgets.qml +++ b/Modules/DesktopWidgets/DesktopWidgets.qml @@ -50,7 +50,7 @@ Variants { } Component.onCompleted: { - Logger.d("DesktopWidgets", "Create panel window for", screen?.name); + Logger.d("DesktopWidgets", "Created panel window for", screen?.name); } Item { diff --git a/Modules/DesktopWidgets/DraggableDesktopWidget.qml b/Modules/DesktopWidgets/DraggableDesktopWidget.qml new file mode 100644 index 000000000..57e2a161c --- /dev/null +++ b/Modules/DesktopWidgets/DraggableDesktopWidget.qml @@ -0,0 +1,167 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import qs.Commons + +Item { + id: root + + // Required properties from parent (set by DesktopWidgets.qml loader) + property ShellScreen screen + property var widgetData: null + property int widgetIndex: -1 + + // Optional customization + property real defaultX: 100 + property real defaultY: 100 + property color textColor: Color.mOnSurface + + // Content slot - allows natural QML child syntax + default property alias content: contentContainer.data + + // Exposed state for child content (e.g., to disable shadow during drag) + readonly property bool isDragging: internal.isDragging + + // Whether to show the background container + property bool showBackground: (widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true + + // Internal dragging state + QtObject { + id: internal + property bool isDragging: false + property real dragOffsetX: 0 + property real dragOffsetY: 0 + property real baseX: (root.widgetData && root.widgetData.x !== undefined) ? root.widgetData.x : root.defaultX + property real baseY: (root.widgetData && root.widgetData.y !== undefined) ? root.widgetData.y : root.defaultY + } + + x: internal.isDragging ? internal.dragOffsetX : internal.baseX + y: internal.isDragging ? internal.dragOffsetY : internal.baseY + + // Update base position from widgetData when not dragging + onWidgetDataChanged: { + if (!internal.isDragging) { + internal.baseX = (widgetData && widgetData.x !== undefined) ? widgetData.x : defaultX; + internal.baseY = (widgetData && widgetData.y !== undefined) ? widgetData.y : defaultY; + } + } + + // Edit mode decoration rectangle + Rectangle { + anchors.fill: parent + anchors.margins: -Style.marginS + color: Settings.data.desktopWidgets.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent" + border.color: (Settings.data.desktopWidgets.editMode || internal.isDragging) ? (internal.isDragging ? Qt.rgba(textColor.r, textColor.g, textColor.b, 0.5) : Color.mPrimary) : "transparent" + border.width: Settings.data.desktopWidgets.editMode ? 3 : (internal.isDragging ? 2 : 0) + radius: Style.radiusL + Style.marginS + z: -1 + } + + // Container with shadow + Rectangle { + id: container + anchors.fill: parent + radius: Style.radiusL + color: Color.mSurface + border { + width: 1 + color: Qt.alpha(Color.mOutline, 0.12) + } + clip: true + visible: root.showBackground + + layer.enabled: Settings.data.general.enableShadows && !internal.isDragging && root.showBackground + layer.effect: MultiEffect { + shadowEnabled: true + shadowBlur: Style.shadowBlur * 1.5 + shadowOpacity: Style.shadowOpacity * 0.6 + shadowColor: Color.black + shadowHorizontalOffset: Settings.data.general.shadowOffsetX + shadowVerticalOffset: Settings.data.general.shadowOffsetY + blurMax: Style.shadowBlurMax + } + } + + // Content slot + Item { + id: contentContainer + anchors.fill: parent + z: 1 + } + + // Drag MouseArea - blocks all interaction in edit mode + MouseArea { + id: dragArea + anchors.fill: parent + z: 1000 + visible: Settings.data.desktopWidgets.editMode + cursorShape: internal.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor + hoverEnabled: true + acceptedButtons: Qt.AllButtons + + property point pressPos: Qt.point(0, 0) + + onPressed: mouse => { + pressPos = Qt.point(mouse.x, mouse.y); + internal.dragOffsetX = root.x; + internal.dragOffsetY = root.y; + internal.isDragging = true; + // Update base position to current position when starting drag + internal.baseX = root.x; + internal.baseY = root.y; + } + + onPositionChanged: mouse => { + if (internal.isDragging && pressed) { + var globalPos = mapToItem(root.parent, mouse.x, mouse.y); + var newX = globalPos.x - pressPos.x; + var newY = globalPos.y - pressPos.y; + + // Boundary clamping + if (root.parent && root.width > 0 && root.height > 0) { + newX = Math.max(0, Math.min(newX, root.parent.width - root.width)); + newY = Math.max(0, Math.min(newY, root.parent.height - root.height)); + } + + // Collision detection (if parent provides checkCollision function) + if (root.parent && root.parent.checkCollision && root.parent.checkCollision(root, newX, newY)) { + return; + } + + internal.dragOffsetX = newX; + internal.dragOffsetY = newY; + } + } + + onReleased: mouse => { + if (internal.isDragging && widgetIndex >= 0 && screen && screen.name) { + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newMonitorWidgets = monitorWidgets.slice(); + for (var i = 0; i < newMonitorWidgets.length; i++) { + if (newMonitorWidgets[i].name === screen.name) { + var widgets = (newMonitorWidgets[i].widgets || []).slice(); + if (widgetIndex < widgets.length) { + widgets[widgetIndex] = Object.assign({}, widgets[widgetIndex], { + "x": internal.dragOffsetX, + "y": internal.dragOffsetY + }); + newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], { + "widgets": widgets + }); + Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets; + } + break; + } + } + // Update base position to final position + internal.baseX = internal.dragOffsetX; + internal.baseY = internal.dragOffsetY; + internal.isDragging = false; + } + } + + onCanceled: { + internal.isDragging = false; + } + } +}