import QtQuick import qs.config import qs.widgets Rectangle { id: root required property var favorite required property string sourceName required property int index required property bool featured signal aliasCommitted(string id, string alias) signal removeRequested(string id) signal moveRequested(string id, int targetIndex) readonly property bool dragging: dragHandler.active implicitHeight: 108 radius: Theme.cardRadius color: root.dragging ? Theme.mix(Theme.bgDark, Theme.accent, 0.09) : Theme.alpha(Theme.bgDark, 0.7) border.width: root.dragging ? 2 : 1 border.color: root.dragging ? Theme.alpha(Theme.accent, 0.82) : Theme.alpha(Theme.fg, 0.07) z: root.dragging ? 10 : 0 transform: Translate { x: root.dragging ? dragHandler.translation.x : 0 y: root.dragging ? dragHandler.translation.y : 0 } PrismEdge { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right inset: root.radius opacity: root.dragging ? 0.82 : 0.2 } Rectangle { id: dragHandle anchors.left: parent.left anchors.leftMargin: 11 anchors.verticalCenter: parent.verticalCenter width: 30 height: 42 radius: 9 activeFocusOnTab: true color: root.dragging || activeFocus ? Theme.alpha(Theme.accent, 0.14) : (handleMouse.containsMouse ? Theme.alpha(Theme.fg, 0.09) : Theme.alpha(Theme.fg, 0.045)) border.width: activeFocus ? 2 : 1 border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.06) Text { anchors.centerIn: parent text: "⠿" color: root.dragging ? Theme.accent : Theme.fgDim font.family: Theme.fontFamily font.pixelSize: 16 } MouseArea { id: handleMouse anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton cursorShape: Qt.SizeAllCursor } DragHandler { id: dragHandler target: null onActiveChanged: { if (!active) root.commitDrag(); } } Keys.onPressed: event => { if (event.key === Qt.Key_Left || event.key === Qt.Key_Up) { root.moveRequested(root.favorite.id, Math.max(0, root.index - 1)); event.accepted = true; } else if (event.key === Qt.Key_Right || event.key === Qt.Key_Down) { const grid = root.GridView.view; const lastIndex = grid ? grid.count - 1 : root.index; root.moveRequested(root.favorite.id, Math.min(lastIndex, root.index + 1)); event.accepted = true; } } } Rectangle { id: aliasFrame anchors.left: dragHandle.right anchors.leftMargin: 10 anchors.right: removeButton.left anchors.rightMargin: 12 anchors.top: parent.top anchors.topMargin: 12 height: 34 radius: 8 color: Theme.alpha(Theme.fg, aliasInput.activeFocus ? 0.075 : 0.045) border.width: aliasInput.activeFocus ? 2 : 1 border.color: aliasInput.activeFocus ? Theme.alpha(Theme.accent, 0.78) : Theme.alpha(Theme.fg, 0.065) TextInput { id: aliasInput anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 activeFocusOnTab: true text: String(root.favorite.alias || "") color: Theme.fg selectionColor: Theme.accent selectedTextColor: Theme.bgDark font.family: Theme.fontFamily font.pixelSize: Theme.fontSize font.weight: Font.Medium verticalAlignment: TextInput.AlignVCenter selectByMouse: true clip: true onEditingFinished: root.aliasCommitted(root.favorite.id, text) Text { anchors.fill: parent visible: aliasInput.text === "" && !aliasInput.activeFocus text: root.sourceName color: Theme.fgDim font: aliasInput.font verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } } } Text { anchors.left: aliasFrame.left anchors.right: removeButton.left anchors.rightMargin: 12 anchors.top: aliasFrame.bottom anchors.topMargin: 7 text: root.sourceName color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall elide: Text.ElideRight } Rectangle { anchors.left: aliasFrame.left anchors.bottom: parent.bottom anchors.bottomMargin: 10 width: badgeCopy.implicitWidth + 14 height: 21 radius: Theme.pillRadius visible: root.featured color: Theme.alpha(Theme.accent, 0.1) border.width: 1 border.color: Theme.alpha(Theme.accent, 0.2) Text { id: badgeCopy anchors.centerIn: parent text: "Control Center" color: Theme.accent font.family: Theme.fontFamily font.pixelSize: 9 font.weight: Font.DemiBold } } SettingsButton { id: removeButton anchors.right: parent.right anchors.rightMargin: 11 anchors.verticalCenter: parent.verticalCenter text: "Remove" activeFocusOnTab: true border.width: activeFocus ? 2 : 1 border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08) onClicked: root.removeRequested(root.favorite.id) Keys.onReturnPressed: root.removeRequested(root.favorite.id) Keys.onSpacePressed: root.removeRequested(root.favorite.id) } function commitDrag(): void { const grid = root.GridView.view; if (!grid || grid.count <= 0) return; const centerX = root.x + dragHandler.translation.x + root.width / 2; const centerY = root.y + dragHandler.translation.y + root.height / 2; const modelCount = grid.count; const column = Math.max(0, Math.min(1, Math.floor(centerX / grid.cellWidth))); const row = Math.max(0, Math.floor(centerY / grid.cellHeight)); root.moveRequested(root.favorite.id, Math.min(modelCount - 1, row * 2 + column)); } }