summaryrefslogtreecommitdiff
path: root/noctalia/.config/plugins/keybind-cheatsheet/Panel.qml
diff options
context:
space:
mode:
authorseraphim <18ycm9tx@anonaddy.me>2026-07-31 14:03:07 +0700
committerseraphim <18ycm9tx@anonaddy.me>2026-07-31 14:03:07 +0700
commit4835f660f9435fa64336daaaae1ea3cab1dfbee6 (patch)
tree45524b7f7cdb6009fa85558cea44678b2b563a43 /noctalia/.config/plugins/keybind-cheatsheet/Panel.qml
parent59226408d8bc950d7ec8c21623f28b5f7de8fce0 (diff)
mouse
Diffstat (limited to 'noctalia/.config/plugins/keybind-cheatsheet/Panel.qml')
-rw-r--r--noctalia/.config/plugins/keybind-cheatsheet/Panel.qml687
1 files changed, 0 insertions, 687 deletions
diff --git a/noctalia/.config/plugins/keybind-cheatsheet/Panel.qml b/noctalia/.config/plugins/keybind-cheatsheet/Panel.qml
deleted file mode 100644
index 946431e..0000000
--- a/noctalia/.config/plugins/keybind-cheatsheet/Panel.qml
+++ /dev/null
@@ -1,687 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import Quickshell
-import Quickshell.Io
-import qs.Commons
-import qs.Services.UI
-import qs.Services.Compositor
-import qs.Widgets
-
-Item {
- id: root
- property var pluginApi: null
-
- // Settings
- property var cfg: pluginApi?.pluginSettings || ({})
- property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
-
- // Settings values
- property int settingsWidth: cfg.windowWidth ?? defaults.windowWidth ?? 1400
- property int settingsHeight: cfg.windowHeight ?? defaults.windowHeight ?? 0
- property bool autoHeight: cfg.autoHeight ?? defaults.autoHeight ?? true
- property int columnCount: cfg.columnCount ?? defaults.columnCount ?? 3
-
- // Bug 4 fix: re-evaluate rawCategories whenever Main.qml increments cheatsheetDataVersion
- property int _dataVersion: pluginApi?.mainInstance?.cheatsheetDataVersion ?? 0
- property var rawCategories: {
- var _v = _dataVersion; // force QML dependency on version counter
- return pluginApi?.pluginSettings?.cheatsheetData || [];
- }
- property var categories: []
-
- // Bug 5 fix: timeout if parsing never completes
- property bool loadingTimedOut: false
-
- Timer {
- id: loadingTimeoutTimer
- interval: 4000
- repeat: false
- running: false
- onTriggered: {
- if (root.isLoading) {
- root.loadingTimedOut = true;
- }
- }
- }
-
- Component.onCompleted: {
- categories = processCategories(rawCategories);
- // Start timeout timer if we have no data yet
- if (root.isLoading) {
- loadingTimeoutTimer.start();
- }
- }
-
-
- // Dynamic column items (up to 4 columns)
- property var columnItems: []
-
- // Memory leak prevention: debounce column updates
- Timer {
- id: columnUpdateDebounce
- interval: 100
- repeat: false
- onTriggered: updateColumnItemsNow()
- }
-
- Component.onDestruction: {
- // Stop timer to prevent firing after destruction
- columnUpdateDebounce.stop();
- loadingTimeoutTimer.stop();
-
- // Clear column items
- columnItems = [];
- }
-
- onRawCategoriesChanged: {
- categories = processCategories(rawCategories);
- updateColumnItems();
- }
-
- onCategoriesChanged: {
- updateColumnItems();
- contentPreferredHeight = calculateDynamicHeight();
- }
-
- onColumnCountChanged: {
- updateColumnItems();
- contentPreferredHeight = calculateDynamicHeight();
- }
-
- onPanelOpenScreenChanged: {
- // Recalculate height when screen becomes available (important for bar widget opening)
- contentPreferredHeight = calculateDynamicHeight();
- root.searchText = "";
- if (searchInput) searchInput.inputItem.forceActiveFocus();
- }
-
- onMaxScreenHeightChanged: {
- contentPreferredHeight = calculateDynamicHeight();
- }
-
- function updateColumnItems() {
- columnUpdateDebounce.restart();
- }
-
- function updateColumnItemsNow() {
- columnItems = []; // Clear old items explicitly
- var assignments = distributeCategories();
- var items = [];
- for (var i = 0; i < columnCount; i++) {
- items.push(buildColumnItems(assignments[i] || []));
- }
- columnItems = items;
- }
-
- // Screen height limit (90% of screen)
- property var panelOpenScreen: pluginApi?.panelOpenScreen
- property real maxScreenHeight: panelOpenScreen ? panelOpenScreen.height * 0.9 : 800
-
- property string searchText: ""
-
- property real contentPreferredWidth: settingsWidth
- property real contentPreferredHeight: calculateDynamicHeight()
- readonly property var geometryPlaceholder: panelContainer
- readonly property bool allowAttach: false
- readonly property bool panelAnchorHorizontalCenter: true
- readonly property bool panelAnchorVerticalCenter: true
- anchors.fill: parent
-
- // Key badge colors — read from settings with manifest defaults as fallback
- readonly property color keyColorAlt: cfg.keyColorAlt || defaults.keyColorAlt || "#FF6B6B"
- readonly property color keyColorXF86: cfg.keyColorXF86 || defaults.keyColorXF86 || "#4ECDC4"
- readonly property color keyColorPrint: cfg.keyColorPrint || defaults.keyColorPrint || "#95E1D3"
- readonly property color keyColorNumeric: cfg.keyColorNumeric || defaults.keyColorNumeric || "#A8DADC"
- readonly property color keyColorMouse: cfg.keyColorMouse || defaults.keyColorMouse || "#F38181"
- // Empty string = use theme color (mPrimary/mSecondary/mTertiary)
- readonly property string keyColorSuperOverride: cfg.keyColorSuper ?? defaults.keyColorSuper ?? ""
- readonly property string keyColorCtrlOverride: cfg.keyColorCtrl ?? defaults.keyColorCtrl ?? ""
- readonly property string keyColorShiftOverride: cfg.keyColorShift ?? defaults.keyColorShift ?? ""
- readonly property color keyColorDefault: cfg.keyColorDefault || defaults.keyColorDefault || "#6C757D"
- readonly property color keyLabelColor: cfg.keyLabelColor || defaults.keyLabelColor || "#FFFFFF"
- // Empty string = theme-aware fallback (Color.mOnSurface). Any non-empty
- // value is treated as an explicit user override.
- readonly property string descriptionColorOverride: cfg.descriptionTextColor || defaults.descriptionTextColor || ""
- readonly property color descriptionTextColor: descriptionColorOverride !== "" ? descriptionColorOverride : Color.mOnSurface
-
- // Per-category text color overrides (empty = fall back to keyLabelColor)
- readonly property string keyTextSuperOverride: cfg.keyTextSuper ?? defaults.keyTextSuper ?? ""
- readonly property string keyTextCtrlOverride: cfg.keyTextCtrl ?? defaults.keyTextCtrl ?? ""
- readonly property string keyTextShiftOverride: cfg.keyTextShift ?? defaults.keyTextShift ?? ""
- readonly property string keyTextAltOverride: cfg.keyTextAlt ?? defaults.keyTextAlt ?? ""
- readonly property string keyTextXF86Override: cfg.keyTextXF86 ?? defaults.keyTextXF86 ?? ""
- readonly property string keyTextPrintOverride: cfg.keyTextPrint ?? defaults.keyTextPrint ?? ""
- readonly property string keyTextNumericOverride: cfg.keyTextNumeric ?? defaults.keyTextNumeric ?? ""
- readonly property string keyTextMouseOverride: cfg.keyTextMouse ?? defaults.keyTextMouse ?? ""
- readonly property string keyTextDefaultOverride: cfg.keyTextDefault ?? defaults.keyTextDefault ?? ""
-
- // The generic X11 mods (Mod2/Mod3/Mod5) are first-class, customizable keys:
- // background colour, text colour, and a display-label override that renames
- // them to something meaningful (e.g. Mod3 -> "Hyper" for caps:hyper). Empty
- // colour = fall back to the default key colour; empty label = raw mod name.
- readonly property string keyColorMod2Override: cfg.keyColorMod2 ?? defaults.keyColorMod2 ?? ""
- readonly property string keyColorMod3Override: cfg.keyColorMod3 ?? defaults.keyColorMod3 ?? ""
- readonly property string keyColorMod5Override: cfg.keyColorMod5 ?? defaults.keyColorMod5 ?? ""
- readonly property string keyTextMod2Override: cfg.keyTextMod2 ?? defaults.keyTextMod2 ?? ""
- readonly property string keyTextMod3Override: cfg.keyTextMod3 ?? defaults.keyTextMod3 ?? ""
- readonly property string keyTextMod5Override: cfg.keyTextMod5 ?? defaults.keyTextMod5 ?? ""
- readonly property string keyLabelMod2Override: cfg.keyLabelMod2 ?? defaults.keyLabelMod2 ?? ""
- readonly property string keyLabelMod3Override: cfg.keyLabelMod3 ?? defaults.keyLabelMod3 ?? ""
- readonly property string keyLabelMod5Override: cfg.keyLabelMod5 ?? defaults.keyLabelMod5 ?? ""
-
- // Workspace category split tuning
- readonly property bool splitWorkspaces: cfg.splitLargeWorkspaceCategory ?? defaults.splitLargeWorkspaceCategory ?? true
- readonly property int workspaceSplitThreshold: cfg.workspaceSplitThreshold ?? defaults.workspaceSplitThreshold ?? 12
-
- // Data is loaded by Main.qml, we just display it
- property bool isLoading: rawCategories.length === 0
-
- function calculateDynamicHeight() {
- // If auto height is disabled, use manual height (but still respect screen limit)
- if (!autoHeight && settingsHeight > 0) {
- return Math.min(settingsHeight, maxScreenHeight);
- }
-
- if (categories.length === 0) return Math.min(400, maxScreenHeight);
-
- var assignments = distributeCategories();
- var maxColumnHeight = 0;
-
- for (var col = 0; col < columnCount; col++) {
- var colHeight = 0;
- var catIndices = assignments[col] || [];
-
- for (var i = 0; i < catIndices.length; i++) {
- var catIndex = catIndices[i];
- if (catIndex >= categories.length) continue;
-
- var cat = categories[catIndex];
- colHeight += 26; // Header
- colHeight += cat.binds.length * 20; // Binds
- if (i < catIndices.length - 1) {
- colHeight += 6; // Spacer
- }
- }
-
- if (colHeight > maxColumnHeight) {
- maxColumnHeight = colHeight;
- }
- }
-
- // header (45) + content + margins (16)
- var totalHeight = 45 + maxColumnHeight + 16 + 15 + 15;
- // Limit to 80% of screen height
- return Math.max(300, Math.min(totalHeight, maxScreenHeight));
- }
-
- // ========== UI ==========
- Rectangle {
- id: panelContainer
- anchors.fill: parent
- color: "transparent"
- radius: Style.radiusL
- clip: true
-
- Rectangle {
- id: header
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- height: 45
- color: Color.mSurfaceVariant
- radius: Style.radiusL
-
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: Style.marginM
- anchors.rightMargin: Style.marginM
- spacing: Style.marginS
-
- // Title section (centered)
- Item { Layout.fillWidth: true }
-
- NIcon {
- icon: "keyboard"
- pointSize: Style.fontSizeM
- color: Color.mPrimary
- }
- NText {
- text: CompositorService.isHyprland ? pluginApi?.tr("panel.title-hyprland") :
- CompositorService.isNiri ? pluginApi?.tr("panel.title-niri") :
- CompositorService.isMango ? pluginApi?.tr("panel.title-mango") :
- pluginApi?.tr("panel.title")
- font.pointSize: Style.fontSizeM
- font.weight: Font.Bold
- color: Color.mPrimary
- }
-
- NTextInput {
- id: searchInput
- placeholderText: pluginApi?.tr("panel.search-placeholder")
- text: root.searchText
-
- onTextChanged: {
- root.searchText = text;
- root.updateColumnItems();
- }
- }
-
- Item { Layout.fillWidth: true }
-
- // Refresh button
- NIconButton {
- icon: "refresh"
- onClicked: {
- pluginApi?.mainInstance?.refresh();
- }
- }
-
- // Settings button
- NIconButton {
- icon: "settings"
- onClicked: {
- var screen = pluginApi?.panelOpenScreen;
- if (screen && pluginApi?.manifest) {
- pluginApi.closePanel(screen);
- BarService.openPluginSettings(screen, pluginApi.manifest);
- }
- }
- }
- }
- }
-
- NText {
- id: loadingText
- anchors.centerIn: parent
- text: root.loadingTimedOut ? pluginApi?.tr("panel.loading-timeout") : pluginApi?.tr("panel.loading")
- visible: root.isLoading
- font.pointSize: Style.fontSizeL
- color: Color.mOnSurface
- }
-
- NScrollView {
- id: scrollView
- visible: root.categories.length > 0 && !root.isLoading
- anchors.top: header.bottom
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- clip: true
- leftPadding: 35
- rightPadding: -10
- topPadding: 15
- bottomPadding: 15
-
- RowLayout {
- id: mainLayout
- width: scrollView.availableWidth - Style.marginS
- spacing: Style.marginS
-
- Repeater {
- model: root.columnItems.length
-
- ColumnLayout {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignTop
- spacing: 2
-
- property var colItems: root.columnItems[index] || []
-
- Repeater {
- model: colItems
- Loader {
- Layout.fillWidth: true
- sourceComponent: modelData.type === "header" ? headerComponent :
- (modelData.type === "spacer" ? spacerComponent : bindComponent)
- property var itemData: modelData
-
- // Memory leak prevention: explicit cleanup
- Component.onDestruction: {
- active = false;
- sourceComponent = undefined;
- }
- }
- }
- }
- }
- }
- }
- }
-
- Component {
- id: headerComponent
- ColumnLayout {
- Layout.preferredWidth: 300
- Layout.topMargin: Style.marginM
- Layout.bottomMargin: 4
- spacing: 0
-
- Item { Layout.fillWidth: true; height: 1 }
-
- NText {
- Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
- x: parent.width - implicitWidth
- text: itemData.title
- font.pointSize: Style.fontSizeM
- font.weight: Font.Bold
- color: Color.mPrimary
- }
-
- Item { Layout.fillWidth: true; height: 1 }
- }
- }
-
-
- Component {
- id: spacerComponent
- Item {
- height: 10
- Layout.fillWidth: true
- }
- }
-
- Component {
- id: bindComponent
- RowLayout {
- id: bindRow
- spacing: Style.marginS
- height: 22
- Layout.bottomMargin: 1
-
- property bool editing: false
-
- Flow {
- Layout.preferredWidth: 220
- Layout.alignment: Qt.AlignVCenter
- spacing: 3
- Repeater {
- model: itemData.keys.split(" + ")
- Rectangle {
- width: keyText.implicitWidth + 10
- height: 18
- color: getKeyColor(modelData)
- radius: 3
- NText {
- id: keyText
- anchors.centerIn: parent
- text: getKeyLabel(modelData)
- font.pointSize: text.length > 12 ? 7 : 8
- font.weight: Font.Bold
- color: getKeyTextColor(modelData)
- }
- }
- }
- }
-
- // Described bind: plain text (unchanged behaviour)
- NText {
- visible: !itemData.undescribed
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- text: itemData.desc
- font.pointSize: Style.fontSizeXS
- color: root.descriptionTextColor
- elide: Text.ElideRight
- }
-
- // Undescribed bind: placeholder + add-description / hide actions
- NText {
- visible: itemData.undescribed && !bindRow.editing
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- text: pluginApi?.tr("panel.no-description")
- font.pointSize: Style.fontSizeXS
- font.italic: true
- color: Color.mOnSurfaceVariant
- elide: Text.ElideRight
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: { bindRow.editing = true; descInput.text = ""; descInput.inputItem.forceActiveFocus(); }
- }
- }
-
- NTextInput {
- id: descInput
- visible: itemData.undescribed && bindRow.editing
- Layout.fillWidth: true
- Layout.preferredHeight: Style.baseWidgetSize
- placeholderText: pluginApi?.tr("panel.add-description-placeholder")
- }
-
- NIconButton {
- visible: itemData.undescribed && bindRow.editing
- Layout.preferredHeight: 18
- icon: "check"
- tooltipText: pluginApi?.tr("panel.save-description")
- onClicked: {
- root.saveBindDescription(itemData.bindId, descInput.text);
- bindRow.editing = false;
- }
- }
-
- NIconButton {
- visible: itemData.undescribed && bindRow.editing
- Layout.preferredHeight: 18
- icon: "close"
- tooltipText: pluginApi?.tr("panel.cancel")
- onClicked: { bindRow.editing = false; }
- }
-
- NIconButton {
- visible: itemData.undescribed && !bindRow.editing
- Layout.preferredHeight: 18
- icon: "edit"
- tooltipText: pluginApi?.tr("panel.add-description")
- onClicked: { bindRow.editing = true; descInput.text = ""; descInput.inputItem.forceActiveFocus(); }
- }
-
- NIconButton {
- visible: itemData.undescribed && !bindRow.editing
- Layout.preferredHeight: 18
- icon: "eye-off"
- tooltipText: pluginApi?.tr("panel.hide-bind")
- onClicked: { root.hideBind(itemData.bindId); }
- }
- }
- }
-
- function getKeyColor(keyName) {
- if (keyName === "Super") return root.keyColorSuperOverride || Color.mPrimary;
- if (keyName === "Ctrl") return root.keyColorCtrlOverride || Color.mSecondary;
- if (keyName === "Shift") return root.keyColorShiftOverride || Color.mTertiary;
- if (keyName === "Alt") return root.keyColorAlt;
- if (keyName.startsWith("XF86")) return root.keyColorXF86;
- if (keyName === "PRINT" || keyName === "Print") return root.keyColorPrint;
- if (keyName.match(/^[0-9]$/)) return root.keyColorNumeric;
- if (keyName.includes("MOUSE") || keyName.includes("Wheel")) return root.keyColorMouse;
- if (keyName === "Mod2") return root.keyColorMod2Override || root.keyColorDefault;
- if (keyName === "Mod3") return root.keyColorMod3Override || root.keyColorDefault;
- if (keyName === "Mod5") return root.keyColorMod5Override || root.keyColorDefault;
- return root.keyColorDefault;
- }
-
- function getKeyTextColor(keyName) {
- if (keyName === "Super") return root.keyTextSuperOverride || root.keyLabelColor;
- if (keyName === "Ctrl") return root.keyTextCtrlOverride || root.keyLabelColor;
- if (keyName === "Shift") return root.keyTextShiftOverride || root.keyLabelColor;
- if (keyName === "Alt") return root.keyTextAltOverride || root.keyLabelColor;
- if (keyName.startsWith("XF86")) return root.keyTextXF86Override || root.keyLabelColor;
- if (keyName === "PRINT" || keyName === "Print") return root.keyTextPrintOverride || root.keyLabelColor;
- if (keyName.match(/^[0-9]$/)) return root.keyTextNumericOverride || root.keyLabelColor;
- if (keyName.includes("MOUSE") || keyName.includes("Wheel")) return root.keyTextMouseOverride || root.keyLabelColor;
- if (keyName === "Mod2") return root.keyTextMod2Override || root.keyLabelColor;
- if (keyName === "Mod3") return root.keyTextMod3Override || root.keyLabelColor;
- if (keyName === "Mod5") return root.keyTextMod5Override || root.keyLabelColor;
- return root.keyTextDefaultOverride || root.keyLabelColor;
- }
-
- // Display label for a key token. Lets users rename the generic X11 modifier
- // names (e.g. Mod3 -> "Hyper"); everything else renders as-is. Purely visual —
- // colour lookup and the cached bind still use the original token.
- function getKeyLabel(keyName) {
- if (keyName === "Mod2" && root.keyLabelMod2Override) return root.keyLabelMod2Override;
- if (keyName === "Mod3" && root.keyLabelMod3Override) return root.keyLabelMod3Override;
- if (keyName === "Mod5" && root.keyLabelMod5Override) return root.keyLabelMod5Override;
- return keyName;
- }
-
- // ===== Bind override helpers (shared keyed map in plugin settings) =====
- function _cloneOverrides() {
- if (!pluginApi || !pluginApi.pluginSettings) return ({});
- var src = pluginApi.pluginSettings.bindOverrides || ({});
- try { return JSON.parse(JSON.stringify(src)); } catch (e) { return ({}); }
- }
-
- function saveBindDescription(bindId, desc) {
- if (!pluginApi || !bindId) return;
- var o = _cloneOverrides();
- if (!o[bindId]) o[bindId] = ({});
- var trimmed = (desc || "").trim();
- if (trimmed.length === 0) {
- delete o[bindId].desc;
- if (Object.keys(o[bindId]).length === 0) delete o[bindId];
- } else {
- o[bindId].desc = trimmed;
- }
- pluginApi.pluginSettings.bindOverrides = o;
- pluginApi.saveSettings();
- pluginApi.mainInstance?.refresh();
- }
-
- function hideBind(bindId) {
- if (!pluginApi || !bindId) return;
- var o = _cloneOverrides();
- if (!o[bindId]) o[bindId] = ({});
- o[bindId].hidden = true;
- pluginApi.pluginSettings.bindOverrides = o;
- pluginApi.saveSettings();
- pluginApi.mainInstance?.refresh();
- }
-
- function buildColumnItems(categoryIndices) {
- var result = [];
- if (!categoryIndices) return result;
-
- for (var i = 0; i < categoryIndices.length; i++) {
- var catIndex = categoryIndices[i];
- if (catIndex >= categories.length) continue;
-
- var cat = categories[catIndex];
- result.push({ type: "header", title: cat.title });
- var term = root.searchText.toLowerCase();
- for (var j = 0; j < cat.binds.length; j++) {
- var bnd = cat.binds[j];
- var isUndesc = bnd.undescribed === true;
- if (!term || (bnd.desc && bnd.desc.toLowerCase().indexOf(term) !== -1) || (isUndesc && bnd.keys.toLowerCase().indexOf(term) !== -1)) {
- result.push({
- type: "bind",
- keys: bnd.keys,
- desc: bnd.desc,
- bindId: bnd.bindId || "",
- undescribed: isUndesc
- });
- }
- }
- if (i < categoryIndices.length - 1) {
- result.push({ type: "spacer" });
- }
- }
- return result;
- }
-
- function processCategories(cats) {
- if (!cats || cats.length === 0) return [];
- if (!root.splitWorkspaces) return cats;
-
- var result = [];
- for (var i = 0; i < cats.length; i++) {
- var cat = cats[i];
- if (!cat.binds || cat.binds.length <= root.workspaceSplitThreshold) {
- result.push(cat);
- continue;
- }
-
- // Detect a category dominated by workspace verbs.
- // Hyprland verbs: workspace, movetoworkspace, movetoworkspacesilent, movecurrentworkspacetomonitor
- // Niri verbs: focus-workspace, move-window-to-workspace, move-column-to-workspace
- var workspaceCount = 0;
- for (var k = 0; k < cat.binds.length; k++) {
- var v = cat.binds[k]._verb || "";
- if (v.indexOf("workspace") !== -1) workspaceCount++;
- }
- var workspaceDominated = workspaceCount >= Math.ceil(cat.binds.length * 0.6);
- if (!workspaceDominated) {
- result.push(cat);
- continue;
- }
-
- var switching = [], moving = [], mouse = [];
- for (var j = 0; j < cat.binds.length; j++) {
- var bind = cat.binds[j];
- var verb = bind._verb || "";
- var isMouse = (bind._mainKey || "").indexOf("MOUSE") !== -1 ||
- (bind.keys || "").indexOf("MOUSE") !== -1 ||
- (bind.keys || "").indexOf("Wheel") !== -1;
-
- if (isMouse) {
- mouse.push(bind);
- } else if (verb.indexOf("move") !== -1 || verb.indexOf("send") !== -1) {
- // Hyprland: movetoworkspace*, Niri: move-window-to-workspace, move-column-to-workspace
- moving.push(bind);
- } else {
- // Hyprland: workspace, Niri: focus-workspace
- switching.push(bind);
- }
- }
-
- if (switching.length > 0) result.push({ title: pluginApi?.tr("panel.workspace-switching"), binds: switching });
- if (moving.length > 0) result.push({ title: pluginApi?.tr("panel.workspace-moving"), binds: moving });
- if (mouse.length > 0) result.push({ title: pluginApi?.tr("panel.workspace-mouse"), binds: mouse });
- }
- return result;
- }
-
- function distributeCategories() {
- var numCols = root.columnCount;
-
- // Calculate weights for each category
- var catData = [];
- for (var i = 0; i < categories.length; i++) {
- var weight = 1 + categories[i].binds.length + 1; // header + binds + spacer
- catData.push({ index: i, weight: weight });
- }
-
- // Sort by weight descending (largest categories first for better distribution)
- catData.sort(function(a, b) { return b.weight - a.weight; });
-
- var columns = [];
- var columnWeights = [];
- for (var c = 0; c < numCols; c++) {
- columns.push([]);
- columnWeights.push(0);
- }
-
- // Assign each category to the column with smallest current weight
- for (var i = 0; i < catData.length; i++) {
- var minCol = 0;
- for (var c = 1; c < numCols; c++) {
- if (columnWeights[c] < columnWeights[minCol]) {
- minCol = c;
- }
- }
- columns[minCol].push(catData[i].index);
- columnWeights[minCol] += catData[i].weight;
- }
-
- // Sort categories within each column by original order for consistent display
- for (var c = 0; c < numCols; c++) {
- columns[c].sort(function(a, b) { return a - b; });
- }
-
- return columns;
- }
-
-}