// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import Quickshell import Quickshell.Widgets import QtQuick import ".." import "Controls.js" as Controls // The grid of controls and its edit mode. In edit mode a tile is dragged to // a new slot, a × hides it, and hidden tiles wait in a strip below with a +. // Every change is written to Prefs at once. Column { id: panel required property QtObject sound property bool editing: false signal openDrawer(string page) spacing: 12 Item { width: panel.width height: 20 Text { anchors.verticalCenter: parent.verticalCenter text: panel.editing ? "Edit controls" : "Controls" font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } color: Theme.text } IconImage { anchors { right: parent.right; verticalCenter: parent.verticalCenter } implicitSize: 16 opacity: panel.editing ? 1 : 0.6 source: Quickshell.iconPath("document-edit", true) MouseArea { anchors.fill: parent anchors.margins: -4 cursorShape: Qt.PointingHandCursor onClicked: panel.editing = !panel.editing } } } Item { id: grid readonly property int cols: 4 readonly property real gap: 8 readonly property real cell: (width - gap * (cols - 1)) / cols readonly property real pitch: cell + gap readonly property int rows: Math.ceil(Prefs.controls.length / cols) width: panel.width height: Math.max(0, rows * pitch - gap) Repeater { model: Prefs.controls delegate: Item { id: slot required property string modelData required property int index width: grid.cell height: grid.cell x: (index % grid.cols) * grid.pitch y: Math.floor(index / grid.cols) * grid.pitch z: mover.pressed ? 1 : 0 ControlTile { width: parent.width height: parent.height x: mover.pressed ? mover.dx : 0 y: mover.pressed ? mover.dy : 0 tileId: slot.modelData sound: panel.sound onOpenPage: page => panel.openDrawer(page) } // Edit mode only, and above the tile so it takes the press // instead of the toggle. Offsets the tile while held; on // release the tile lands in the slot under its centre. MouseArea { id: mover anchors.fill: parent enabled: panel.editing preventStealing: true cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor property real sx: 0 property real sy: 0 property real dx: 0 property real dy: 0 onPressed: mouse => { sx = mouse.x; sy = mouse.y; dx = 0; dy = 0; } onPositionChanged: mouse => { dx = mouse.x - sx; dy = mouse.y - sy; } onReleased: { const to = Controls.slotAt(slot.x + dx + width / 2, slot.y + dy + height / 2, grid.pitch, grid.cols, Prefs.controls.length); if (to !== slot.index) Prefs.setControls(Controls.move(Prefs.controls, slot.index, to)); } } IconImage { visible: panel.editing && !mover.pressed anchors { top: parent.top; right: parent.right; margins: 3 } implicitSize: 14 source: Quickshell.iconPath("window-close", true) MouseArea { anchors.fill: parent anchors.margins: -4 cursorShape: Qt.PointingHandCursor onClicked: Prefs.setControls(Controls.hide(Prefs.controls, slot.modelData)) } } } } } Flow { width: panel.width spacing: 6 visible: panel.editing && Controls.hidden(Prefs.controls).length > 0 Repeater { model: Controls.hidden(Prefs.controls) delegate: Rectangle { id: chip required property string modelData width: chipRow.implicitWidth + 16 height: 28 radius: 14 color: Qt.alpha(Theme.surface, 0.5) Row { id: chipRow anchors.centerIn: parent spacing: 4 IconImage { anchors.verticalCenter: parent.verticalCenter implicitSize: 14 source: Quickshell.iconPath("list-add", true) } Text { anchors.verticalCenter: parent.verticalCenter text: Controls.tile(chip.modelData).label font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } color: Theme.text } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: Prefs.setControls(Controls.show(Prefs.controls, chip.modelData)) } } } } }