mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
NSectionEditor: add drag & drop between sections
This commit is contained in:
+107
-13
@@ -20,6 +20,7 @@ NBox {
|
||||
property bool barIsVertical: false // When true, map left/right to top/bottom in labels
|
||||
property int maxWidgets: -1 // -1 means unlimited
|
||||
property bool draggable: true // Enable/disable drag reordering
|
||||
property alias dropTargetArea: gridContainer
|
||||
|
||||
// Get display label for a section
|
||||
function getSectionLabel(sectionId) {
|
||||
@@ -41,6 +42,58 @@ NBox {
|
||||
property string settingsDialogComponent: "invalid-settings-dialog"
|
||||
property var screen: null // Screen reference for per-screen widget settings
|
||||
property var _activeDialog: null
|
||||
property bool crossDropHoverActive: false
|
||||
|
||||
function clearCrossSectionHover() {
|
||||
crossDropHoverActive = false;
|
||||
|
||||
var parentItem = root.parent;
|
||||
if (!parentItem || !parentItem.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < parentItem.children.length; i++) {
|
||||
var candidate = parentItem.children[i];
|
||||
if (!candidate || candidate.crossDropHoverActive === undefined)
|
||||
continue;
|
||||
candidate.crossDropHoverActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCrossSectionHover(globalX, globalY) {
|
||||
var parentItem = root.parent;
|
||||
if (!parentItem || !parentItem.children) {
|
||||
crossDropHoverActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
crossDropHoverActive = false;
|
||||
|
||||
for (var i = 0; i < parentItem.children.length; i++) {
|
||||
var candidate = parentItem.children[i];
|
||||
if (!candidate || candidate.sectionId === undefined || candidate.crossDropHoverActive === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var shouldHighlight = false;
|
||||
if (candidate !== root && candidate.visible && candidate.enabled) {
|
||||
var localPoint = candidate.mapFromGlobal(globalX, globalY);
|
||||
var area = candidate.dropTargetArea ? candidate.dropTargetArea : candidate;
|
||||
shouldHighlight = localPoint.x >= area.x && localPoint.y >= area.y && localPoint.x <= area.x + area.width && localPoint.y <= area.y + area.height;
|
||||
}
|
||||
|
||||
candidate.crossDropHoverActive = shouldHighlight;
|
||||
}
|
||||
}
|
||||
|
||||
function isPointInsideSelf(globalX, globalY) {
|
||||
if (globalX < 0 || globalY < 0)
|
||||
return false;
|
||||
var localPoint = root.mapFromGlobal(globalX, globalY);
|
||||
return localPoint.x >= 0 && localPoint.y >= 0 && localPoint.x <= root.width && localPoint.y <= root.height;
|
||||
}
|
||||
|
||||
readonly property bool showCrossSectionDropHint: crossDropHoverActive
|
||||
|
||||
Component.onDestruction: {
|
||||
if (_activeDialog && _activeDialog.close) {
|
||||
@@ -68,6 +121,7 @@ NBox {
|
||||
color: Color.mSurface
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
Layout.fillWidth: true
|
||||
z: flowDragArea.dragStarted ? 5000 : 0
|
||||
|
||||
// Calculate width to fit gridColumns widgets with spacing
|
||||
function calculateWidgetWidth(gridWidth) {
|
||||
@@ -76,6 +130,33 @@ NBox {
|
||||
return Math.floor(widgetWidth);
|
||||
}
|
||||
|
||||
function findSectionAtGlobalPosition(globalX, globalY) {
|
||||
var parentItem = root.parent;
|
||||
if (!parentItem || !parentItem.children) {
|
||||
return "";
|
||||
}
|
||||
|
||||
for (var i = 0; i < parentItem.children.length; i++) {
|
||||
var candidate = parentItem.children[i];
|
||||
if (!candidate || candidate === root) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only consider sibling section editors
|
||||
if (candidate.sectionId === undefined || !candidate.visible || !candidate.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var localPoint = candidate.mapFromGlobal(globalX, globalY);
|
||||
var area = candidate.dropTargetArea ? candidate.dropTargetArea : candidate;
|
||||
if (localPoint.x >= area.x && localPoint.y >= area.y && localPoint.x <= area.x + area.width && localPoint.y <= area.y + area.height) {
|
||||
return candidate.sectionId;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
Layout.minimumHeight: {
|
||||
// header + minimal content area
|
||||
var absoluteMin = (Style.marginL * 2) + (Style.fontSizeL * 2) + Style.marginM + (65 * Style.uiScaleRatio);
|
||||
@@ -320,10 +401,20 @@ NBox {
|
||||
var gridTopMargin = Style.marginS;
|
||||
var gridBottomMargin = Style.marginS;
|
||||
var calculatedHeight = gridTopMargin + (rows * root.widgetItemHeight) + ((rows - 1) * Style.marginS) + gridBottomMargin;
|
||||
return Math.max(65 * Style.uiScaleRatio, calculatedHeight);
|
||||
return calculatedHeight;
|
||||
}
|
||||
Layout.minimumHeight: widgetModel.length === 0 ? (65 * Style.uiScaleRatio) : ((Style.marginS * 2) + root.widgetItemHeight)
|
||||
clip: !flowDragArea.dragStarted
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.iRadiusL
|
||||
color: Qt.alpha(Color.mSecondary, 0.12)
|
||||
border.color: Color.mSecondary
|
||||
border.width: Style.borderM
|
||||
visible: root.showCrossSectionDropHint
|
||||
z: 1500
|
||||
}
|
||||
Layout.minimumHeight: 65 * Style.uiScaleRatio
|
||||
clip: true // Clip to prevent overflow
|
||||
|
||||
Grid {
|
||||
id: widgetGrid
|
||||
@@ -494,16 +585,6 @@ NBox {
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.5
|
||||
}
|
||||
|
||||
// CPU-intensive indicator icon
|
||||
NIcon {
|
||||
visible: root.widgetRegistry && root.widgetRegistry.isCpuIntensive(modelData.id)
|
||||
icon: "cpu-intensive"
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: root.getWidgetColor(modelData)[1]
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.5 : 0
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.5
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Layout.preferredWidth: buttonsCount * buttonsWidth * Style.uiScaleRatio
|
||||
@@ -716,6 +797,7 @@ NBox {
|
||||
}
|
||||
|
||||
function resetDragState() {
|
||||
root.clearCrossSectionHover();
|
||||
dragStarted = false;
|
||||
potentialDrag = false;
|
||||
draggedIndex = -1;
|
||||
@@ -788,6 +870,9 @@ NBox {
|
||||
dragGhost.color = root.getWidgetColor(draggedModelData)[0];
|
||||
ghostText.text = draggedModelData.id;
|
||||
}
|
||||
|
||||
var startGlobal = flowDragArea.mapToGlobal(mouse.x, mouse.y);
|
||||
root.updateCrossSectionHover(startGlobal.x, startGlobal.y);
|
||||
}
|
||||
|
||||
if (dragStarted) {
|
||||
@@ -795,6 +880,9 @@ NBox {
|
||||
dragGhost.x = mouse.x - dragGhost.width / 2;
|
||||
dragGhost.y = mouse.y - dragGhost.height / 2;
|
||||
|
||||
var moveGlobal = flowDragArea.mapToGlobal(mouse.x, mouse.y);
|
||||
root.updateCrossSectionHover(moveGlobal.x, moveGlobal.y);
|
||||
|
||||
// Update drop indicator
|
||||
updateDropIndicator(mouse.x, mouse.y);
|
||||
}
|
||||
@@ -805,6 +893,12 @@ NBox {
|
||||
if (dragStarted && dropTargetIndex !== -1 && dropTargetIndex !== draggedIndex) {
|
||||
// Perform the reorder
|
||||
reorderWidget(sectionId, draggedIndex, dropTargetIndex);
|
||||
} else if (dragStarted && draggedIndex !== -1) {
|
||||
var globalPos = flowDragArea.mapToGlobal(mouse.x, mouse.y);
|
||||
var targetSectionId = root.findSectionAtGlobalPosition(globalPos.x, globalPos.y);
|
||||
if (targetSectionId !== "" && targetSectionId !== root.sectionId) {
|
||||
root.moveWidget(root.sectionId, draggedIndex, targetSectionId);
|
||||
}
|
||||
}
|
||||
|
||||
// Always signal end of interaction if we started one
|
||||
|
||||
Reference in New Issue
Block a user