From d1daedc6d2ec69419b3d392d5a0c3d5bb1ed9ac1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 14 May 2026 09:37:45 +0200 Subject: [PATCH] qs: cheatsheet: display categories nicely --- .../ii/cheatsheet/CheatsheetKeybinds.qml | 146 +-------------- .../cheatsheet/CheatsheetKeybindsCategory.qml | 177 ++++++++++++++++++ .../ii/services/HyprlandKeybinds.qml | 10 + 3 files changed, 193 insertions(+), 140 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybindsCategory.qml diff --git a/dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybinds.qml b/dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybinds.qml index ff194e562..aab5a993f 100644 --- a/dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybinds.qml +++ b/dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybinds.qml @@ -10,76 +10,9 @@ import Quickshell Item { 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 - implicitWidth: QsWindow.window.screen.width * 0.7 - implicitHeight: QsWindow.window.screen.height * 0.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 : {}, - ) + implicitWidth: QsWindow?.window?.screen.width * 0.7 ?? 0 + implicitHeight: QsWindow?.window?.screen.height * 0.7 ?? 0 StyledFlickable { id: flickable @@ -91,79 +24,12 @@ Item { id: flow height: flickable.height flow: Flow.TopToBottom - spacing: 4 + spacing: 12 Repeater { - model: root.keybinds - delegate: BindLine { + model: [...HyprlandKeybinds.keybindCategories, ""] + delegate: CheatsheetKeybindsCategory { required property var modelData - keyData: modelData - } - } - } - } - - function modMaskToStringList(modMask: int): list { - 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 + categoryName: modelData } } } diff --git a/dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybindsCategory.qml b/dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybindsCategory.qml new file mode 100644 index 000000000..7189837dd --- /dev/null +++ b/dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybindsCategory.qml @@ -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 { + 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, ""); + } + } + } + } + } +} \ No newline at end of file diff --git a/dots/.config/quickshell/ii/services/HyprlandKeybinds.qml b/dots/.config/quickshell/ii/services/HyprlandKeybinds.qml index f24c6e055..4c3e73bf3 100644 --- a/dots/.config/quickshell/ii/services/HyprlandKeybinds.qml +++ b/dots/.config/quickshell/ii/services/HyprlandKeybinds.qml @@ -15,6 +15,7 @@ import Quickshell.Hyprland Singleton { id: root property var keybinds: [] + property var keybindCategories: [] Connections { target: Hyprland @@ -35,6 +36,15 @@ Singleton { onStreamFinished: { try { 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) { console.error("[CheatsheetKeybinds] Error parsing keybinds:", e) }