mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 21:45:38 -05:00
refactor: app drawer de-dupe
This commit is contained in:
@@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
viewMode: Prefs.spotlightModalViewMode
|
||||||
onTriggered: updateFilteredApps()
|
gridColumns: 4
|
||||||
}
|
|
||||||
|
onAppLaunched: hide()
|
||||||
ListModel {
|
onViewModeSelected: Prefs.setSpotlightModalViewMode(mode)
|
||||||
id: filteredModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
// Categories organized in 2 rows: 4 + 5
|
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
|
||||||
Column {
|
|
||||||
width: parent.width
|
onCategorySelected: appLauncher.setCategory(category)
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
onAppLaunched: appDrawerPopout.hide()
|
||||||
|
onViewModeSelected: Prefs.setAppLauncherViewMode(mode)
|
||||||
ListModel {
|
|
||||||
id: filteredModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
201
Modules/AppDrawer/AppLauncher.qml
Normal file
201
Modules/AppDrawer/AppLauncher.qml
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
143
Modules/AppDrawer/CategorySelector.qml
Normal file
143
Modules/AppDrawer/CategorySelector.qml
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
shell.qml
10
shell.qml
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user