diff --git a/Common/SlideFadeBehavior.qml b/Common/SlideFadeBehavior.qml deleted file mode 100644 index e2059b88..00000000 --- a/Common/SlideFadeBehavior.qml +++ /dev/null @@ -1,46 +0,0 @@ -import QtQuick -import qs.Common - -Item { - id: root - // attach to target - required property Item target - property int direction: Anims.direction.fadeOnly - - // call these - function show() { _apply(true) } - function hide() { _apply(false) } - - function _apply(showing) { - const off = Anims.slidePx - let fromX = 0 - let toX = 0 - switch(direction) { - case Anims.direction.fromLeft: fromX = -off; toX = 0; break - case Anims.direction.fromRight: fromX = off; toX = 0; break - default: fromX = 0; toX = 0; - } - - if (showing) { - target.x = fromX - target.opacity = 0 - target.visible = true - animX.from = fromX; animX.to = toX - animO.from = 0; animO.to = 1 - } else { - animX.from = target.x; animX.to = (direction === Anims.direction.fromLeft ? -off : - direction === Anims.direction.fromRight ? off : 0) - animO.from = target.opacity; animO.to = 0 - } - seq.restart() - } - - SequentialAnimation { - id: seq - ParallelAnimation { - NumberAnimation { id: animX; target: root.target; property: "x"; duration: Anims.durMed; easing.type: Easing.OutCubic } - NumberAnimation { id: animO; target: root.target; property: "opacity"; duration: Anims.durShort } - } - ScriptAction { script: if (root.target.opacity === 0) root.target.visible = false } - } -} diff --git a/Modals/SpotlightModal.qml b/Modals/SpotlightModal.qml index 50c3da08..50fca3a8 100644 --- a/Modals/SpotlightModal.qml +++ b/Modals/SpotlightModal.qml @@ -6,43 +6,25 @@ import Quickshell.Io import qs.Common import qs.Services import qs.Widgets +import qs.Modules.AppDrawer DankModal { id: spotlightModal property bool spotlightOpen: false - property var filteredApps: [] - property int selectedIndex: 0 - property int maxResults: 50 - property var categories: { - var allCategories = AppSearchService.getAllCategories().filter((cat) => { - return cat !== "Education" && cat !== "Science"; - }); - // Insert "Recents" after "All" - var result = ["All", "Recents"]; - return result.concat(allCategories.filter((cat) => { - return cat !== "All"; - })); - } - property string selectedCategory: "All" - property string viewMode: Prefs.spotlightModalViewMode // "list" or "grid" - property int gridColumns: 4 function show() { console.log("SpotlightModal: show() called"); spotlightOpen = true; console.log("SpotlightModal: spotlightOpen set to", spotlightOpen); - searchDebounceTimer.stop(); // Stop any pending search - updateFilteredApps(); // Immediate update when showing + appLauncher.searchQuery = ""; } function hide() { spotlightOpen = false; - searchDebounceTimer.stop(); // Stop any pending search - searchQuery = ""; - selectedIndex = 0; - selectedCategory = "All"; - updateFilteredApps(); + appLauncher.searchQuery = ""; + appLauncher.selectedIndex = 0; + appLauncher.setCategory("All"); } function toggle() { @@ -52,149 +34,8 @@ DankModal { show(); } - property string searchQuery: "" - function updateFilteredApps() { - filteredApps = []; - selectedIndex = 0; - var apps = []; - if (searchQuery.length === 0) { - // Show apps from category - if (selectedCategory === "All") { - // For "All" category, show all available apps - apps = AppSearchService.applications || []; - } else if (selectedCategory === "Recents") { - // For "Recents" category, get recent apps from Prefs and filter out non-existent ones - var recentApps = Prefs.getRecentApps(); - apps = recentApps.map((recentApp) => { - return AppSearchService.getAppByExec(recentApp.exec); - }).filter((app) => { - return app !== null && !app.noDisplay; - }); - } else { - // For specific categories, limit results - var categoryApps = AppSearchService.getAppsInCategory(selectedCategory); - apps = categoryApps.slice(0, maxResults); - } - } else { - // Search with category filter - if (selectedCategory === "All") { - // For "All" category, search all apps without limit - apps = AppSearchService.searchApplications(searchQuery); - } else if (selectedCategory === "Recents") { - // For "Recents" category, search within recent apps - var recentApps = Prefs.getRecentApps(); - var recentDesktopEntries = recentApps.map((recentApp) => { - return AppSearchService.getAppByExec(recentApp.exec); - }).filter((app) => { - return app !== null && !app.noDisplay; - }); - if (recentDesktopEntries.length > 0) { - var allSearchResults = AppSearchService.searchApplications(searchQuery); - var recentNames = new Set(recentDesktopEntries.map((app) => { - return app.name; - })); - // Filter search results to only include recent apps - apps = allSearchResults.filter((searchApp) => { - return recentNames.has(searchApp.name); - }); - } else { - apps = []; - } - } else { - // For specific categories, filter search results by category - var categoryApps = AppSearchService.getAppsInCategory(selectedCategory); - if (categoryApps.length > 0) { - var allSearchResults = AppSearchService.searchApplications(searchQuery); - var categoryNames = new Set(categoryApps.map((app) => { - return app.name; - })); - // Filter search results to only include apps from the selected category - apps = allSearchResults.filter((searchApp) => { - return categoryNames.has(searchApp.name); - }).slice(0, maxResults); - } else { - apps = []; - } - } - } - // Convert to our format - batch operations for better performance - filteredApps = apps.map((app) => { - return ({ - "name": app.name, - "exec": app.execString || "", - "icon": app.icon || "application-x-executable", - "comment": app.comment || "", - "categories": app.categories || [], - "desktopEntry": app - }); - }); - // Clear and repopulate model efficiently - filteredModel.clear(); - filteredApps.forEach((app) => { - return filteredModel.append(app); - }); - } - function launchApp(app) { - Prefs.addRecentApp(app); - if (app.desktopEntry) { - app.desktopEntry.execute(); - } else { - var cleanExec = app.exec.replace(/%[fFuU]/g, "").trim(); - console.log("Spotlight: Launching app directly:", cleanExec); - Quickshell.execDetached(["sh", "-c", cleanExec]); - } - hide(); - } - function selectNext() { - if (filteredModel.count > 0) { - if (viewMode === "grid") { - // Grid navigation: move DOWN by one row (gridColumns positions) - var columnsCount = gridColumns; - var newIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1); - selectedIndex = newIndex; - } else { - // List navigation: next item - selectedIndex = (selectedIndex + 1) % filteredModel.count; - } - } - } - - function selectPrevious() { - if (filteredModel.count > 0) { - if (viewMode === "grid") { - // Grid navigation: move UP by one row (gridColumns positions) - var columnsCount = gridColumns; - var newIndex = Math.max(selectedIndex - columnsCount, 0); - selectedIndex = newIndex; - } else { - // List navigation: previous item - selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1; - } - } - } - - function selectNextInRow() { - if (filteredModel.count > 0 && viewMode === "grid") { - // Grid navigation: move RIGHT by one position - selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1); - } - } - - function selectPreviousInRow() { - if (filteredModel.count > 0 && viewMode === "grid") { - // Grid navigation: move LEFT by one position - selectedIndex = Math.max(selectedIndex - 1, 0); - } - } - - function launchSelected() { - if (filteredModel.count > 0 && selectedIndex >= 0 && selectedIndex < filteredModel.count) { - var selectedApp = filteredModel.get(selectedIndex); - launchApp(selectedApp); - } - } // DankModal configuration visible: spotlightOpen @@ -220,26 +61,17 @@ DankModal { Component.onCompleted: { console.log("SpotlightModal: Component.onCompleted called - component loaded successfully!"); - var allCategories = AppSearchService.getAllCategories().filter((cat) => { - return cat !== "Education" && cat !== "Science"; - }); - // Insert "Recents" after "All" - var result = ["All", "Recents"]; - categories = result.concat(allCategories.filter((cat) => { - return cat !== "All"; - })); } - // Search debouncing - Timer { - id: searchDebounceTimer - interval: 50 - repeat: false - onTriggered: updateFilteredApps() - } - - ListModel { - id: filteredModel + // App launcher logic + AppLauncher { + id: appLauncher + + viewMode: Prefs.spotlightModalViewMode + gridColumns: 4 + + onAppLaunched: hide() + onViewModeSelected: Prefs.setSpotlightModalViewMode(mode) } content: Component { @@ -253,19 +85,19 @@ DankModal { hide(); event.accepted = true; } else if (event.key === Qt.Key_Down) { - selectNext(); + appLauncher.selectNext(); event.accepted = true; } else if (event.key === Qt.Key_Up) { - selectPrevious(); + appLauncher.selectPrevious(); event.accepted = true; - } else if (event.key === Qt.Key_Right && viewMode === "grid") { - selectNextInRow(); + } else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") { + appLauncher.selectNextInRow(); event.accepted = true; - } else if (event.key === Qt.Key_Left && viewMode === "grid") { - selectPreviousInRow(); + } else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") { + appLauncher.selectPreviousInRow(); event.accepted = true; } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - launchSelected(); + appLauncher.launchSelected(); event.accepted = true; } else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) { searchField.text = event.text; @@ -280,99 +112,15 @@ DankModal { spacing: Theme.spacingM - // Combined row for categories and view mode toggle - Column { + // Category selector + CategorySelector { width: parent.width - spacing: Theme.spacingM - visible: categories.length > 1 || filteredModel.count > 0 - - // Categories organized in 2 rows: 4 + 5 - Column { - width: parent.width - spacing: Theme.spacingS - - // Top row: All, Development, Graphics, Internet (4 items) - Row { - property var topRowCategories: ["All", "Recents", "Development", "Graphics"] - - width: parent.width - spacing: Theme.spacingS - - Repeater { - model: parent.topRowCategories.filter((cat) => { - return categories.includes(cat); - }) - - Rectangle { - height: 36 - width: (parent.width - (parent.topRowCategories.length - 1) * Theme.spacingS) / parent.topRowCategories.length - radius: Theme.cornerRadiusLarge - color: selectedCategory === modelData ? Theme.primary : "transparent" - border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) - - Text { - anchors.centerIn: parent - text: modelData - color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText - font.pixelSize: Theme.fontSizeMedium - font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal - elide: Text.ElideRight - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - selectedCategory = modelData; - updateFilteredApps(); - } - } - } - } - } - - // Bottom row: Media, Office, Settings, System, Utilities (5 items) - Row { - property var bottomRowCategories: ["Internet", "Media", "Office", "Settings", "System"] - - width: parent.width - spacing: Theme.spacingS - - Repeater { - model: parent.bottomRowCategories.filter((cat) => { - return categories.includes(cat); - }) - - Rectangle { - height: 36 - width: (parent.width - (parent.bottomRowCategories.length - 1) * Theme.spacingS) / parent.bottomRowCategories.length - radius: Theme.cornerRadiusLarge - color: selectedCategory === modelData ? Theme.primary : "transparent" - border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) - - Text { - anchors.centerIn: parent - text: modelData - color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText - font.pixelSize: Theme.fontSizeMedium - font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal - elide: Text.ElideRight - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - selectedCategory = modelData; - updateFilteredApps(); - } - } - } - } - } - } + categories: appLauncher.categories + selectedCategory: appLauncher.selectedCategory + compact: false + visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0 + + onCategorySelected: appLauncher.setCategory(category) } // Search field with view toggle buttons @@ -398,10 +146,9 @@ DankModal { font.pixelSize: Theme.fontSizeLarge enabled: spotlightOpen placeholderText: "Search applications..." - text: searchQuery + text: appLauncher.searchQuery onTextEdited: { - searchQuery = text; - searchDebounceTimer.restart(); + appLauncher.searchQuery = text; } Connections { @@ -418,16 +165,16 @@ DankModal { if (event.key === Qt.Key_Escape) { hide(); event.accepted = true; - } else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length > 0) { + } else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.searchQuery.length > 0) { // Launch first app when typing in search field - if (filteredApps.length > 0) { - launchApp(filteredApps[0]); + if (appLauncher.model.count > 0) { + appLauncher.launchApp(appLauncher.model.get(0)); } event.accepted = true; } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || - (event.key === Qt.Key_Left && viewMode === "grid") || - (event.key === Qt.Key_Right && viewMode === "grid") || - ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length === 0)) { + (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") || + (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") || + ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.searchQuery.length === 0)) { // Pass navigation keys and enter (when not searching) to main handler event.accepted = false; } @@ -437,7 +184,7 @@ DankModal { // View mode toggle buttons next to search bar Row { spacing: Theme.spacingXS - visible: filteredModel.count > 0 + visible: appLauncher.model.count > 0 anchors.verticalCenter: parent.verticalCenter // List view button @@ -445,15 +192,15 @@ DankModal { width: 36 height: 36 radius: Theme.cornerRadiusLarge - color: viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : listViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent" - border.color: viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent" + color: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : listViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent" + border.color: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent" border.width: 1 DankIcon { anchors.centerIn: parent name: "view_list" size: 18 - color: viewMode === "list" ? Theme.primary : Theme.surfaceText + color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText } MouseArea { @@ -463,8 +210,7 @@ DankModal { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - viewMode = "list"; - Prefs.setSpotlightModalViewMode("list"); + appLauncher.setViewMode("list"); } } } @@ -474,15 +220,15 @@ DankModal { width: 36 height: 36 radius: Theme.cornerRadiusLarge - color: viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : gridViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent" - border.color: viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent" + color: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : gridViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent" + border.color: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent" border.width: 1 DankIcon { anchors.centerIn: parent name: "grid_view" size: 18 - color: viewMode === "grid" ? Theme.primary : Theme.surfaceText + color: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText } MouseArea { @@ -492,8 +238,7 @@ DankModal { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - viewMode = "grid"; - Prefs.setSpotlightModalViewMode("grid"); + appLauncher.setViewMode("grid"); } } } @@ -513,18 +258,18 @@ DankModal { id: resultsList anchors.fill: parent - visible: viewMode === "list" - model: filteredModel - currentIndex: selectedIndex + visible: appLauncher.viewMode === "list" + model: appLauncher.model + currentIndex: appLauncher.selectedIndex itemHeight: 60 iconSize: 40 showDescription: true hoverUpdatesSelection: false onItemClicked: function(index, modelData) { - launchApp(modelData); + appLauncher.launchApp(modelData); } onItemHovered: function(index) { - selectedIndex = index; + appLauncher.selectedIndex = index; } } @@ -533,21 +278,21 @@ DankModal { id: resultsGrid anchors.fill: parent - visible: viewMode === "grid" - model: filteredModel + visible: appLauncher.viewMode === "grid" + model: appLauncher.model columns: 4 adaptiveColumns: false minCellWidth: 120 maxCellWidth: 160 iconSizeRatio: 0.55 maxIconSize: 48 - currentIndex: selectedIndex + currentIndex: appLauncher.selectedIndex hoverUpdatesSelection: false onItemClicked: function(index, modelData) { - launchApp(modelData); + appLauncher.launchApp(modelData); } onItemHovered: function(index) { - selectedIndex = index; + appLauncher.selectedIndex = index; } } } diff --git a/Modules/Popouts/AppDrawerPopout.qml b/Modules/AppDrawer/AppDrawerPopout.qml similarity index 65% rename from Modules/Popouts/AppDrawerPopout.qml rename to Modules/AppDrawer/AppDrawerPopout.qml index 149586ce..facb6a48 100644 --- a/Modules/Popouts/AppDrawerPopout.qml +++ b/Modules/AppDrawer/AppDrawerPopout.qml @@ -8,153 +8,25 @@ import Quickshell.Widgets import qs.Common import qs.Services import qs.Widgets +import qs.Modules.AppDrawer PanelWindow { - // For recents, use the recent apps from Prefs and filter out non-existent ones - id: appDrawerPopout property bool isVisible: false - // App management - property var categories: AppSearchService.getAllCategories() - property string selectedCategory: "All" - property var recentApps: Prefs.recentlyUsedApps.map((recentApp) => { - var app = AppSearchService.getAppByExec(recentApp.exec); - return app && !app.noDisplay ? app : null; - }).filter((app) => { - return app !== null; - }) - property var pinnedApps: ["firefox", "code", "terminal", "file-manager"] property bool showCategories: false - property string viewMode: Prefs.appLauncherViewMode // "list" or "grid" - property int selectedIndex: 0 - function updateFilteredModel() { - filteredModel.clear(); - selectedIndex = 0; - var apps = []; - var searchQuery = searchField ? searchField.text : ""; - // Get apps based on category and search - if (searchQuery.length > 0) { - // Search across all apps or category - var baseApps = selectedCategory === "All" ? AppSearchService.applications : selectedCategory === "Recents" ? recentApps.map((recentApp) => { - return AppSearchService.getAppByExec(recentApp.exec); - }).filter((app) => { - return app !== null && !app.noDisplay; - }) : AppSearchService.getAppsInCategory(selectedCategory); - if (baseApps && baseApps.length > 0) { - var searchResults = AppSearchService.searchApplications(searchQuery); - apps = searchResults.filter((app) => { - return baseApps.includes(app); - }); - } - } else { - // Just category filter - if (selectedCategory === "Recents") - apps = recentApps.map((recentApp) => { - return AppSearchService.getAppByExec(recentApp.exec); - }).filter((app) => { - return app !== null && !app.noDisplay; - }); - else - apps = AppSearchService.getAppsInCategory(selectedCategory) || []; - } - // Add to model with null checks - if (apps && apps.length > 0) - apps.forEach((app) => { - if (app) - filteredModel.append({ - "name": app.name || "", - "exec": app.execString || "", - "icon": app.icon || "application-x-executable", - "comment": app.comment || "", - "categories": app.categories || [], - "desktopEntry": app - }); - }); - - } - - function selectNext() { - if (filteredModel.count > 0) { - if (viewMode === "grid") { - // Grid navigation: move by columns - var columnsCount = appGrid.columns || 4; - var newIndex = Math.min(selectedIndex + columnsCount, filteredModel.count - 1); - console.log("Grid navigation DOWN: from", selectedIndex, "to", newIndex, "columns:", columnsCount); - selectedIndex = newIndex; - } else { - // List navigation: next item - selectedIndex = (selectedIndex + 1) % filteredModel.count; - } - } - } - - function selectPrevious() { - if (filteredModel.count > 0) { - if (viewMode === "grid") { - // Grid navigation: move by columns - var columnsCount = appGrid.columns || 4; - var newIndex = Math.max(selectedIndex - columnsCount, 0); - console.log("Grid navigation UP: from", selectedIndex, "to", newIndex, "columns:", columnsCount); - selectedIndex = newIndex; - } else { - // List navigation: previous item - selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1; - } - } - } - - function selectNextInRow() { - if (filteredModel.count > 0 && viewMode === "grid") - selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1); - - } - - function selectPreviousInRow() { - if (filteredModel.count > 0 && viewMode === "grid") - selectedIndex = Math.max(selectedIndex - 1, 0); - - } - - function launchSelected() { - if (filteredModel.count > 0 && selectedIndex >= 0 && selectedIndex < filteredModel.count) { - var selectedApp = filteredModel.get(selectedIndex); - if (selectedApp.desktopEntry) { - Prefs.addRecentApp(selectedApp.desktopEntry); - selectedApp.desktopEntry.execute(); - } else { - appDrawerPopout.launchApp(selectedApp.exec); - } - appDrawerPopout.hide(); - } - } - - function launchApp(exec) { - // Try to find the desktop entry - var app = AppSearchService.getAppByExec(exec); - if (app) { - app.execute(); - } else { - // Fallback to direct execution - var cleanExec = exec.replace(/%[fFuU]/g, "").trim(); - console.log("Launching app directly:", cleanExec); - Quickshell.execDetached(["sh", "-c", cleanExec]); - } - } function show() { appDrawerPopout.isVisible = true; searchField.enabled = true; - searchDebounceTimer.stop(); // Stop any pending search - updateFilteredModel(); + appLauncher.searchQuery = ""; } function hide() { searchField.enabled = false; // Disable before hiding to prevent Wayland warnings appDrawerPopout.isVisible = false; - searchDebounceTimer.stop(); // Stop any pending search searchField.text = ""; showCategories = false; } @@ -173,14 +45,6 @@ PanelWindow { WlrLayershell.namespace: "quickshell-launcher" visible: isVisible color: "transparent" - Component.onCompleted: { - var allCategories = AppSearchService.getAllCategories(); - // Insert "Recents" after "All" - categories = ["All", "Recents"].concat(allCategories.filter((cat) => { - return cat !== "All"; - })); - updateFilteredModel(); - } // Full screen overlay setup for proper focus anchors { @@ -190,17 +54,15 @@ PanelWindow { bottom: true } - // Search debouncing - Timer { - id: searchDebounceTimer - - interval: 50 - repeat: false - onTriggered: updateFilteredModel() - } - - ListModel { - id: filteredModel + // App launcher logic + AppLauncher { + id: appLauncher + + viewMode: Prefs.appLauncherViewMode + gridColumns: 4 + + onAppLaunched: appDrawerPopout.hide() + onViewModeSelected: Prefs.setAppLauncherViewMode(mode) } // Background dim with click to close @@ -329,19 +191,19 @@ PanelWindow { appDrawerPopout.hide(); event.accepted = true; } else if (event.key === Qt.Key_Down) { - selectNext(); + appLauncher.selectNext(); event.accepted = true; } else if (event.key === Qt.Key_Up) { - selectPrevious(); + appLauncher.selectPrevious(); event.accepted = true; - } else if (event.key === Qt.Key_Right && viewMode === "grid") { - selectNextInRow(); + } else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") { + appLauncher.selectNextInRow(); event.accepted = true; - } else if (event.key === Qt.Key_Left && viewMode === "grid") { - selectPreviousInRow(); + } else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") { + appLauncher.selectPreviousInRow(); event.accepted = true; } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - launchSelected(); + appLauncher.launchSelected(); event.accepted = true; } else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\s]/)) { // User started typing, focus search field and pass the character @@ -378,7 +240,7 @@ PanelWindow { // Quick stats Text { anchors.verticalCenter: parent.verticalCenter - text: filteredModel.count + " apps" + text: appLauncher.model.count + " apps" font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceVariantText } @@ -404,21 +266,15 @@ PanelWindow { enabled: appDrawerPopout.isVisible placeholderText: "Search applications..." onTextEdited: { - searchDebounceTimer.restart(); + appLauncher.searchQuery = text; } Keys.onPressed: function(event) { - if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && filteredModel.count && text.length > 0) { + if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.model.count && text.length > 0) { // Launch first app when typing in search field - var firstApp = filteredModel.get(0); - if (firstApp.desktopEntry) { - Prefs.addRecentApp(firstApp.desktopEntry); - firstApp.desktopEntry.execute(); - } else { - appDrawerPopout.launchApp(firstApp.exec); - } - appDrawerPopout.hide(); + var firstApp = appLauncher.model.get(0); + appLauncher.launchApp(firstApp); event.accepted = true; - } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || (event.key === Qt.Key_Left && viewMode === "grid") || (event.key === Qt.Key_Right && viewMode === "grid") || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) { + } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") || (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) { // Pass navigation keys and enter (when not searching) to main handler event.accepted = false; } @@ -467,7 +323,7 @@ PanelWindow { } Text { - text: selectedCategory + text: appLauncher.selectedCategory font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceText anchors.verticalCenter: parent.verticalCenter @@ -510,12 +366,11 @@ PanelWindow { circular: false iconName: "view_list" iconSize: 20 - iconColor: viewMode === "list" ? Theme.primary : Theme.surfaceText - hoverColor: viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) - backgroundColor: viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + iconColor: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText + hoverColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) + backgroundColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" onClicked: { - viewMode = "list"; - Prefs.setAppLauncherViewMode("list"); + appLauncher.setViewMode("list"); } } @@ -525,12 +380,11 @@ PanelWindow { circular: false iconName: "grid_view" iconSize: 20 - iconColor: viewMode === "grid" ? Theme.primary : Theme.surfaceText - hoverColor: viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) - backgroundColor: viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" + iconColor: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText + hoverColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) + backgroundColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" onClicked: { - viewMode = "grid"; - Prefs.setAppLauncherViewMode("grid"); + appLauncher.setViewMode("grid"); } } @@ -558,24 +412,18 @@ PanelWindow { id: appList anchors.fill: parent - visible: viewMode === "list" - model: filteredModel - currentIndex: selectedIndex + visible: appLauncher.viewMode === "list" + model: appLauncher.model + currentIndex: appLauncher.selectedIndex itemHeight: 72 iconSize: 56 showDescription: true hoverUpdatesSelection: false onItemClicked: function(index, modelData) { - if (modelData.desktopEntry) { - Prefs.addRecentApp(modelData.desktopEntry); - modelData.desktopEntry.execute(); - } else { - appDrawerPopout.launchApp(modelData.exec); - } - appDrawerPopout.hide(); + appLauncher.launchApp(modelData); } onItemHovered: function(index) { - selectedIndex = index; + appLauncher.selectedIndex = index; } } @@ -584,23 +432,17 @@ PanelWindow { id: appGrid anchors.fill: parent - visible: viewMode === "grid" - model: filteredModel + visible: appLauncher.viewMode === "grid" + model: appLauncher.model columns: 4 adaptiveColumns: false - currentIndex: selectedIndex + currentIndex: appLauncher.selectedIndex hoverUpdatesSelection: false onItemClicked: function(index, modelData) { - if (modelData.desktopEntry) { - Prefs.addRecentApp(modelData.desktopEntry); - modelData.desktopEntry.execute(); - } else { - appDrawerPopout.launchApp(modelData.exec); - } - appDrawerPopout.hide(); + appLauncher.launchApp(modelData); } onItemHovered: function(index) { - selectedIndex = index; + appLauncher.selectedIndex = index; } } @@ -654,7 +496,7 @@ PanelWindow { // Make mouse wheel scrolling more responsive property real wheelStepSize: 60 - model: categories + model: appLauncher.categories spacing: 4 MouseArea { @@ -686,8 +528,8 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter text: modelData font.pixelSize: Theme.fontSizeMedium - color: selectedCategory === modelData ? Theme.primary : Theme.surfaceText - font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal + color: appLauncher.selectedCategory === modelData ? Theme.primary : Theme.surfaceText + font.weight: appLauncher.selectedCategory === modelData ? Font.Medium : Font.Normal } MouseArea { @@ -697,9 +539,8 @@ PanelWindow { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - selectedCategory = modelData; + appLauncher.setCategory(modelData); showCategories = false; - updateFilteredModel(); } } diff --git a/Modules/AppDrawer/AppLauncher.qml b/Modules/AppDrawer/AppLauncher.qml new file mode 100644 index 00000000..6ec3c770 --- /dev/null +++ b/Modules/AppDrawer/AppLauncher.qml @@ -0,0 +1,201 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + // Public interface + property string searchQuery: "" + property string selectedCategory: "All" + property string viewMode: "list" // "list" or "grid" + property int selectedIndex: 0 + property int maxResults: 50 + property int gridColumns: 4 + property bool debounceSearch: true + property int debounceInterval: 50 + + // Categories (computed from AppSearchService) + property var categories: { + var allCategories = AppSearchService.getAllCategories().filter(cat => { + return cat !== "Education" && cat !== "Science"; + }); + var result = ["All", "Recents"]; + return result.concat(allCategories.filter(cat => { + return cat !== "All"; + })); + } + + // Recent apps helper + property var recentApps: Prefs.recentlyUsedApps.map(recentApp => { + var app = AppSearchService.getAppByExec(recentApp.exec); + return app && !app.noDisplay ? app : null; + }).filter(app => { + return app !== null; + }) + + // Signals + signal appLaunched(var app) + signal categorySelected(string category) + signal viewModeSelected(string mode) + + // Internal model + property alias model: filteredModel + + ListModel { + id: filteredModel + } + + // Search debouncing + Timer { + id: searchDebounceTimer + interval: root.debounceInterval + repeat: false + onTriggered: updateFilteredModel() + } + + // Watch for changes + onSearchQueryChanged: { + if (debounceSearch) { + searchDebounceTimer.restart(); + } else { + updateFilteredModel(); + } + } + onSelectedCategoryChanged: updateFilteredModel() + + function updateFilteredModel() { + filteredModel.clear(); + selectedIndex = 0; + + var apps = []; + + if (searchQuery.length === 0) { + // Show apps from category + if (selectedCategory === "All") { + apps = AppSearchService.applications || []; + } else if (selectedCategory === "Recents") { + apps = recentApps; + } else { + var categoryApps = AppSearchService.getAppsInCategory(selectedCategory); + apps = categoryApps.slice(0, maxResults); + } + } else { + // Search with category filter + if (selectedCategory === "All") { + apps = AppSearchService.searchApplications(searchQuery); + } else if (selectedCategory === "Recents") { + if (recentApps.length > 0) { + var allSearchResults = AppSearchService.searchApplications(searchQuery); + var recentNames = new Set(recentApps.map(app => app.name)); + apps = allSearchResults.filter(searchApp => { + return recentNames.has(searchApp.name); + }); + } else { + apps = []; + } + } else { + var categoryApps = AppSearchService.getAppsInCategory(selectedCategory); + if (categoryApps.length > 0) { + var allSearchResults = AppSearchService.searchApplications(searchQuery); + var categoryNames = new Set(categoryApps.map(app => app.name)); + apps = allSearchResults.filter(searchApp => { + return categoryNames.has(searchApp.name); + }).slice(0, maxResults); + } else { + apps = []; + } + } + } + + // Convert to model format and populate + apps.forEach(app => { + if (app) { + filteredModel.append({ + "name": app.name || "", + "exec": app.execString || "", + "icon": app.icon || "application-x-executable", + "comment": app.comment || "", + "categories": app.categories || [], + "desktopEntry": app + }); + } + }); + } + + // Keyboard navigation functions + function selectNext() { + if (filteredModel.count > 0) { + if (viewMode === "grid") { + var newIndex = Math.min(selectedIndex + gridColumns, filteredModel.count - 1); + selectedIndex = newIndex; + } else { + selectedIndex = (selectedIndex + 1) % filteredModel.count; + } + } + } + + function selectPrevious() { + if (filteredModel.count > 0) { + if (viewMode === "grid") { + var newIndex = Math.max(selectedIndex - gridColumns, 0); + selectedIndex = newIndex; + } else { + selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : filteredModel.count - 1; + } + } + } + + function selectNextInRow() { + if (filteredModel.count > 0 && viewMode === "grid") { + selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1); + } + } + + function selectPreviousInRow() { + if (filteredModel.count > 0 && viewMode === "grid") { + selectedIndex = Math.max(selectedIndex - 1, 0); + } + } + + // App launching + function launchSelected() { + if (filteredModel.count > 0 && selectedIndex >= 0 && selectedIndex < filteredModel.count) { + var selectedApp = filteredModel.get(selectedIndex); + launchApp(selectedApp); + } + } + + function launchApp(appData) { + if (appData.desktopEntry) { + Prefs.addRecentApp(appData.desktopEntry); + appData.desktopEntry.execute(); + } else { + // Fallback to direct execution + var cleanExec = appData.exec.replace(/%[fFuU]/g, "").trim(); + console.log("AppLauncher: Launching app directly:", cleanExec); + Quickshell.execDetached(["sh", "-c", cleanExec]); + } + appLaunched(appData); + } + + // Category management + function setCategory(category) { + selectedCategory = category; + categorySelected(category); + } + + // View mode management + function setViewMode(mode) { + viewMode = mode; + viewModeSelected(mode); + } + + // Initialize + Component.onCompleted: { + updateFilteredModel(); + } +} \ No newline at end of file diff --git a/Modules/AppDrawer/CategorySelector.qml b/Modules/AppDrawer/CategorySelector.qml new file mode 100644 index 00000000..4b5510db --- /dev/null +++ b/Modules/AppDrawer/CategorySelector.qml @@ -0,0 +1,143 @@ +import QtQuick +import QtQuick.Controls +import qs.Common +import qs.Widgets + +Item { + id: root + + property var categories: [] + property string selectedCategory: "All" + property bool compact: false // For different layout styles + + signal categorySelected(string category) + + height: compact ? 36 : (72 + Theme.spacingS) // Single row vs two rows + + // Compact single-row layout (for SpotlightModal style) + Row { + visible: compact + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: categories.slice(0, Math.min(categories.length, 8)) // Limit for space + + Rectangle { + height: 36 + width: (parent.width - (Math.min(categories.length, 8) - 1) * Theme.spacingS) / Math.min(categories.length, 8) + radius: Theme.cornerRadiusLarge + color: selectedCategory === modelData ? Theme.primary : "transparent" + border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) + + Text { + anchors.centerIn: parent + text: modelData + color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText + font.pixelSize: Theme.fontSizeMedium + font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal + elide: Text.ElideRight + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + selectedCategory = modelData; + categorySelected(modelData); + } + } + } + } + } + + // Two-row layout (for SpotlightModal organized style) + Column { + visible: !compact + width: parent.width + spacing: Theme.spacingS + + // Top row: All, Recents, Development, Graphics (4 items) + Row { + property var topRowCategories: ["All", "Recents", "Development", "Graphics"] + + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: parent.topRowCategories.filter(cat => { + return categories.includes(cat); + }) + + Rectangle { + height: 36 + width: (parent.width - (parent.topRowCategories.length - 1) * Theme.spacingS) / parent.topRowCategories.length + radius: Theme.cornerRadiusLarge + color: selectedCategory === modelData ? Theme.primary : "transparent" + border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) + + Text { + anchors.centerIn: parent + text: modelData + color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText + font.pixelSize: Theme.fontSizeMedium + font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal + elide: Text.ElideRight + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + selectedCategory = modelData; + categorySelected(modelData); + } + } + } + } + } + + // Bottom row: Internet, Media, Office, Settings, System (5 items) + Row { + property var bottomRowCategories: ["Internet", "Media", "Office", "Settings", "System"] + + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: parent.bottomRowCategories.filter(cat => { + return categories.includes(cat); + }) + + Rectangle { + height: 36 + width: (parent.width - (parent.bottomRowCategories.length - 1) * Theme.spacingS) / parent.bottomRowCategories.length + radius: Theme.cornerRadiusLarge + color: selectedCategory === modelData ? Theme.primary : "transparent" + border.color: selectedCategory === modelData ? "transparent" : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) + + Text { + anchors.centerIn: parent + text: modelData + color: selectedCategory === modelData ? Theme.surface : Theme.surfaceText + font.pixelSize: Theme.fontSizeMedium + font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal + elide: Text.ElideRight + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + selectedCategory = modelData; + categorySelected(modelData); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Modules/Popouts/CentcomPopout.qml b/Modules/CentcomCenter/CentcomPopout.qml similarity index 90% rename from Modules/Popouts/CentcomPopout.qml rename to Modules/CentcomCenter/CentcomPopout.qml index 50c96cc1..585924db 100644 --- a/Modules/Popouts/CentcomPopout.qml +++ b/Modules/CentcomCenter/CentcomPopout.qml @@ -97,6 +97,44 @@ PanelWindow { } } + // Only resize after animation is complete + onOpacityChanged: { + if (opacity === 1) { + // Animation finished, now we can safely resize + Qt.callLater(() => { + height = calculateHeight(); + }); + } + } + + Connections { + function onEventsByDateChanged() { + if (mainContainer.opacity === 1) { + mainContainer.height = mainContainer.calculateHeight(); + } + } + + function onKhalAvailableChanged() { + if (mainContainer.opacity === 1) { + mainContainer.height = mainContainer.calculateHeight(); + } + } + + target: CalendarService + enabled: CalendarService !== null + } + + Connections { + function onSelectedDateEventsChanged() { + if (mainContainer.opacity === 1) { + mainContainer.height = mainContainer.calculateHeight(); + } + } + + target: events + enabled: events !== null + } + Rectangle { anchors.fill: parent color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04) @@ -122,27 +160,6 @@ PanelWindow { } - Connections { - function onEventsByDateChanged() { - mainContainer.height = mainContainer.calculateHeight(); - } - - function onKhalAvailableChanged() { - mainContainer.height = mainContainer.calculateHeight(); - } - - target: CalendarService - enabled: CalendarService !== null - } - - Connections { - function onSelectedDateEventsChanged() { - mainContainer.height = mainContainer.calculateHeight(); - } - - target: events - enabled: events !== null - } Column { diff --git a/Modules/Popouts/ControlCenterPopout.qml b/Modules/ControlCenter/ControlCenterPopout.qml similarity index 100% rename from Modules/Popouts/ControlCenterPopout.qml rename to Modules/ControlCenter/ControlCenterPopout.qml diff --git a/Modules/PowerMenuPopup.qml b/Modules/ControlCenter/PowerMenu.qml similarity index 100% rename from Modules/PowerMenuPopup.qml rename to Modules/ControlCenter/PowerMenu.qml diff --git a/Modules/Popouts/ProcessListPopout.qml b/Modules/ProcessList/ProcessListPopout.qml similarity index 100% rename from Modules/Popouts/ProcessListPopout.qml rename to Modules/ProcessList/ProcessListPopout.qml diff --git a/Modules/Popouts/BatteryPopout.qml b/Modules/TopBar/BatteryPopout.qml similarity index 100% rename from Modules/Popouts/BatteryPopout.qml rename to Modules/TopBar/BatteryPopout.qml diff --git a/Modules/TrayMenuPopup.qml b/Modules/TopBar/SystemTrayContextMenu.qml similarity index 93% rename from Modules/TrayMenuPopup.qml rename to Modules/TopBar/SystemTrayContextMenu.qml index 69b3a872..abb35758 100644 --- a/Modules/TrayMenuPopup.qml +++ b/Modules/TopBar/SystemTrayContextMenu.qml @@ -8,13 +8,13 @@ import qs.Common PanelWindow { id: root - property bool showTrayMenu: false - property real trayMenuX: 0 - property real trayMenuY: 0 + property bool showContextMenu: false + property real contextMenuX: 0 + property real contextMenuY: 0 property var currentTrayMenu: null property var currentTrayItem: null - visible: showTrayMenu + visible: showContextMenu WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: WlrKeyboardFocus.None @@ -30,8 +30,8 @@ PanelWindow { Rectangle { id: menuContainer - x: trayMenuX - y: trayMenuY + x: contextMenuX + y: contextMenuY width: Math.max(180, Math.min(300, menuList.maxTextWidth + Theme.spacingL * 2)) height: Math.max(60, menuList.contentHeight + Theme.spacingS * 2) color: Theme.popupBackground() @@ -39,8 +39,8 @@ PanelWindow { border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.width: 1 // Material 3 animations - opacity: showTrayMenu ? 1 : 0 - scale: showTrayMenu ? 1 : 0.85 + opacity: showContextMenu ? 1 : 0 + scale: showContextMenu ? 1 : 0.85 // Material 3 drop shadow Rectangle { @@ -139,7 +139,7 @@ PanelWindow { if (modelData.triggered) modelData.triggered(); - showTrayMenu = false; + showContextMenu = false; } } @@ -180,7 +180,7 @@ PanelWindow { anchors.fill: parent z: -1 onClicked: { - showTrayMenu = false; + showContextMenu = false; } } diff --git a/Modules/TopBar/TopBar.qml b/Modules/TopBar/TopBar.qml index 37b0dcc1..449e1dcb 100644 --- a/Modules/TopBar/TopBar.qml +++ b/Modules/TopBar/TopBar.qml @@ -194,11 +194,11 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter visible: Prefs.showSystemTray onMenuRequested: (menu, item, x, y) => { - trayMenuPopup.currentTrayMenu = menu; - trayMenuPopup.currentTrayItem = item; - trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL; - trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS; - trayMenuPopup.showTrayMenu = true; + systemTrayContextMenu.currentTrayMenu = menu; + systemTrayContextMenu.currentTrayItem = item; + systemTrayContextMenu.contextMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL; + systemTrayContextMenu.contextMenuY = Theme.barHeight - Theme.spacingXS; + systemTrayContextMenu.showContextMenu = true; menu.menuVisible = true; } } diff --git a/shell.qml b/shell.qml index d14b8558..554ef7d2 100644 --- a/shell.qml +++ b/shell.qml @@ -2,13 +2,13 @@ import Quickshell import qs.Modules +import qs.Modules.AppDrawer import qs.Modules.CentcomCenter import qs.Modules.ControlCenter import qs.Modules.Settings import qs.Modules.TopBar import qs.Modules.ProcessList import qs.Modules.ControlCenter.Network -import qs.Modules.Popouts import qs.Modals ShellRoot { @@ -29,8 +29,8 @@ ShellRoot { id: centcomPopout } - TrayMenuPopup { - id: trayMenuPopup + SystemTrayContextMenu { + id: systemTrayContextMenu } NotificationCenter { @@ -63,8 +63,8 @@ ShellRoot { id: batteryPopout } - PowerMenuPopup { - id: powerMenuPopup + PowerMenu { + id: powerMenu } PowerConfirmModal {