From 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 Mon Sep 17 00:00:00 2001 From: AlexanderCurl Date: Sat, 18 Apr 2026 17:46:06 +0100 Subject: Replaced file structures for themes in the correct format --- .../quickshell/Modals/KeybindsModal.qml | 347 +++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/KeybindsModal.qml (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/KeybindsModal.qml') diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/KeybindsModal.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/KeybindsModal.qml new file mode 100644 index 0000000..e1d6765 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/KeybindsModal.qml @@ -0,0 +1,347 @@ +import QtQml +import QtQuick +import QtQuick.Layouts +import Quickshell.Hyprland +import qs.Common +import qs.Modals.Common +import qs.Services +import qs.Widgets + +DankModal { + id: root + + layerNamespace: "dms:keybinds" + useOverlayLayer: true + property real scrollStep: 60 + property var activeFlickable: null + property real _maxW: Math.min(Screen.width * 0.92, 1200) + property real _maxH: Math.min(Screen.height * 0.92, 900) + modalWidth: _maxW + modalHeight: _maxH + onBackgroundClicked: close() + onOpened: { + Qt.callLater(() => { + modalFocusScope.forceActiveFocus(); + if (contentLoader.item?.searchField) + contentLoader.item.searchField.forceActiveFocus(); + }); + if (!Object.keys(KeybindsService.cheatsheet).length && KeybindsService.cheatsheetAvailable) + KeybindsService.loadCheatsheet(); + } + + HyprlandFocusGrab { + windows: [root.contentWindow] + active: root.useHyprlandFocusGrab && root.shouldHaveFocus + } + + function scrollDown() { + if (!root.activeFlickable) + return; + let newY = root.activeFlickable.contentY + scrollStep; + newY = Math.min(newY, root.activeFlickable.contentHeight - root.activeFlickable.height); + root.activeFlickable.contentY = newY; + } + + function scrollUp() { + if (!root.activeFlickable) + return; + let newY = root.activeFlickable.contentY - root.scrollStep; + newY = Math.max(0, newY); + root.activeFlickable.contentY = newY; + } + + modalFocusScope.Keys.onPressed: event => { + if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) { + scrollDown(); + event.accepted = true; + } else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) { + scrollUp(); + event.accepted = true; + } else if (event.key === Qt.Key_Down) { + scrollDown(); + event.accepted = true; + } else if (event.key === Qt.Key_Up) { + scrollUp(); + event.accepted = true; + } + } + + content: Component { + Item { + anchors.fill: parent + property alias searchField: searchField + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingL + spacing: Theme.spacingL + + RowLayout { + width: parent.width + + StyledText { + Layout.alignment: Qt.AlignLeft + text: KeybindsService.cheatsheet.title || i18n("Keybinds") + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Bold + color: Theme.primary + } + + DankTextField { + id: searchField + Layout.alignment: Qt.AlignRight + leftIconName: "search" + keyForwardTargets: [root.modalFocusScope] + onTextEdited: searchDebounce.restart() + Keys.onEscapePressed: event => { + root.close(); + event.accepted = true; + } + } + } + + Timer { + id: searchDebounce + interval: 50 + repeat: false + onTriggered: { + mainFlickable.categories = mainFlickable.generateCategories(searchField.text); + } + } + + DankFlickable { + id: mainFlickable + width: parent.width + height: parent.height - parent.spacing - 40 + contentWidth: rowLayout.implicitWidth + contentHeight: rowLayout.implicitHeight + clip: true + + Component.onCompleted: root.activeFlickable = mainFlickable + + property var rawBinds: KeybindsService.cheatsheet.binds || {} + + function generateCategories(query) { + const lowerQuery = query ? query.toLowerCase().trim() : ""; + const lowerQueryWords = query.split(/\s+/); + const processed = {}; + + for (const cat in rawBinds) { + const binds = rawBinds[cat]; + const catLower = cat.toLowerCase(); + const subcats = {}; + let hasSubcats = false; + for (let i = 0; i < binds.length; i++) { + const bind = binds[i]; + const keyLower = bind.key.toLowerCase(); + const descLower = bind.desc.toLowerCase(); + const actionLower = bind.action.toLowerCase(); + + if (bind.hideOnOverlay) + continue; + let shouldContinue = false; + for (let j = 0; j < lowerQueryWords.length; j++) { + const word = lowerQueryWords[j]; + if (!( + word.length === 0 || + keyLower.includes(word) || + descLower.includes(word) || + catLower.includes(word) || + actionLower.includes(word) + )) { + shouldContinue = true; + break; + } + } + if (shouldContinue) + continue; + + if (bind.subcat) { + hasSubcats = true; + if (!subcats[bind.subcat]) + subcats[bind.subcat] = []; + subcats[bind.subcat].push(bind); + } else { + if (!subcats["_root"]) + subcats["_root"] = []; + subcats["_root"].push(bind); + } + } + + if (Object.keys(subcats).length === 0) + continue; + + processed[cat] = { + hasSubcats: hasSubcats, + subcats: subcats, + subcatKeys: Object.keys(subcats) + }; + } + + return processed; + } + + property var categories: generateCategories(""); + + function estimateCategoryHeight(catName) { + const catData = categories[catName]; + if (!catData) + return 0; + let bindCount = 0; + for (const key of catData.subcatKeys) { + bindCount += catData.subcats[key]?.length || 0; + if (key !== "_root") + bindCount += 1; + } + return 40 + bindCount * 28; + } + + property var categoryKeys: Object.keys(categories); + + function distributeCategories(cols) { + const columns = []; + const heights = []; + for (let i = 0; i < cols; i++) { + columns.push([]); + heights.push(0); + } + const sorted = [...categoryKeys].sort((a, b) => estimateCategoryHeight(b) - estimateCategoryHeight(a)); + for (const cat of sorted) { + let minIdx = 0; + for (let i = 1; i < cols; i++) { + if (heights[i] < heights[minIdx]) + minIdx = i; + } + columns[minIdx].push(cat); + heights[minIdx] += estimateCategoryHeight(cat); + } + return columns; + } + + Row { + id: rowLayout + width: mainFlickable.width + spacing: Theme.spacingM + + property int numColumns: Math.max(1, Math.min(3, Math.floor(width / 350))) + property var columnCategories: mainFlickable.distributeCategories(numColumns) + + Repeater { + model: rowLayout.numColumns + + Column { + id: masonryColumn + width: (rowLayout.width - rowLayout.spacing * (rowLayout.numColumns - 1)) / rowLayout.numColumns + spacing: Theme.spacingXL + + Repeater { + model: rowLayout.columnCategories[index] || [] + + Column { + id: categoryColumn + width: parent.width + spacing: Theme.spacingXS + + property string catName: modelData + property var catData: mainFlickable.categories[catName] + + StyledText { + text: categoryColumn.catName + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Bold + color: Theme.primary + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.primary + opacity: 0.3 + } + + Item { + width: 1 + height: Theme.spacingXS + } + + Column { + width: parent.width + spacing: Theme.spacingM + + Repeater { + model: categoryColumn.catData?.subcatKeys || [] + + Column { + width: parent.width + spacing: Theme.spacingXS + + property string subcatName: modelData + property var subcatBinds: categoryColumn.catData?.subcats?.[subcatName] || [] + + StyledText { + visible: parent.subcatName !== "_root" + text: parent.subcatName + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.DemiBold + color: Theme.primary + opacity: 0.7 + } + + Column { + width: parent.width + spacing: Theme.spacingXS + + Repeater { + model: parent.parent.subcatBinds + + Item { + width: parent.width + height: 24 + + StyledRect { + id: keyBadge + width: Math.min(keyText.implicitWidth + 12, 160) + height: 22 + radius: 4 + anchors.verticalCenter: parent.verticalCenter + + StyledText { + id: keyText + anchors.centerIn: parent + color: Theme.secondary + text: (modelData.key || "").replace(/\+/g, " + ") + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + isMonospace: true + elide: Text.ElideRight + width: Math.min(implicitWidth, 148) + } + } + + StyledText { + anchors.left: parent.left + anchors.leftMargin: 170 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: modelData.desc || modelData.action || "" + font.pixelSize: Theme.fontSizeSmall + opacity: 0.9 + elide: Text.ElideRight + wrapMode: Text.NoWrap + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} -- cgit v1.3