aboutsummaryrefslogtreecommitdiffstats
path: root/keybinds/PadPanel.qml
blob: d9f89416d96ff12effb66529f9bffd52770fa3ba (plain)
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// 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 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
                    }
                }
            }
        }
    }
}