import QtQuick import qs.Common import qs.Services import qs.Widgets Item { id: root property bool editMode: false property var widgetData: null property int widgetIndex: -1 property bool isSlider: false property Component widgetComponent: null property real gridCellWidth: 100 property real gridCellHeight: 60 property int gridColumns: 4 property var gridLayout: null z: dragArea.drag.active ? 10000 : 1 signal widgetMoved(int fromIndex, int toIndex) signal removeWidget(int index) signal toggleWidgetSize(int index) width: { const widgetWidth = widgetData?.width || 50 if (widgetWidth <= 25) return gridCellWidth else if (widgetWidth <= 50) return gridCellWidth * 2 else if (widgetWidth <= 75) return gridCellWidth * 3 else return gridCellWidth * 4 } height: isSlider ? 16 : gridCellHeight Rectangle { id: dragIndicator anchors.fill: parent color: "transparent" border.color: Theme.primary border.width: dragArea.drag.active ? 2 : 0 radius: Theme.cornerRadius opacity: dragArea.drag.active ? 0.8 : 1.0 z: dragArea.drag.active ? 10000 : 1 Behavior on border.width { NumberAnimation { duration: 150 } } Behavior on opacity { NumberAnimation { duration: 150 } } } Loader { id: widgetLoader anchors.fill: parent sourceComponent: widgetComponent property var widgetData: root.widgetData property int widgetIndex: root.widgetIndex property int globalWidgetIndex: root.widgetIndex property int widgetWidth: root.widgetData?.width || 50 MouseArea { id: editModeBlocker anchors.fill: parent enabled: root.editMode acceptedButtons: Qt.AllButtons onPressed: function(mouse) { mouse.accepted = true } onWheel: function(wheel) { wheel.accepted = true } z: 100 } } MouseArea { id: dragArea anchors.fill: parent enabled: editMode cursorShape: editMode ? Qt.OpenHandCursor : Qt.PointingHandCursor drag.target: editMode ? root : null drag.axis: Drag.XAndYAxis drag.smoothed: true onPressed: function(mouse) { if (editMode) { cursorShape = Qt.ClosedHandCursor if (root.gridLayout && root.gridLayout.moveToTop) { root.gridLayout.moveToTop(root) } } } onReleased: function(mouse) { if (editMode) { cursorShape = Qt.OpenHandCursor root.snapToGrid() } } } Drag.active: dragArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 function swapIndices(i, j) { if (i === j) return; const arr = SettingsData.controlCenterWidgets; if (!arr || i < 0 || j < 0 || i >= arr.length || j >= arr.length) return; const copy = arr.slice(); const tmp = copy[i]; copy[i] = copy[j]; copy[j] = tmp; SettingsData.setControlCenterWidgets(copy); } function snapToGrid() { if (!editMode || !gridLayout) return const globalPos = root.mapToItem(gridLayout, 0, 0) const cellWidth = gridLayout.width / gridColumns const cellHeight = gridCellHeight + Theme.spacingS const centerX = globalPos.x + (root.width / 2) const centerY = globalPos.y + (root.height / 2) let targetCol = Math.max(0, Math.floor(centerX / cellWidth)) let targetRow = Math.max(0, Math.floor(centerY / cellHeight)) targetCol = Math.min(targetCol, gridColumns - 1) const newIndex = findBestInsertionIndex(targetRow, targetCol) if (newIndex !== widgetIndex && newIndex >= 0 && newIndex < (SettingsData.controlCenterWidgets?.length || 0)) { swapIndices(widgetIndex, newIndex) } } function findBestInsertionIndex(targetRow, targetCol) { const widgets = SettingsData.controlCenterWidgets || []; const n = widgets.length; if (!n || widgetIndex < 0 || widgetIndex >= n) return -1; function spanFor(width) { const w = width ?? 50; if (w <= 25) return 1; if (w <= 50) return 2; if (w <= 75) return 3; return 4; } const cols = gridColumns || 4; let row = 0, col = 0; let draggedOrigKey = null; const pos = []; for (let i = 0; i < n; i++) { const span = Math.min(spanFor(widgets[i].width), cols); if (col + span > cols) { row++; col = 0; } const startCol = col; const centerKey = row * cols + (startCol + (span - 1) / 2); if (i === widgetIndex) { draggedOrigKey = centerKey; } else { pos.push({ index: i, row, startCol, span, centerKey }); } col += span; if (col >= cols) { row++; col = 0; } } if (pos.length === 0) return -1; const centerColCoord = targetCol + 0.5; const targetKey = targetRow * cols + centerColCoord; for (let k = 0; k < pos.length; k++) { const p = pos[k]; if (p.row === targetRow && centerColCoord >= p.startCol && centerColCoord < (p.startCol + p.span)) { return p.index; } } let lo = 0, hi = pos.length - 1; if (targetKey <= pos[0].centerKey) return pos[0].index; if (targetKey >= pos[hi].centerKey) return pos[hi].index; while (lo <= hi) { const mid = (lo + hi) >> 1; const mk = pos[mid].centerKey; if (targetKey < mk) hi = mid - 1; else if (targetKey > mk) lo = mid + 1; else return pos[mid].index; } const movingUp = (draggedOrigKey != null) ? (targetKey < draggedOrigKey) : false; return (movingUp ? pos[lo].index : pos[hi].index); } Rectangle { width: 16 height: 16 radius: 8 color: Theme.error anchors.top: parent.top anchors.right: parent.right anchors.margins: -4 visible: editMode z: 10 DankIcon { anchors.centerIn: parent name: "close" size: 12 color: Theme.primaryText } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: removeWidget(widgetIndex) } } PieChartSizeControl { anchors.bottom: parent.bottom anchors.left: parent.left anchors.margins: -6 visible: editMode z: 10 currentSize: root.widgetData?.width || 50 isSlider: root.isSlider widgetIndex: root.widgetIndex onSizeChanged: (newSize) => { var widgets = SettingsData.controlCenterWidgets.slice() if (widgetIndex >= 0 && widgetIndex < widgets.length) { widgets[widgetIndex].width = newSize SettingsData.setControlCenterWidgets(widgets) } } } Rectangle { id: dragHandle width: 16 height: 12 radius: 2 color: Theme.primary anchors.top: parent.top anchors.left: parent.left anchors.margins: 4 visible: editMode z: 15 opacity: dragArea.drag.active ? 1.0 : 0.7 DankIcon { anchors.centerIn: parent name: "drag_indicator" size: 10 color: Theme.primaryText } Behavior on opacity { NumberAnimation { duration: 150 } } } Rectangle { anchors.fill: parent color: editMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" radius: Theme.cornerRadius border.color: "transparent" border.width: 0 z: -1 Behavior on color { ColorAnimation { duration: Theme.shortDuration } } } }