From 0a4317f712f37805fd49976ca6c91db041b6c65b Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 10 Sep 2025 09:10:47 -0400 Subject: [PATCH] More drag and drop fixes --- .../SettingsPanel/Bar/BarSectionEditor.qml | 374 +++++++++++------- Modules/SettingsPanel/Tabs/BarTab.qml | 22 ++ Services/ColorSchemeService.qml | 2 +- 3 files changed, 249 insertions(+), 149 deletions(-) diff --git a/Modules/SettingsPanel/Bar/BarSectionEditor.qml b/Modules/SettingsPanel/Bar/BarSectionEditor.qml index 7a1684a..6eb745d 100644 --- a/Modules/SettingsPanel/Bar/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Bar/BarSectionEditor.qml @@ -18,6 +18,8 @@ NBox { signal removeWidget(string section, int index) signal reorderWidget(string section, int fromIndex, int toIndex) signal updateWidgetSettings(string section, int index, var settings) + signal dragStarted + signal dragEnded color: Color.mSurface Layout.fillWidth: true @@ -105,13 +107,11 @@ NBox { } // Drag and Drop Widget Area - // Replace your Flow section with this: - - // Drag and Drop Widget Area - use Item container Item { Layout.fillWidth: true Layout.fillHeight: true Layout.minimumHeight: 65 * scaling + clip: false // Don't clip children so ghost can move freely Flow { id: widgetFlow @@ -139,13 +139,18 @@ NBox { readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id) // Visual feedback during drag - states: State { - when: flowDragArea.draggedIndex === index - PropertyChanges { - target: widgetItem - scale: 1.1 - opacity: 0.9 - z: 1000 + opacity: flowDragArea.draggedIndex === index ? 0.5 : 1.0 + scale: flowDragArea.draggedIndex === index ? 0.95 : 1.0 + z: flowDragArea.draggedIndex === index ? 1000 : 0 + + Behavior on opacity { + NumberAnimation { + duration: 150 + } + } + Behavior on scale { + NumberAnimation { + duration: 150 } } @@ -227,31 +232,184 @@ NBox { } } - // MouseArea outside Flow, covering the same area + // Ghost/Clone widget for dragging + Rectangle { + id: dragGhost + width: 0 + height: Style.baseWidgetSize * 1.15 * scaling + radius: Style.radiusL * scaling + color: "transparent" + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + opacity: 0.7 + visible: flowDragArea.dragStarted + z: 2000 + clip: false // Ensure ghost isn't clipped + + Text { + id: ghostText + anchors.centerIn: parent + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnPrimary + } + } + + // Drop indicator - visual feedback for where the widget will be inserted + Rectangle { + id: dropIndicator + width: 3 * scaling + height: Style.baseWidgetSize * 1.15 * scaling + radius: width / 2 + color: Color.mPrimary + opacity: 0 + visible: opacity > 0 + z: 1999 + + SequentialAnimation on opacity { + id: pulseAnimation + running: false + loops: Animation.Infinite + NumberAnimation { + to: 1 + duration: 400 + easing.type: Easing.InOutQuad + } + NumberAnimation { + to: 0.6 + duration: 400 + easing.type: Easing.InOutQuad + } + } + + Behavior on x { + NumberAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } + Behavior on y { + NumberAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } + } + + // MouseArea for drag and drop MouseArea { id: flowDragArea anchors.fill: parent - z: -1 // Ensure this mouse area is below the Settings and Close buttons + z: -1 - // Critical properties for proper event handling acceptedButtons: Qt.LeftButton - preventStealing: false // Prevent child items from stealing events - propagateComposedEvents: draggedIndex != -1 // Don't propagate to children during drag - hoverEnabled: draggedIndex != -1 + preventStealing: false + propagateComposedEvents: !dragStarted + hoverEnabled: true // Always track mouse for drag operations property point startPos: Qt.point(0, 0) property bool dragStarted: false property int draggedIndex: -1 property real dragThreshold: 15 * scaling property Item draggedWidget: null - property point clickOffsetInWidget: Qt.point(0, 0) - property point originalWidgetPos: Qt.point(0, 0) // ADD THIS: Store original position + property int dropTargetIndex: -1 + property var draggedModelData: null + + // Drop position calculation + function updateDropIndicator(mouseX, mouseY) { + if (!dragStarted || draggedIndex === -1) { + dropIndicator.opacity = 0 + pulseAnimation.running = false + return + } + + let bestIndex = -1 + let bestPosition = null + let minDistance = Infinity + + // Check position relative to each widget + for (var i = 0; i < widgetModel.length; i++) { + if (i === draggedIndex) + continue + + const widget = widgetFlow.children[i] + if (!widget || widget.widgetIndex === undefined) + continue + + // Check distance to left edge (insert before) + const leftDist = Math.sqrt(Math.pow(mouseX - widget.x, + 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2)) + + // Check distance to right edge (insert after) + const rightDist = Math.sqrt(Math.pow(mouseX - (widget.x + widget.width), + 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2)) + + if (leftDist < minDistance) { + minDistance = leftDist + bestIndex = i + bestPosition = Qt.point(widget.x - dropIndicator.width / 2 - Style.marginXS * scaling, widget.y) + } + + if (rightDist < minDistance) { + minDistance = rightDist + bestIndex = i + 1 + bestPosition = Qt.point(widget.x + widget.width + Style.marginXS * scaling - dropIndicator.width / 2, + widget.y) + } + } + + // Check if we should insert at position 0 (very beginning) + if (widgetModel.length > 0 && draggedIndex !== 0) { + const firstWidget = widgetFlow.children[0] + if (firstWidget) { + const dist = Math.sqrt(Math.pow(mouseX, 2) + Math.pow(mouseY - firstWidget.y, 2)) + if (dist < minDistance && mouseX < firstWidget.x + firstWidget.width / 2) { + minDistance = dist + bestIndex = 0 + bestPosition = Qt.point(Math.max(0, firstWidget.x - dropIndicator.width - Style.marginS * scaling), + firstWidget.y) + } + } + } + + // Only show indicator if we're close enough and it's a different position + if (minDistance < 80 * scaling && bestIndex !== -1) { + // Adjust index if we're moving forward + let adjustedIndex = bestIndex + if (bestIndex > draggedIndex) { + adjustedIndex = bestIndex - 1 + } + + // Don't show if it's the same position + if (adjustedIndex === draggedIndex) { + dropIndicator.opacity = 0 + pulseAnimation.running = false + dropTargetIndex = -1 + return + } + + dropTargetIndex = adjustedIndex + if (bestPosition) { + dropIndicator.x = bestPosition.x + dropIndicator.y = bestPosition.y + dropIndicator.opacity = 1 + if (!pulseAnimation.running) { + pulseAnimation.running = true + } + } + } else { + dropIndicator.opacity = 0 + pulseAnimation.running = false + dropTargetIndex = -1 + } + } onPressed: mouse => { startPos = Qt.point(mouse.x, mouse.y) dragStarted = false draggedIndex = -1 draggedWidget = null + dropTargetIndex = -1 + draggedModelData = null // Find which widget was clicked for (var i = 0; i < widgetModel.length; i++) { @@ -266,20 +424,10 @@ NBox { if (localX < buttonsStartX) { draggedIndex = widget.widgetIndex draggedWidget = widget - - // Calculate and store where within the widget the user clicked - const clickOffsetX = mouse.x - widget.x - const clickOffsetY = mouse.y - widget.y - clickOffsetInWidget = Qt.point(clickOffsetX, clickOffsetY) - - // STORE ORIGINAL POSITION - originalWidgetPos = Qt.point(widget.x, widget.y) - - // Immediately set prevent stealing to true when drag candidate is found + draggedModelData = widget.modelData preventStealing = true break } else { - // Click was on buttons - allow event propagation mouse.accepted = false return } @@ -296,147 +444,77 @@ NBox { if (!dragStarted && distance > dragThreshold) { dragStarted = true - //Logger.log("BarSectionEditor", "Drag started") - // Enable visual feedback + // Emit signal when drag starts + root.dragStarted() + + // Setup ghost widget if (draggedWidget) { - draggedWidget.z = 1000 + dragGhost.width = draggedWidget.width + dragGhost.color = root.getWidgetColor(draggedModelData) + ghostText.text = draggedModelData.id } } - if (dragStarted && draggedWidget) { - // Adjust position to account for where within the widget the user clicked - draggedWidget.x = mouse.x - clickOffsetInWidget.x - draggedWidget.y = mouse.y - clickOffsetInWidget.y + if (dragStarted) { + // Move ghost widget + dragGhost.x = mouse.x - dragGhost.width / 2 + dragGhost.y = mouse.y - dragGhost.height / 2 + + // Update drop indicator + updateDropIndicator(mouse.x, mouse.y) } } } onReleased: mouse => { - if (dragStarted && draggedWidget) { - // Find drop target using improved logic - let targetIndex = -1 - let minDistance = Infinity - const mouseX = mouse.x - const mouseY = mouse.y + if (dragStarted && dropTargetIndex !== -1 && dropTargetIndex !== draggedIndex) { + // Perform the reorder + reorderWidget(sectionId, draggedIndex, dropTargetIndex) + } - // Check if we should insert at the beginning - let insertAtBeginning = true - let insertAtEnd = true - - // Check if the dragged item is already the last item - let isLastItem = true - for (var k = 0; k < widgetModel.length; k++) { - if (k !== draggedIndex && k > draggedIndex) { - isLastItem = false - break - } - } - - for (var i = 0; i < widgetModel.length; i++) { - if (i !== draggedIndex) { - const widget = widgetFlow.children[i] - if (widget && widget.widgetIndex !== undefined) { - const centerX = widget.x + widget.width / 2 - const centerY = widget.y + widget.height / 2 - const distance = Math.sqrt(Math.pow(mouseX - centerX, 2) + Math.pow(mouseY - centerY, 2)) - - // Check if mouse is to the right of this widget - if (mouseX > widget.x + widget.width / 2) { - insertAtBeginning = false - } - // Check if mouse is to the left of this widget - if (mouseX < widget.x + widget.width / 2) { - insertAtEnd = false - } - - if (distance < minDistance) { - minDistance = distance - targetIndex = widget.widgetIndex - } - } - } - } - - // If dragging the last item to the right, don't reorder - if (isLastItem && insertAtEnd) { - insertAtEnd = false - targetIndex = -1 - //Logger.log("BarSectionEditor", "Last item dropped to right - no reordering needed") - } - - // Determine final target index based on position - let finalTargetIndex = targetIndex - - if (insertAtBeginning && widgetModel.length > 1) { - // Insert at the very beginning (position 0) - finalTargetIndex = 0 - //Logger.log("BarSectionEditor", "Inserting at beginning") - } else if (insertAtEnd && widgetModel.length > 1) { - // Insert at the very end - let maxIndex = -1 - for (var j = 0; j < widgetModel.length; j++) { - if (j !== draggedIndex) { - maxIndex = Math.max(maxIndex, j) - } - } - finalTargetIndex = maxIndex - //Logger.log("BarSectionEditor", "Inserting at end, target:", finalTargetIndex) - } else if (targetIndex !== -1) { - // Normal case - determine if we should insert before or after the target - const targetWidget = widgetFlow.children[targetIndex] - if (targetWidget) { - const targetCenterX = targetWidget.x + targetWidget.width / 2 - if (mouseX > targetCenterX) { - - // Mouse is to the right of target center, insert after - //Logger.log("BarSectionEditor", "Inserting after widget at index:", targetIndex) - } else { - // Mouse is to the left of target center, insert before - finalTargetIndex = targetIndex - //Logger.log("BarSectionEditor", "Inserting before widget at index:", targetIndex) - } - } - } - - //Logger.log("BarSectionEditor", "Final drop target index:", finalTargetIndex) - - // Check if reordering is needed - if (finalTargetIndex !== -1 && finalTargetIndex !== draggedIndex) { - // Reordering will happen - reset position for the Flow to handle - draggedWidget.x = 0 - draggedWidget.y = 0 - draggedWidget.z = 0 - reorderWidget(sectionId, draggedIndex, finalTargetIndex) - } else { - // No reordering - restore original position - draggedWidget.x = originalWidgetPos.x - draggedWidget.y = originalWidgetPos.y - draggedWidget.z = 0 - //Logger.log("BarSectionEditor", "No reordering - restoring original position") - } - } else if (draggedIndex !== -1 && !dragStarted) { - - // This was a click without drag - could add click handling here if needed + // Emit signal when drag ends (only if it was actually started) + if (dragStarted) { + root.dragEnded() } // Reset everything dragStarted = false draggedIndex = -1 draggedWidget = null - preventStealing = false // Allow normal event propagation again - originalWidgetPos = Qt.point(0, 0) // Reset stored position + dropTargetIndex = -1 + draggedModelData = null + preventStealing = false + dropIndicator.opacity = 0 + pulseAnimation.running = false + dragGhost.width = 0 } - // Handle case where mouse leaves the area during drag onExited: { - if (dragStarted && draggedWidget) { - // Restore original position when mouse leaves area - draggedWidget.x = originalWidgetPos.x - draggedWidget.y = originalWidgetPos.y - draggedWidget.z = 0 + if (dragStarted) { + // Hide drop indicator when mouse leaves, but keep ghost visible + dropIndicator.opacity = 0 + pulseAnimation.running = false } } + + onCanceled: { + // Handle cancel (e.g., ESC key pressed during drag) + if (dragStarted) { + root.dragEnded() + } + + // Reset everything + dragStarted = false + draggedIndex = -1 + draggedWidget = null + dropTargetIndex = -1 + draggedModelData = null + preventStealing = false + dropIndicator.opacity = 0 + pulseAnimation.running = false + dragGhost.width = 0 + } } } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index c543018..4b5ffdc 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -9,6 +9,22 @@ import qs.Modules.SettingsPanel.Bar ColumnLayout { id: root + // Handler for drag start - disables panel background clicks + function handleDragStart() { + var panel = PanelService.getPanel("settingsPanel") + if (panel && panel.disableBackgroundClick) { + panel.disableBackgroundClick() + } + } + + // Handler for drag end - re-enables panel background clicks + function handleDragEnd() { + var panel = PanelService.getPanel("settingsPanel") + if (panel && panel.enableBackgroundClick) { + panel.enableBackgroundClick() + } + } + ColumnLayout { spacing: Style.marginL * scaling @@ -116,6 +132,8 @@ ColumnLayout { onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onDragStarted: root.handleDragStart() + onDragEnded: root.handleDragEnd() } // Center Section @@ -128,6 +146,8 @@ ColumnLayout { onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onDragStarted: root.handleDragStart() + onDragEnded: root.handleDragEnd() } // Right Section @@ -140,6 +160,8 @@ ColumnLayout { onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onDragStarted: root.handleDragStart() + onDragEnded: root.handleDragEnd() } } } diff --git a/Services/ColorSchemeService.qml b/Services/ColorSchemeService.qml index aed0228..46148e2 100644 --- a/Services/ColorSchemeService.qml +++ b/Services/ColorSchemeService.qml @@ -118,7 +118,7 @@ Singleton { } } writeColorsToDisk(variant) - Logger.log("ColorScheme", "Applying color scheme:", path) + Logger.log("ColorScheme", "Applying color scheme:", getBasename(path)) } catch (e) { Logger.error("ColorScheme", "Failed to parse scheme JSON:", e) }