1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// 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
}
}
}
}
}
}
|