// 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 keycap's legends; // 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) // The keycaps, left to right and top to bottom, as printed: icon and // text on top, the shifted (or Alt) legend below, in the palette's // colours nearest the real caps: orange is peach, purple is mauve. // Glyphs are checked by name in Inconsolata Nerd Font (see AGENTS.md, // Theme). readonly property var caps: [ { icon: "\u{F0239}", sub: "\u{F05F9}", cap: Theme.red, ink: Theme.base }, // firefox / incognito { icon: "\u{F0174}", sub: "\u{F0354}", cap: Theme.teal, ink: Theme.base }, // code_tags / markdown { icon: "\u{F338}", cap: Theme.yellow, ink: Theme.base }, // gimp { icon: "\u{F0147}", sub: "\u{F0432}", cap: Theme.mauve, ink: Theme.base }, // clipboard / qrcode { icon: "\u{F018D}", text: "SSH", cap: Theme.crust, ink: Theme.green }, // console { icon: "\u{F0BCC}", text: "split", cap: Theme.peach, ink: Theme.base }, // view_split_vertical { icon: "\u{F036C}", cap: Theme.blue, ink: Theme.base }, // microphone { icon: "\u{F02C3}", text: "group", cap: Theme.peach, ink: Theme.base }, // group { text: "1", sub: "\u{F03B0}", cap: Theme.peach, ink: Theme.base }, // numeric_5_box_outline { text: "2", sub: "\u{F03B5}", cap: Theme.peach, ink: Theme.base }, { text: "3", sub: "\u{F03B8}", cap: Theme.peach, ink: Theme.base }, { text: "4", sub: "\u{F03BB}", cap: Theme.peach, ink: Theme.base }, ] 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 { id: keycap required property int index readonly property string padId: "k" + (index + 1) readonly property var cap: panel.caps[index] width: panel.unit height: panel.unit radius: panel.unit * 0.12 color: cap.cap border.color: panel.hovered === padId ? Theme.text : "transparent" border.width: 3 Column { anchors.centerIn: parent Text { anchors.horizontalCenter: parent.horizontalCenter visible: !!keycap.cap.icon text: keycap.cap.icon ?? "" color: keycap.cap.ink font.family: Theme.iconFamily font.pixelSize: panel.unit * 0.34 } Text { anchors.horizontalCenter: parent.horizontalCenter visible: !!keycap.cap.text text: keycap.cap.text ?? "" color: keycap.cap.ink font.family: Theme.fontFamily font.bold: true // A lone digit is the cap's main legend. font.pixelSize: panel.unit * (keycap.cap.icon ? 0.16 : 0.34) } Text { anchors.horizontalCenter: parent.horizontalCenter visible: !!keycap.cap.sub text: keycap.cap.sub ?? "" color: keycap.cap.ink font.family: Theme.iconFamily font.pixelSize: panel.unit * 0.26 } } 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 } } } } } }