qs: cheatsheet: display categories nicely

This commit is contained in:
end-4
2026-05-14 09:37:45 +02:00
parent 2ade168a20
commit d1daedc6d2
3 changed files with 193 additions and 140 deletions
@@ -10,76 +10,9 @@ import Quickshell
Item { Item {
id: root id: root
readonly property var keybinds: HyprlandKeybinds.keybinds.filter(function(keybind) {
return keybind.has_description;
})
property real columnSpacing: 40
property real titleSpacing: 7
property real padding: 4 property real padding: 4
implicitWidth: QsWindow.window.screen.width * 0.7 implicitWidth: QsWindow?.window?.screen.width * 0.7 ?? 0
implicitHeight: QsWindow.window.screen.height * 0.7 implicitHeight: QsWindow?.window?.screen.height * 0.7 ?? 0
// Excellent symbol explaination and source :
// http://xahlee.info/comp/unicode_computing_symbols.html
// https://www.nerdfonts.com/cheat-sheet
property var macSymbolMap: ({
"Ctrl": "󰘴",
"Alt": "󰘵",
"Shift": "󰘶",
"Space": "󱁐",
"Tab": "↹",
"Equal": "󰇼",
"Minus": "",
"Print": "",
"BackSpace": "󰭜",
"Delete": "⌦",
"Return": "󰌑",
"Period": ".",
"Escape": "⎋"
})
property var functionSymbolMap: ({
"F1": "󱊫",
"F2": "󱊬",
"F3": "󱊭",
"F4": "󱊮",
"F5": "󱊯",
"F6": "󱊰",
"F7": "󱊱",
"F8": "󱊲",
"F9": "󱊳",
"F10": "󱊴",
"F11": "󱊵",
"F12": "󱊶",
})
property var mouseSymbolMap: ({
"mouse_up": "󱕐",
"mouse_down": "󱕑",
"mouse:272": "L󰍽",
"mouse:273": "R󰍽",
"Scroll ↑/↓": "󱕒",
"Page_↑/↓": "⇞/⇟",
})
property var keyBlacklist: ["SUPER_L", "SUPER_R"]
property var keySubstitutions: Object.assign({
"Super": "",
"mouse_up": "Scroll ↓", // ikr, weird
"mouse_down": "Scroll ↑", // trust me bro
"mouse:272": "LMB",
"mouse:273": "RMB",
"mouse:275": "MouseBack",
"Slash": "/",
"Hash": "#",
"Return": "Enter",
// "Shift": "",
},
!!Config.options.cheatsheet.superKey ? {
"Super": Config.options.cheatsheet.superKey,
}: {},
Config.options.cheatsheet.useMacSymbol ? macSymbolMap : {},
Config.options.cheatsheet.useFnSymbol ? functionSymbolMap : {},
Config.options.cheatsheet.useMouseSymbol ? mouseSymbolMap : {},
)
StyledFlickable { StyledFlickable {
id: flickable id: flickable
@@ -91,79 +24,12 @@ Item {
id: flow id: flow
height: flickable.height height: flickable.height
flow: Flow.TopToBottom flow: Flow.TopToBottom
spacing: 4 spacing: 12
Repeater { Repeater {
model: root.keybinds model: [...HyprlandKeybinds.keybindCategories, ""]
delegate: BindLine { delegate: CheatsheetKeybindsCategory {
required property var modelData required property var modelData
keyData: modelData categoryName: modelData
}
}
}
}
function modMaskToStringList(modMask: int): list<string> {
var list = [];
if (modMask & (1 << 0)) { list.push("Shift"); }
if (modMask & (1 << 1)) { list.push("Caps"); }
if (modMask & (1 << 2)) { list.push("Ctrl"); }
if (modMask & (1 << 3)) { list.push("Alt"); }
if (modMask & (1 << 4)) { list.push("Mod2"); }
if (modMask & (1 << 5)) { list.push("Mod3"); }
if (modMask & (1 << 6)) { list.push("Super"); }
if (modMask & (1 << 7)) { list.push("Mod5"); }
return list;
}
property int maxBindWidth: 0
component BindLine: Row {
required property var keyData
Row {
spacing: 16
Row {
id: modRow
Component.onCompleted: root.maxBindWidth = Math.max(root.maxBindWidth, implicitWidth)
width: root.maxBindWidth
spacing: 4
Repeater {
model: {
const modList = root.modMaskToStringList(keyData.modmask)
if (modList.length == 0) return []
if (Config.options.cheatsheet.splitButtons) return modList;
return [modList.join(" ")]
}
delegate: KeyboardKey {
required property var modelData
key: root.keySubstitutions[modelData] || modelData
pixelSize: Config.options.cheatsheet.fontSize.key
}
}
StyledText {
id: keybindPlus
anchors.verticalCenter: parent.verticalCenter
visible: !keyBlacklist.includes(keyData.key) && keyData.modmask > 0
text: "+"
}
KeyboardKey {
id: keybindKey
anchors.verticalCenter: parent.verticalCenter
visible: !keyBlacklist.includes(keyData.key)
key: StringUtils.toTitleCase(root.keySubstitutions[keyData.key] || keyData.key)
pixelSize: Config.options.cheatsheet.fontSize.key
color: Appearance.colors.colOnLayer0
}
}
Item {
anchors.verticalCenter: parent.verticalCenter
implicitWidth: commentText.implicitWidth + root.columnSpacing
implicitHeight: commentText.implicitHeight
StyledText {
id: commentText
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
font.pixelSize: Config.options.cheatsheet.fontSize.comment || Appearance.font.pixelSize.smaller
text: keyData.description
} }
} }
} }
@@ -0,0 +1,177 @@
pragma ComponentBehavior: Bound
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
import Quickshell
Column {
id: root
required property string categoryName
readonly property bool isCategorized: categoryName?.length > 0
property int maxBindWidth: 0
property real columnSpacing: 40
property real titleSpacing: 7
// Excellent symbol explaination and source :
// http://xahlee.info/comp/unicode_computing_symbols.html
// https://www.nerdfonts.com/cheat-sheet
property var macSymbolMap: ({
"Ctrl": "󰘴",
"Alt": "󰘵",
"Shift": "󰘶",
"Space": "󱁐",
"Tab": "↹",
"Equal": "󰇼",
"Minus": "",
"Print": "",
"BackSpace": "󰭜",
"Delete": "⌦",
"Return": "󰌑",
"Period": ".",
"Escape": "⎋"
})
property var functionSymbolMap: ({
"F1": "󱊫",
"F2": "󱊬",
"F3": "󱊭",
"F4": "󱊮",
"F5": "󱊯",
"F6": "󱊰",
"F7": "󱊱",
"F8": "󱊲",
"F9": "󱊳",
"F10": "󱊴",
"F11": "󱊵",
"F12": "󱊶",
})
property var mouseSymbolMap: ({
"mouse_up": "󱕐",
"mouse_down": "󱕑",
"mouse:272": "L󰍽",
"mouse:273": "R󰍽",
"Scroll ↑/↓": "󱕒",
"Page_↑/↓": "⇞/⇟",
})
property var keyBlacklist: ["SUPER_L", "SUPER_R"]
property var keySubstitutions: Object.assign({
"Super": "",
"mouse_up": "Scroll ↓", // ikr, weird
"mouse_down": "Scroll ↑", // trust me bro
"mouse:272": "LMB",
"mouse:273": "RMB",
"mouse:275": "MouseBack",
"Slash": "/",
"Hash": "#",
"Return": "Enter",
// "Shift": "",
},
!!Config.options.cheatsheet.superKey ? {
"Super": Config.options.cheatsheet.superKey,
}: {},
Config.options.cheatsheet.useMacSymbol ? macSymbolMap : {},
Config.options.cheatsheet.useFnSymbol ? functionSymbolMap : {},
Config.options.cheatsheet.useMouseSymbol ? mouseSymbolMap : {},
)
function modMaskToStringList(modMask: int): list<string> {
var list = [];
// Funny mathematical order but we wanna have this natural user-facing order
if (modMask & (1 << 2)) { list.push("Ctrl"); }
if (modMask & (1 << 6)) { list.push("Super"); }
if (modMask & (1 << 0)) { list.push("Shift"); }
if (modMask & (1 << 3)) { list.push("Alt"); }
if (modMask & (1 << 1)) { list.push("Caps"); }
if (modMask & (1 << 4)) { list.push("Mod2"); }
if (modMask & (1 << 5)) { list.push("Mod3"); }
if (modMask & (1 << 7)) { list.push("Mod5"); }
return list;
}
spacing: titleSpacing
StyledText {
text: root.isCategorized ? root.categoryName : "Uncategorized"
font.pixelSize: Appearance.font.pixelSize.title
}
Column {
spacing: 4
Repeater {
model: {
if (!root.isCategorized) {
return HyprlandKeybinds.keybinds.filter(bind => bind.description?.length > 0 && bind.description.indexOf(":") === -1);
}
return HyprlandKeybinds.keybinds.filter(bind => bind.description?.length > 0 && bind.description.substring(0, bind.description.indexOf(":")) === root.categoryName);
}
delegate: BindLine {
required property var modelData
keyData: modelData
categoryName: root.categoryName
}
}
}
component BindLine: Row {
id: bindLine
required property var keyData
property string categoryName: ""
Row {
spacing: 16
Row {
id: modRow
Component.onCompleted: root.maxBindWidth = Math.max(root.maxBindWidth, implicitWidth)
width: root.maxBindWidth
spacing: 4
Repeater {
model: {
const modList = root.modMaskToStringList(bindLine.keyData.modmask).map(mod => root.keySubstitutions[mod] || mod)
if (modList.length == 0) return []
if (Config.options.cheatsheet.splitButtons) return modList;
return [modList.join(" ")]
}
delegate: KeyboardKey {
required property var modelData
key: modelData
pixelSize: Config.options.cheatsheet.fontSize.key
}
}
StyledText {
id: keybindPlus
anchors.verticalCenter: parent.verticalCenter
visible: !keyBlacklist.includes(bindLine.keyData.key) && bindLine.keyData.modmask > 0
text: "+"
}
KeyboardKey {
id: keybindKey
anchors.verticalCenter: parent.verticalCenter
visible: !keyBlacklist.includes(bindLine.keyData.key)
key: StringUtils.toTitleCase(root.keySubstitutions[bindLine.keyData.key] || bindLine.keyData.key)
pixelSize: Config.options.cheatsheet.fontSize.key
color: Appearance.colors.colOnLayer0
}
}
Item {
anchors.verticalCenter: parent.verticalCenter
implicitWidth: commentText.implicitWidth + root.columnSpacing
implicitHeight: commentText.implicitHeight
StyledText {
id: commentText
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
font.pixelSize: Config.options.cheatsheet.fontSize.comment || Appearance.font.pixelSize.smaller
text: {
const regex = new RegExp("\\s*" + bindLine.categoryName + "\\s*:\\s*");
return bindLine.keyData.description.replace(regex, "");
}
}
}
}
}
}
@@ -15,6 +15,7 @@ import Quickshell.Hyprland
Singleton { Singleton {
id: root id: root
property var keybinds: [] property var keybinds: []
property var keybindCategories: []
Connections { Connections {
target: Hyprland target: Hyprland
@@ -35,6 +36,15 @@ Singleton {
onStreamFinished: { onStreamFinished: {
try { try {
root.keybinds = JSON.parse(text) root.keybinds = JSON.parse(text)
var groups = []
for (var i = 0; i < root.keybinds.length; i++) {
var bind = root.keybinds[i].description
var group = bind.substring(0, bind.indexOf(":"))
if (!groups.includes(group) && group.length > 0) {
groups.push(group)
}
}
root.keybindCategories = groups
} catch (e) { } catch (e) {
console.error("[CheatsheetKeybinds] Error parsing keybinds:", e) console.error("[CheatsheetKeybinds] Error parsing keybinds:", e)
} }