1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 05:55:37 -05:00

refactor: app drawer de-dupe

This commit is contained in:
bbedward
2025-07-23 15:36:29 -04:00
parent c01da89311
commit 94b10159a9
13 changed files with 505 additions and 604 deletions

View File

@@ -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 }
}
}

View File

@@ -6,43 +6,25 @@ import Quickshell.Io
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.AppDrawer
DankModal { DankModal {
id: spotlightModal id: spotlightModal
property bool spotlightOpen: false 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() { function show() {
console.log("SpotlightModal: show() called"); console.log("SpotlightModal: show() called");
spotlightOpen = true; spotlightOpen = true;
console.log("SpotlightModal: spotlightOpen set to", spotlightOpen); console.log("SpotlightModal: spotlightOpen set to", spotlightOpen);
searchDebounceTimer.stop(); // Stop any pending search appLauncher.searchQuery = "";
updateFilteredApps(); // Immediate update when showing
} }
function hide() { function hide() {
spotlightOpen = false; spotlightOpen = false;
searchDebounceTimer.stop(); // Stop any pending search appLauncher.searchQuery = "";
searchQuery = ""; appLauncher.selectedIndex = 0;
selectedIndex = 0; appLauncher.setCategory("All");
selectedCategory = "All";
updateFilteredApps();
} }
function toggle() { function toggle() {
@@ -52,149 +34,8 @@ DankModal {
show(); 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 // DankModal configuration
visible: spotlightOpen visible: spotlightOpen
@@ -220,26 +61,17 @@ DankModal {
Component.onCompleted: { Component.onCompleted: {
console.log("SpotlightModal: Component.onCompleted called - component loaded successfully!"); 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 // App launcher logic
Timer { AppLauncher {
id: searchDebounceTimer id: appLauncher
interval: 50
repeat: false
onTriggered: updateFilteredApps()
}
ListModel { viewMode: Prefs.spotlightModalViewMode
id: filteredModel gridColumns: 4
onAppLaunched: hide()
onViewModeSelected: Prefs.setSpotlightModalViewMode(mode)
} }
content: Component { content: Component {
@@ -253,19 +85,19 @@ DankModal {
hide(); hide();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Down) { } else if (event.key === Qt.Key_Down) {
selectNext(); appLauncher.selectNext();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Up) { } else if (event.key === Qt.Key_Up) {
selectPrevious(); appLauncher.selectPrevious();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Right && viewMode === "grid") { } else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") {
selectNextInRow(); appLauncher.selectNextInRow();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Left && viewMode === "grid") { } else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
selectPreviousInRow(); appLauncher.selectPreviousInRow();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
launchSelected(); appLauncher.launchSelected();
event.accepted = true; event.accepted = true;
} else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) { } else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
searchField.text = event.text; searchField.text = event.text;
@@ -280,99 +112,15 @@ DankModal {
spacing: Theme.spacingM spacing: Theme.spacingM
// Combined row for categories and view mode toggle // Category selector
Column { CategorySelector {
width: parent.width width: parent.width
spacing: Theme.spacingM categories: appLauncher.categories
visible: categories.length > 1 || filteredModel.count > 0 selectedCategory: appLauncher.selectedCategory
compact: false
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
// Categories organized in 2 rows: 4 + 5 onCategorySelected: appLauncher.setCategory(category)
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();
}
}
}
}
}
}
} }
// Search field with view toggle buttons // Search field with view toggle buttons
@@ -398,10 +146,9 @@ DankModal {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: spotlightOpen enabled: spotlightOpen
placeholderText: "Search applications..." placeholderText: "Search applications..."
text: searchQuery text: appLauncher.searchQuery
onTextEdited: { onTextEdited: {
searchQuery = text; appLauncher.searchQuery = text;
searchDebounceTimer.restart();
} }
Connections { Connections {
@@ -418,16 +165,16 @@ DankModal {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
hide(); hide();
event.accepted = true; 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 // Launch first app when typing in search field
if (filteredApps.length > 0) { if (appLauncher.model.count > 0) {
launchApp(filteredApps[0]); appLauncher.launchApp(appLauncher.model.get(0));
} }
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
(event.key === Qt.Key_Left && viewMode === "grid") || (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") ||
(event.key === Qt.Key_Right && viewMode === "grid") || (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") ||
((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && searchQuery.length === 0)) { ((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 // Pass navigation keys and enter (when not searching) to main handler
event.accepted = false; event.accepted = false;
} }
@@ -437,7 +184,7 @@ DankModal {
// View mode toggle buttons next to search bar // View mode toggle buttons next to search bar
Row { Row {
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: filteredModel.count > 0 visible: appLauncher.model.count > 0
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
// List view button // List view button
@@ -445,15 +192,15 @@ DankModal {
width: 36 width: 36
height: 36 height: 36
radius: Theme.cornerRadiusLarge 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" 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: viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent" border.color: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent"
border.width: 1 border.width: 1
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: "view_list" name: "view_list"
size: 18 size: 18
color: viewMode === "list" ? Theme.primary : Theme.surfaceText color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
} }
MouseArea { MouseArea {
@@ -463,8 +210,7 @@ DankModal {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
viewMode = "list"; appLauncher.setViewMode("list");
Prefs.setSpotlightModalViewMode("list");
} }
} }
} }
@@ -474,15 +220,15 @@ DankModal {
width: 36 width: 36
height: 36 height: 36
radius: Theme.cornerRadiusLarge 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" 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: viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent" border.color: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent"
border.width: 1 border.width: 1
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: "grid_view" name: "grid_view"
size: 18 size: 18
color: viewMode === "grid" ? Theme.primary : Theme.surfaceText color: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
} }
MouseArea { MouseArea {
@@ -492,8 +238,7 @@ DankModal {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
viewMode = "grid"; appLauncher.setViewMode("grid");
Prefs.setSpotlightModalViewMode("grid");
} }
} }
} }
@@ -513,18 +258,18 @@ DankModal {
id: resultsList id: resultsList
anchors.fill: parent anchors.fill: parent
visible: viewMode === "list" visible: appLauncher.viewMode === "list"
model: filteredModel model: appLauncher.model
currentIndex: selectedIndex currentIndex: appLauncher.selectedIndex
itemHeight: 60 itemHeight: 60
iconSize: 40 iconSize: 40
showDescription: true showDescription: true
hoverUpdatesSelection: false hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
launchApp(modelData); appLauncher.launchApp(modelData);
} }
onItemHovered: function(index) { onItemHovered: function(index) {
selectedIndex = index; appLauncher.selectedIndex = index;
} }
} }
@@ -533,21 +278,21 @@ DankModal {
id: resultsGrid id: resultsGrid
anchors.fill: parent anchors.fill: parent
visible: viewMode === "grid" visible: appLauncher.viewMode === "grid"
model: filteredModel model: appLauncher.model
columns: 4 columns: 4
adaptiveColumns: false adaptiveColumns: false
minCellWidth: 120 minCellWidth: 120
maxCellWidth: 160 maxCellWidth: 160
iconSizeRatio: 0.55 iconSizeRatio: 0.55
maxIconSize: 48 maxIconSize: 48
currentIndex: selectedIndex currentIndex: appLauncher.selectedIndex
hoverUpdatesSelection: false hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
launchApp(modelData); appLauncher.launchApp(modelData);
} }
onItemHovered: function(index) { onItemHovered: function(index) {
selectedIndex = index; appLauncher.selectedIndex = index;
} }
} }
} }

View File

@@ -8,153 +8,25 @@ import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.AppDrawer
PanelWindow { PanelWindow {
// For recents, use the recent apps from Prefs and filter out non-existent ones
id: appDrawerPopout id: appDrawerPopout
property bool isVisible: false 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 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() { function show() {
appDrawerPopout.isVisible = true; appDrawerPopout.isVisible = true;
searchField.enabled = true; searchField.enabled = true;
searchDebounceTimer.stop(); // Stop any pending search appLauncher.searchQuery = "";
updateFilteredModel();
} }
function hide() { function hide() {
searchField.enabled = false; // Disable before hiding to prevent Wayland warnings searchField.enabled = false; // Disable before hiding to prevent Wayland warnings
appDrawerPopout.isVisible = false; appDrawerPopout.isVisible = false;
searchDebounceTimer.stop(); // Stop any pending search
searchField.text = ""; searchField.text = "";
showCategories = false; showCategories = false;
} }
@@ -173,14 +45,6 @@ PanelWindow {
WlrLayershell.namespace: "quickshell-launcher" WlrLayershell.namespace: "quickshell-launcher"
visible: isVisible visible: isVisible
color: "transparent" 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 // Full screen overlay setup for proper focus
anchors { anchors {
@@ -190,17 +54,15 @@ PanelWindow {
bottom: true bottom: true
} }
// Search debouncing // App launcher logic
Timer { AppLauncher {
id: searchDebounceTimer id: appLauncher
interval: 50 viewMode: Prefs.appLauncherViewMode
repeat: false gridColumns: 4
onTriggered: updateFilteredModel()
}
ListModel { onAppLaunched: appDrawerPopout.hide()
id: filteredModel onViewModeSelected: Prefs.setAppLauncherViewMode(mode)
} }
// Background dim with click to close // Background dim with click to close
@@ -329,19 +191,19 @@ PanelWindow {
appDrawerPopout.hide(); appDrawerPopout.hide();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Down) { } else if (event.key === Qt.Key_Down) {
selectNext(); appLauncher.selectNext();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Up) { } else if (event.key === Qt.Key_Up) {
selectPrevious(); appLauncher.selectPrevious();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Right && viewMode === "grid") { } else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") {
selectNextInRow(); appLauncher.selectNextInRow();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Left && viewMode === "grid") { } else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
selectPreviousInRow(); appLauncher.selectPreviousInRow();
event.accepted = true; event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
launchSelected(); appLauncher.launchSelected();
event.accepted = true; event.accepted = true;
} else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\s]/)) { } 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 // User started typing, focus search field and pass the character
@@ -378,7 +240,7 @@ PanelWindow {
// Quick stats // Quick stats
Text { Text {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: filteredModel.count + " apps" text: appLauncher.model.count + " apps"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -404,21 +266,15 @@ PanelWindow {
enabled: appDrawerPopout.isVisible enabled: appDrawerPopout.isVisible
placeholderText: "Search applications..." placeholderText: "Search applications..."
onTextEdited: { onTextEdited: {
searchDebounceTimer.restart(); appLauncher.searchQuery = text;
} }
Keys.onPressed: function(event) { 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 // Launch first app when typing in search field
var firstApp = filteredModel.get(0); var firstApp = appLauncher.model.get(0);
if (firstApp.desktopEntry) { appLauncher.launchApp(firstApp);
Prefs.addRecentApp(firstApp.desktopEntry);
firstApp.desktopEntry.execute();
} else {
appDrawerPopout.launchApp(firstApp.exec);
}
appDrawerPopout.hide();
event.accepted = true; 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 // Pass navigation keys and enter (when not searching) to main handler
event.accepted = false; event.accepted = false;
} }
@@ -467,7 +323,7 @@ PanelWindow {
} }
Text { Text {
text: selectedCategory text: appLauncher.selectedCategory
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -510,12 +366,11 @@ PanelWindow {
circular: false circular: false
iconName: "view_list" iconName: "view_list"
iconSize: 20 iconSize: 20
iconColor: viewMode === "list" ? Theme.primary : Theme.surfaceText iconColor: appLauncher.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) 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: viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" backgroundColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
onClicked: { onClicked: {
viewMode = "list"; appLauncher.setViewMode("list");
Prefs.setAppLauncherViewMode("list");
} }
} }
@@ -525,12 +380,11 @@ PanelWindow {
circular: false circular: false
iconName: "grid_view" iconName: "grid_view"
iconSize: 20 iconSize: 20
iconColor: viewMode === "grid" ? Theme.primary : Theme.surfaceText iconColor: appLauncher.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) 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: viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" backgroundColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
onClicked: { onClicked: {
viewMode = "grid"; appLauncher.setViewMode("grid");
Prefs.setAppLauncherViewMode("grid");
} }
} }
@@ -558,24 +412,18 @@ PanelWindow {
id: appList id: appList
anchors.fill: parent anchors.fill: parent
visible: viewMode === "list" visible: appLauncher.viewMode === "list"
model: filteredModel model: appLauncher.model
currentIndex: selectedIndex currentIndex: appLauncher.selectedIndex
itemHeight: 72 itemHeight: 72
iconSize: 56 iconSize: 56
showDescription: true showDescription: true
hoverUpdatesSelection: false hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
if (modelData.desktopEntry) { appLauncher.launchApp(modelData);
Prefs.addRecentApp(modelData.desktopEntry);
modelData.desktopEntry.execute();
} else {
appDrawerPopout.launchApp(modelData.exec);
}
appDrawerPopout.hide();
} }
onItemHovered: function(index) { onItemHovered: function(index) {
selectedIndex = index; appLauncher.selectedIndex = index;
} }
} }
@@ -584,23 +432,17 @@ PanelWindow {
id: appGrid id: appGrid
anchors.fill: parent anchors.fill: parent
visible: viewMode === "grid" visible: appLauncher.viewMode === "grid"
model: filteredModel model: appLauncher.model
columns: 4 columns: 4
adaptiveColumns: false adaptiveColumns: false
currentIndex: selectedIndex currentIndex: appLauncher.selectedIndex
hoverUpdatesSelection: false hoverUpdatesSelection: false
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
if (modelData.desktopEntry) { appLauncher.launchApp(modelData);
Prefs.addRecentApp(modelData.desktopEntry);
modelData.desktopEntry.execute();
} else {
appDrawerPopout.launchApp(modelData.exec);
}
appDrawerPopout.hide();
} }
onItemHovered: function(index) { onItemHovered: function(index) {
selectedIndex = index; appLauncher.selectedIndex = index;
} }
} }
@@ -654,7 +496,7 @@ PanelWindow {
// Make mouse wheel scrolling more responsive // Make mouse wheel scrolling more responsive
property real wheelStepSize: 60 property real wheelStepSize: 60
model: categories model: appLauncher.categories
spacing: 4 spacing: 4
MouseArea { MouseArea {
@@ -686,8 +528,8 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: modelData text: modelData
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: selectedCategory === modelData ? Theme.primary : Theme.surfaceText color: appLauncher.selectedCategory === modelData ? Theme.primary : Theme.surfaceText
font.weight: selectedCategory === modelData ? Font.Medium : Font.Normal font.weight: appLauncher.selectedCategory === modelData ? Font.Medium : Font.Normal
} }
MouseArea { MouseArea {
@@ -697,9 +539,8 @@ PanelWindow {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
selectedCategory = modelData; appLauncher.setCategory(modelData);
showCategories = false; showCategories = false;
updateFilteredModel();
} }
} }

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}
}
}
}
}

View File

@@ -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 { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04) 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 { Column {

View File

@@ -8,13 +8,13 @@ import qs.Common
PanelWindow { PanelWindow {
id: root id: root
property bool showTrayMenu: false property bool showContextMenu: false
property real trayMenuX: 0 property real contextMenuX: 0
property real trayMenuY: 0 property real contextMenuY: 0
property var currentTrayMenu: null property var currentTrayMenu: null
property var currentTrayItem: null property var currentTrayItem: null
visible: showTrayMenu visible: showContextMenu
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
@@ -30,8 +30,8 @@ PanelWindow {
Rectangle { Rectangle {
id: menuContainer id: menuContainer
x: trayMenuX x: contextMenuX
y: trayMenuY y: contextMenuY
width: Math.max(180, Math.min(300, menuList.maxTextWidth + Theme.spacingL * 2)) width: Math.max(180, Math.min(300, menuList.maxTextWidth + Theme.spacingL * 2))
height: Math.max(60, menuList.contentHeight + Theme.spacingS * 2) height: Math.max(60, menuList.contentHeight + Theme.spacingS * 2)
color: Theme.popupBackground() color: Theme.popupBackground()
@@ -39,8 +39,8 @@ PanelWindow {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
// Material 3 animations // Material 3 animations
opacity: showTrayMenu ? 1 : 0 opacity: showContextMenu ? 1 : 0
scale: showTrayMenu ? 1 : 0.85 scale: showContextMenu ? 1 : 0.85
// Material 3 drop shadow // Material 3 drop shadow
Rectangle { Rectangle {
@@ -139,7 +139,7 @@ PanelWindow {
if (modelData.triggered) if (modelData.triggered)
modelData.triggered(); modelData.triggered();
showTrayMenu = false; showContextMenu = false;
} }
} }
@@ -180,7 +180,7 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
onClicked: { onClicked: {
showTrayMenu = false; showContextMenu = false;
} }
} }

View File

@@ -194,11 +194,11 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemTray visible: Prefs.showSystemTray
onMenuRequested: (menu, item, x, y) => { onMenuRequested: (menu, item, x, y) => {
trayMenuPopup.currentTrayMenu = menu; systemTrayContextMenu.currentTrayMenu = menu;
trayMenuPopup.currentTrayItem = item; systemTrayContextMenu.currentTrayItem = item;
trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL; systemTrayContextMenu.contextMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL;
trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS; systemTrayContextMenu.contextMenuY = Theme.barHeight - Theme.spacingXS;
trayMenuPopup.showTrayMenu = true; systemTrayContextMenu.showContextMenu = true;
menu.menuVisible = true; menu.menuVisible = true;
} }
} }

View File

@@ -2,13 +2,13 @@
import Quickshell import Quickshell
import qs.Modules import qs.Modules
import qs.Modules.AppDrawer
import qs.Modules.CentcomCenter import qs.Modules.CentcomCenter
import qs.Modules.ControlCenter import qs.Modules.ControlCenter
import qs.Modules.Settings import qs.Modules.Settings
import qs.Modules.TopBar import qs.Modules.TopBar
import qs.Modules.ProcessList import qs.Modules.ProcessList
import qs.Modules.ControlCenter.Network import qs.Modules.ControlCenter.Network
import qs.Modules.Popouts
import qs.Modals import qs.Modals
ShellRoot { ShellRoot {
@@ -29,8 +29,8 @@ ShellRoot {
id: centcomPopout id: centcomPopout
} }
TrayMenuPopup { SystemTrayContextMenu {
id: trayMenuPopup id: systemTrayContextMenu
} }
NotificationCenter { NotificationCenter {
@@ -63,8 +63,8 @@ ShellRoot {
id: batteryPopout id: batteryPopout
} }
PowerMenuPopup { PowerMenu {
id: powerMenuPopup id: powerMenu
} }
PowerConfirmModal { PowerConfirmModal {