2026-02-15 22:16:51 +01:00
|
|
|
import QtQuick
|
|
|
|
|
|
|
|
|
|
Item {
|
2026-03-08 22:40:30 +01:00
|
|
|
property bool hideQRScanButton: true
|
2026-03-15 14:57:30 +01:00
|
|
|
property bool combineWallets: allWalletsEntrySelected
|
|
|
|
|
onCombineWalletsChanged: {
|
|
|
|
|
tokens.combineWallets = combineWallets
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 22:16:51 +01:00
|
|
|
Flickable {
|
|
|
|
|
id: canvas
|
|
|
|
|
anchors.fill: parent
|
2026-03-15 17:50:22 +01:00
|
|
|
boundsBehavior: Flickable.StopAtBounds
|
2026-02-15 22:16:51 +01:00
|
|
|
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
|
2026-03-07 15:40:13 +01:00
|
|
|
color: palette.alternateBase
|
2026-02-15 22:16:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Repeater {
|
|
|
|
|
model: canvas.editable ? tokensLayout.rowCount + 1 : 0
|
|
|
|
|
delegate: Rectangle {
|
|
|
|
|
y: index * (parent.height / tokensLayout.rowCount)
|
|
|
|
|
width: parent.width
|
|
|
|
|
height: 1
|
2026-03-07 15:40:13 +01:00
|
|
|
color: palette.alternateBase
|
2026-02-15 22:16:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
2026-03-13 12:45:08 +01:00
|
|
|
x: modelData.col * cellWidth
|
|
|
|
|
y: modelData.row * cellHeight
|
|
|
|
|
width: modelData.colSpan * cellWidth
|
|
|
|
|
height: modelData.rowSpan * cellHeight
|
2026-02-15 22:16:51 +01:00
|
|
|
|
|
|
|
|
// 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
|
2026-03-07 23:55:20 +01:00
|
|
|
source: modelData.source
|
2026-02-17 21:00:12 +01:00
|
|
|
clip: true
|
2026-02-15 22:16:51 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 15:40:13 +01:00
|
|
|
// Drag the widget
|
2026-02-15 22:16:51 +01:00
|
|
|
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,
|
2026-03-13 12:45:08 +01:00
|
|
|
tokensLayout.screenColumnCount - modelData.colSpan))
|
2026-02-15 22:16:51 +01:00
|
|
|
candidateRow = Math.max(0, Math.min(candidateRow,
|
2026-03-13 12:45:08 +01:00
|
|
|
tokensLayout.rowCount - modelData.rowSpan))
|
2026-02-15 22:16:51 +01:00
|
|
|
|
|
|
|
|
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)
|
2026-03-13 12:45:08 +01:00
|
|
|
modelData.col = finalCol
|
|
|
|
|
modelData.row = finalRow
|
2026-02-15 22:16:51 +01:00
|
|
|
canvas.interactive = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 15:40:13 +01:00
|
|
|
// Resize handle
|
2026-02-15 22:16:51 +01:00
|
|
|
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"
|
2026-03-07 15:40:13 +01:00
|
|
|
visible: false // disable configurability for now.
|
2026-02-15 22:16:51 +01:00
|
|
|
MouseArea {
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
onClicked: {
|
|
|
|
|
canvas.editable = !canvas.editable
|
|
|
|
|
activityTabs.interactive = !activityTabs.interactive
|
|
|
|
|
if (canvas.editable)
|
|
|
|
|
tokensLayout.rowCount += 12
|
|
|
|
|
else
|
|
|
|
|
tokensLayout.trimRows()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-07 15:40:13 +01:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-15 22:16:51 +01:00
|
|
|
}
|