import QtQuick Item { property bool hideQRScanButton: true property bool combineWallets: allWalletsEntrySelected onCombineWalletsChanged: { tokens.combineWallets = combineWallets } Flickable { id: canvas anchors.fill: parent boundsBehavior: Flickable.StopAtBounds property bool editable: false property real cellWidth: parent.width / tokensLayout.screenColumnCount property real cellHeight: parent.height / tokensLayout.screenRowCount contentWidth: cellWidth * tokensLayout.screenColumnCount contentHeight: cellHeight * tokensLayout.rowCount Repeater { model: canvas.editable ? tokensLayout.screenColumnCount + 1 : 0 delegate: Rectangle { x: index * (parent.width / tokensLayout.screenColumnCount) width: 1 height: parent.height color: palette.alternateBase } } Repeater { model: canvas.editable ? tokensLayout.rowCount + 1 : 0 delegate: Rectangle { y: index * (parent.height / tokensLayout.rowCount) width: parent.width height: 1 color: palette.alternateBase } } // Interactive widgets Repeater { model: tokensLayout.widgets delegate: Item { id: container z: dragArea.pressed || resizeArea.pressed ? 100 : 1 property real cellWidth: canvas.cellWidth property real cellHeight: canvas.cellHeight x: modelData.col * cellWidth y: modelData.row * cellHeight width: modelData.colSpan * cellWidth height: modelData.rowSpan * cellHeight // Visual border (highlight when interacting) Rectangle { anchors.fill: parent color: "transparent" border.color: (dragArea.pressed || resizeArea.pressed) ? "blue" : "gray" border.width: 2 radius: 8 visible: canvas.editable } Loader { anchors.fill: parent anchors.margins: 8 source: modelData.source clip: true } // Drag the widget MouseArea { id: dragArea anchors.fill: parent enabled: canvas.editable cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor property point grabOffset: Qt.point(0, 0) onPressed: (mouse) => { var pressGlobal = dragArea.mapToItem(container.parent, mouse.x, mouse.y) grabOffset = Qt.point(pressGlobal.x - container.x, pressGlobal.y - container.y) canvas.interactive = false } onPositionChanged: (mouse) => { if (pressed) { var currentGlobal = dragArea.mapToItem(container.parent, mouse.x, mouse.y) // Desired position if no snap (grabbed point follows cursor exactly) var desiredX = currentGlobal.x - grabOffset.x var desiredY = currentGlobal.y - grabOffset.y // Live snap to grid var candidateCol = Math.round(desiredX / cellWidth) var candidateRow = Math.round(desiredY / cellHeight) // Clamp to keep widget fully inside grid candidateCol = Math.max(0, Math.min(candidateCol, tokensLayout.screenColumnCount - modelData.colSpan)) candidateRow = Math.max(0, Math.min(candidateRow, tokensLayout.rowCount - modelData.rowSpan)) container.x = candidateCol * cellWidth container.y = candidateRow * cellHeight } } onReleased: { // Update backend to final snapped position var finalCol = Math.round(container.x / cellWidth) var finalRow = Math.round(container.y / cellHeight) modelData.col = finalCol modelData.row = finalRow canvas.interactive = true } } // Resize handle Rectangle { id: resizeHandle width: 24 height: 24 anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: -8 color: "#80ffffff" radius: 4 border.color: "#666666" border.width: 1 visible: canvas.editable // Grip visual Repeater { model: 3 Rectangle { width: 4 height: 4 color: "#666666" x: 4 + index * 5 y: 4 + index * 5 } } MouseArea { id: resizeArea anchors.fill: parent cursorShape: Qt.SizeFDiagCursor enabled: canvas.editable property real startWidth: 0 property real startHeight: 0 property point pressMousePos: Qt.point(0, 0) onPressed: (mouse) => { startWidth = container.width startHeight = container.height pressMousePos = resizeArea.mapToItem(container.parent, mouse.x, mouse.y) canvas.interactive = false } onPositionChanged: (mouse) => { if (pressed) { var currentMousePos = resizeArea.mapToItem(container.parent, mouse.x, mouse.y) var deltaX = currentMousePos.x - pressMousePos.x var deltaY = currentMousePos.y - pressMousePos.y var newPixelWidth = startWidth + deltaX var newPixelHeight = startHeight + deltaY // Enforce minimum before snapping newPixelWidth = Math.max(newPixelWidth, model.minimumWidth * cellWidth) newPixelHeight = Math.max(newPixelHeight, model.minimumHeight * cellHeight) // Live snap to grid units var candColSpan = Math.round(newPixelWidth / cellWidth) var candRowSpan = Math.round(newPixelHeight / cellHeight) // Clamp to grid bounds candColSpan = Math.min(candColSpan, tokensLayout.screenColumnCount - model.col) candRowSpan = Math.min(candRowSpan, tokensLayout.rowCount - model.row) container.width = candColSpan * cellWidth container.height = candRowSpan * cellHeight } } onReleased: { var finalColSpan = container.width / cellWidth var finalRowSpan = container.height / cellHeight model.colSpan = finalColSpan model.rowSpan = finalRowSpan canvas.interactive = true } } } } } } Rectangle { width: 50 height: 20 anchors.right: parent.right color: "blue" visible: false // disable configurability for now. MouseArea { anchors.fill: parent onClicked: { canvas.editable = !canvas.editable activityTabs.interactive = !activityTabs.interactive if (canvas.editable) tokensLayout.rowCount += 12 else tokensLayout.trimRows() } } } Keys.onPressed: (event)=> { if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { if (canvas.editable) { event.accepted = true canvas.editable = false activityTabs.interactive = true } } } }