From 5d94c0a7d44a2255b81815a52a7056a94a39842d Mon Sep 17 00:00:00 2001 From: nippy Date: Sat, 18 Apr 2026 13:49:56 +0200 Subject: update Raveos themes --- .../Center/NotificationKeyboardController.qml | 556 +++++++++++++++++++++ 1 file changed, 556 insertions(+) create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml') diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml new file mode 100644 index 0000000..eda12c0 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml @@ -0,0 +1,556 @@ +import QtQuick +import qs.Services + +QtObject { + id: controller + + property var listView: null + property bool isOpen: false + property var onClose: null + + property int selectionVersion: 0 + + property bool keyboardNavigationActive: false + property int selectedFlatIndex: 0 + property var flatNavigation: [] + property bool showKeyboardHints: false + + property string selectedNotificationId: "" + property string selectedGroupKey: "" + property string selectedItemType: "" + property bool isTogglingGroup: false + property bool isRebuilding: false + + function rebuildFlatNavigation() { + isRebuilding = true; + + const nav = []; + const groups = NotificationService.groupedNotifications; + + for (var i = 0; i < groups.length; i++) { + const group = groups[i]; + const isExpanded = NotificationService.expandedGroups[group.key] || false; + + nav.push({ + "type": "group", + "groupIndex": i, + "notificationIndex": -1, + "groupKey": group.key, + "notificationId": "" + }); + + if (isExpanded) { + const notifications = group.notifications || []; + const maxNotifications = Math.min(notifications.length, 10); + for (var j = 0; j < maxNotifications; j++) { + const notifId = String(notifications[j] && notifications[j].notification && notifications[j].notification.id ? notifications[j].notification.id : ""); + nav.push({ + "type": "notification", + "groupIndex": i, + "notificationIndex": j, + "groupKey": group.key, + "notificationId": notifId + }); + } + } + } + + flatNavigation = nav; + updateSelectedIndexFromId(); + isRebuilding = false; + } + + function updateSelectedIndexFromId() { + if (!keyboardNavigationActive) { + return; + } + + for (var i = 0; i < flatNavigation.length; i++) { + const item = flatNavigation[i]; + + if (selectedItemType === "group" && item.type === "group" && item.groupKey === selectedGroupKey) { + selectedFlatIndex = i; + selectionVersion++; // Trigger UI update + return; + } else if (selectedItemType === "notification" && item.type === "notification" && String(item.notificationId) === String(selectedNotificationId)) { + selectedFlatIndex = i; + selectionVersion++; // Trigger UI update + return; + } + } + + // If not found, try to find the same group but select the group header instead + if (selectedItemType === "notification") { + for (var j = 0; j < flatNavigation.length; j++) { + const groupItem = flatNavigation[j]; + if (groupItem.type === "group" && groupItem.groupKey === selectedGroupKey) { + selectedFlatIndex = j; + selectedItemType = "group"; + selectedNotificationId = ""; + selectionVersion++; // Trigger UI update + return; + } + } + } + + // If still not found, clamp to valid range and update + if (flatNavigation.length > 0) { + selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1); + selectedFlatIndex = Math.max(selectedFlatIndex, 0); + updateSelectedIdFromIndex(); + selectionVersion++; // Trigger UI update + } + } + + function updateSelectedIdFromIndex() { + if (selectedFlatIndex >= 0 && selectedFlatIndex < flatNavigation.length) { + const item = flatNavigation[selectedFlatIndex]; + selectedItemType = item.type; + selectedGroupKey = item.groupKey; + selectedNotificationId = item.notificationId; + } + } + + function reset() { + selectedFlatIndex = 0; + keyboardNavigationActive = false; + showKeyboardHints = false; + // Reset keyboardActive when modal is reset + if (listView) { + listView.keyboardActive = false; + } + rebuildFlatNavigation(); + } + + function selectNext() { + keyboardNavigationActive = true; + if (flatNavigation.length === 0) + return; + + // Re-enable auto-scrolling when arrow keys are used + if (listView && listView.enableAutoScroll) { + listView.enableAutoScroll(); + } + + selectedFlatIndex = Math.min(selectedFlatIndex + 1, flatNavigation.length - 1); + updateSelectedIdFromIndex(); + selectionVersion++; + ensureVisible(); + } + + function selectNextWrapping() { + keyboardNavigationActive = true; + if (flatNavigation.length === 0) + return; + + // Re-enable auto-scrolling when arrow keys are used + if (listView && listView.enableAutoScroll) { + listView.enableAutoScroll(); + } + + selectedFlatIndex = (selectedFlatIndex + 1) % flatNavigation.length; + updateSelectedIdFromIndex(); + selectionVersion++; + ensureVisible(); + } + + function selectPrevious() { + keyboardNavigationActive = true; + if (flatNavigation.length === 0) + return; + + // Re-enable auto-scrolling when arrow keys are used + if (listView && listView.enableAutoScroll) { + listView.enableAutoScroll(); + } + + selectedFlatIndex = Math.max(selectedFlatIndex - 1, 0); + updateSelectedIdFromIndex(); + selectionVersion++; + ensureVisible(); + } + + function toggleGroupExpanded() { + if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) + return; + const currentItem = flatNavigation[selectedFlatIndex]; + const groups = NotificationService.groupedNotifications; + const group = groups[currentItem.groupIndex]; + if (!group) + return; + + // Prevent expanding groups with < 2 notifications + const notificationCount = group.notifications ? group.notifications.length : 0; + if (notificationCount < 2) + return; + const wasExpanded = NotificationService.expandedGroups[group.key] || false; + const groupIndex = currentItem.groupIndex; + + isTogglingGroup = true; + NotificationService.toggleGroupExpansion(group.key); + rebuildFlatNavigation(); + + // Smart selection after toggle + if (!wasExpanded) { + // Just expanded - move to first notification in the group + for (var i = 0; i < flatNavigation.length; i++) { + if (flatNavigation[i].type === "notification" && flatNavigation[i].groupIndex === groupIndex) { + selectedFlatIndex = i; + break; + } + } + } else { + // Just collapsed - stay on the group header + for (var i = 0; i < flatNavigation.length; i++) { + if (flatNavigation[i].type === "group" && flatNavigation[i].groupIndex === groupIndex) { + selectedFlatIndex = i; + break; + } + } + } + + isTogglingGroup = false; + } + + function handleEnterKey() { + if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) + return; + const currentItem = flatNavigation[selectedFlatIndex]; + const groups = NotificationService.groupedNotifications; + const group = groups[currentItem.groupIndex]; + if (!group) + return; + if (currentItem.type === "group") { + const notificationCount = group.notifications ? group.notifications.length : 0; + if (notificationCount >= 2) { + toggleGroupExpanded(); + } else { + executeAction(0); + } + } else if (currentItem.type === "notification") { + executeAction(0); + } + } + + function toggleTextExpanded() { + if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) + return; + const currentItem = flatNavigation[selectedFlatIndex]; + const groups = NotificationService.groupedNotifications; + const group = groups[currentItem.groupIndex]; + if (!group) + return; + let messageId = ""; + + if (currentItem.type === "group") { + messageId = group.latestNotification?.notification?.id + "_desc"; + } else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) { + messageId = group.notifications[currentItem.notificationIndex]?.notification?.id + "_desc"; + } + + if (messageId) { + NotificationService.toggleMessageExpansion(messageId); + } + } + + function executeAction(actionIndex) { + if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) + return; + const currentItem = flatNavigation[selectedFlatIndex]; + const groups = NotificationService.groupedNotifications; + const group = groups[currentItem.groupIndex]; + if (!group) + return; + let actions = []; + + if (currentItem.type === "group") { + actions = group.latestNotification?.actions || []; + } else if (currentItem.type === "notification" && currentItem.notificationIndex >= 0 && currentItem.notificationIndex < group.notifications.length) { + actions = group.notifications[currentItem.notificationIndex]?.actions || []; + } + + if (actionIndex >= 0 && actionIndex < actions.length) { + const action = actions[actionIndex]; + if (action.invoke) { + action.invoke(); + if (onClose) + onClose(); + } + } + } + + function clearSelected() { + if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length) + return; + const currentItem = flatNavigation[selectedFlatIndex]; + const groups = NotificationService.groupedNotifications; + const group = groups[currentItem.groupIndex]; + if (!group) + return; + if (currentItem.type === "group") { + NotificationService.dismissGroup(group.key); + } else if (currentItem.type === "notification") { + const notification = group.notifications[currentItem.notificationIndex]; + NotificationService.dismissNotification(notification); + } + + rebuildFlatNavigation(); + + if (flatNavigation.length === 0) { + keyboardNavigationActive = false; + if (listView) { + listView.keyboardActive = false; + } + } else { + selectedFlatIndex = Math.min(selectedFlatIndex, flatNavigation.length - 1); + updateSelectedIdFromIndex(); + ensureVisible(); + } + } + + function findRepeater(parent) { + if (!parent || !parent.children) { + return null; + } + for (var i = 0; i < parent.children.length; i++) { + const child = parent.children[i]; + if (child.objectName === "notificationRepeater") { + return child; + } + const found = findRepeater(child); + if (found) { + return found; + } + } + return null; + } + + function ensureVisible() { + if (flatNavigation.length === 0 || selectedFlatIndex >= flatNavigation.length || !listView) + return; + const currentItem = flatNavigation[selectedFlatIndex]; + + if (keyboardNavigationActive && currentItem && currentItem.groupIndex >= 0) { + if (currentItem.type === "notification") { + const groupDelegate = listView.itemAtIndex(currentItem.groupIndex); + if (groupDelegate && groupDelegate.children && groupDelegate.children.length > 0) { + const notificationCard = groupDelegate.children[0]; + const repeater = findRepeater(notificationCard); + + if (repeater && currentItem.notificationIndex < repeater.count) { + const notificationItem = repeater.itemAt(currentItem.notificationIndex); + if (notificationItem) { + const itemPos = notificationItem.mapToItem(listView.contentItem, 0, 0); + const itemY = itemPos.y; + const itemHeight = notificationItem.height; + + const viewportTop = listView.contentY; + const viewportBottom = listView.contentY + listView.height; + + if (itemY < viewportTop) { + listView.contentY = itemY - 20; + } else if (itemY + itemHeight > viewportBottom) { + listView.contentY = itemY + itemHeight - listView.height + 20; + } + } + } + } + } else { + listView.positionViewAtIndex(currentItem.groupIndex, ListView.Contain); + } + + listView.forceLayout(); + } + } + + function handleKey(event) { + if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) { + NotificationService.clearAllNotifications(); + rebuildFlatNavigation(); + if (flatNavigation.length === 0) { + keyboardNavigationActive = false; + if (listView) { + listView.keyboardActive = false; + } + } else { + selectedFlatIndex = 0; + updateSelectedIdFromIndex(); + } + selectionVersion++; + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Escape) { + if (keyboardNavigationActive) { + keyboardNavigationActive = false; + event.accepted = true; + } else { + if (onClose) + onClose(); + event.accepted = true; + } + } else if (event.key === Qt.Key_Down || event.key === 16777237) { + if (!keyboardNavigationActive) { + keyboardNavigationActive = true; + rebuildFlatNavigation(); // Ensure we have fresh navigation data + selectedFlatIndex = 0; + updateSelectedIdFromIndex(); + // Set keyboardActive on listView to show highlight + if (listView) { + listView.keyboardActive = true; + } + selectionVersion++; + ensureVisible(); + event.accepted = true; + } else { + selectNext(); + event.accepted = true; + } + } else if (event.key === Qt.Key_Up || event.key === 16777235) { + if (!keyboardNavigationActive) { + keyboardNavigationActive = true; + rebuildFlatNavigation(); // Ensure we have fresh navigation data + selectedFlatIndex = 0; + updateSelectedIdFromIndex(); + // Set keyboardActive on listView to show highlight + if (listView) { + listView.keyboardActive = true; + } + selectionVersion++; + ensureVisible(); + event.accepted = true; + } else if (selectedFlatIndex === 0) { + keyboardNavigationActive = false; + // Reset keyboardActive when navigation is disabled + if (listView) { + listView.keyboardActive = false; + } + selectionVersion++; + event.accepted = true; + return; + } else { + selectPrevious(); + event.accepted = true; + } + } else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) { + if (!keyboardNavigationActive) { + keyboardNavigationActive = true; + rebuildFlatNavigation(); + selectedFlatIndex = 0; + updateSelectedIdFromIndex(); + if (listView) { + listView.keyboardActive = true; + } + selectionVersion++; + ensureVisible(); + } else { + selectNext(); + } + event.accepted = true; + } else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) { + if (!keyboardNavigationActive) { + keyboardNavigationActive = true; + rebuildFlatNavigation(); + selectedFlatIndex = 0; + updateSelectedIdFromIndex(); + if (listView) { + listView.keyboardActive = true; + } + selectionVersion++; + ensureVisible(); + } else if (selectedFlatIndex === 0) { + keyboardNavigationActive = false; + if (listView) { + listView.keyboardActive = false; + } + selectionVersion++; + } else { + selectPrevious(); + } + event.accepted = true; + } else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) { + if (!keyboardNavigationActive) { + keyboardNavigationActive = true; + rebuildFlatNavigation(); + selectedFlatIndex = 0; + updateSelectedIdFromIndex(); + if (listView) { + listView.keyboardActive = true; + } + selectionVersion++; + ensureVisible(); + } else { + selectNext(); + } + event.accepted = true; + } else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) { + if (!keyboardNavigationActive) { + keyboardNavigationActive = true; + rebuildFlatNavigation(); + selectedFlatIndex = 0; + updateSelectedIdFromIndex(); + if (listView) { + listView.keyboardActive = true; + } + selectionVersion++; + ensureVisible(); + } else if (selectedFlatIndex === 0) { + keyboardNavigationActive = false; + if (listView) { + listView.keyboardActive = false; + } + selectionVersion++; + } else { + selectPrevious(); + } + event.accepted = true; + } else if (keyboardNavigationActive) { + if (event.key === Qt.Key_Space) { + toggleGroupExpanded(); + event.accepted = true; + } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + handleEnterKey(); + event.accepted = true; + } else if (event.key === Qt.Key_E) { + toggleTextExpanded(); + event.accepted = true; + } else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) { + clearSelected(); + event.accepted = true; + } else if (event.key === Qt.Key_Tab) { + selectNext(); + event.accepted = true; + } else if (event.key === Qt.Key_Backtab) { + selectPrevious(); + event.accepted = true; + } else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) { + const actionIndex = event.key - Qt.Key_1; + executeAction(actionIndex); + event.accepted = true; + } + } + + if (event.key === Qt.Key_F10) { + showKeyboardHints = !showKeyboardHints; + event.accepted = true; + } + } + + // Get current selection info for UI + function getCurrentSelection() { + if (!keyboardNavigationActive || selectedFlatIndex < 0 || selectedFlatIndex >= flatNavigation.length) { + return { + "type": "", + "groupIndex": -1, + "notificationIndex": -1 + }; + } + const result = flatNavigation[selectedFlatIndex] || { + "type": "", + "groupIndex": -1, + "notificationIndex": -1 + }; + return result; + } +} -- cgit v1.3