// 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 QtQuick // The macropad as it sits on the desk: twelve keys in three rows of four, // the two encoders to their right. Each key carries its plain action; // hovering a key or an encoder lists everything it does underneath. The // last one hovered stays listed, so moving the pointer off does not blank it. Item { id: panel // Binds.padActions(): "k1".."k12", "top", "bottom" to [{ how, label }]. property var actions: ({}) property string hovered: "" // Keys are unit wide; the pad is four of them, three gaps, a gap and an // encoder: 5.56 units, fitted to most of the panel's width. readonly property int unit: Math.floor(width * 0.85 / 5.56) readonly property int padWidth: Math.round(unit * 5.56) function plain(id) { const a = (actions[id] ?? []).find(x => x.how === "Press"); return a ? a.label : ""; } function title(id) { if (id === "top") return "Top encoder"; if (id === "bottom") return "Bottom encoder"; return "Key " + id.slice(1); } // Pinned at a fixed height rather than centred: centred, the pad moved // every time the list under it changed length. Column { anchors.horizontalCenter: parent.horizontalCenter y: parent.height * 0.3 spacing: panel.unit * 0.5 Row { spacing: panel.unit * 0.3 Grid { id: keys columns: 4 spacing: panel.unit * 0.12 Repeater { model: 12 Rectangle { required property int index readonly property string padId: "k" + (index + 1) width: panel.unit height: panel.unit radius: panel.unit * 0.12 color: panel.hovered === padId ? Theme.surfaceAlt : Theme.surface border.color: panel.hovered === padId ? Theme.accent : "transparent" border.width: 2 Text { anchors.fill: parent anchors.margins: 4 text: panel.plain(parent.padId) wrapMode: Text.Wrap elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: Theme.text font.family: Theme.fontFamily font.pixelSize: Math.max(10, panel.unit * 0.16) } MouseArea { anchors.fill: parent hoverEnabled: true onEntered: panel.hovered = parent.padId } } } } // One encoder level with the top row, one with the bottom. Item { width: panel.unit * 0.9 height: keys.height Repeater { model: ["top", "bottom"] Rectangle { required property string modelData y: modelData === "top" ? 0 : parent.height - height width: parent.width height: width radius: width / 2 color: panel.hovered === modelData ? Theme.surfaceAlt : Theme.surface border.color: panel.hovered === modelData ? Theme.accent : Theme.overlay border.width: 2 MouseArea { anchors.fill: parent hoverEnabled: true onEntered: panel.hovered = parent.modelData } } } } } Column { width: panel.padWidth spacing: 6 Text { text: panel.hovered ? panel.title(panel.hovered) : "Hover a key or an encoder" color: Theme.accent font.family: Theme.fontFamily font.pixelSize: 18 font.bold: true bottomPadding: 4 } Repeater { model: panel.actions[panel.hovered] ?? [] // Fixed widths summing to the column's: a Row sizes to its // children. Row { required property var modelData spacing: 12 Text { width: panel.padWidth * 0.4 text: modelData.how color: Theme.subtext font.family: Theme.fontFamily font.pixelSize: 15 } Text { width: panel.padWidth * 0.6 - 12 text: modelData.label wrapMode: Text.Wrap color: Theme.text font.family: Theme.fontFamily font.pixelSize: 15 } } } } } }