mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
- Aggregate plugins/extensions in new "all" tab - Quick tab actions - New tile mode for results - Plugins can enforce/require view mode, or set preferred default - Danksearch under "files" category
485 lines
16 KiB
QML
485 lines
16 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Popup {
|
|
id: root
|
|
|
|
property var item: null
|
|
property var controller: null
|
|
property var searchField: null
|
|
property var parentHandler: null
|
|
|
|
signal hideRequested
|
|
signal editAppRequested(var app)
|
|
|
|
function hasContextMenuActions(spotlightItem) {
|
|
if (!spotlightItem)
|
|
return false;
|
|
if (spotlightItem.type === "app" && !spotlightItem.isCore)
|
|
return true;
|
|
if (spotlightItem.type === "plugin" && spotlightItem.pluginId) {
|
|
var instance = PluginService.pluginInstances[spotlightItem.pluginId];
|
|
if (!instance)
|
|
return false;
|
|
if (typeof instance.getContextMenuActions !== "function")
|
|
return false;
|
|
var actions = instance.getContextMenuActions(spotlightItem.data);
|
|
return Array.isArray(actions) && actions.length > 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
readonly property var desktopEntry: item?.data ?? null
|
|
readonly property string appId: desktopEntry?.id || desktopEntry?.execString || ""
|
|
readonly property bool isPinned: SessionData.isPinnedApp(appId)
|
|
readonly property bool isRegularApp: item?.type === "app" && !item.isCore && desktopEntry
|
|
readonly property bool isPluginItem: item?.type === "plugin"
|
|
|
|
function getPluginContextMenuActions() {
|
|
if (!isPluginItem || !item?.pluginId)
|
|
return [];
|
|
|
|
var instance = PluginService.pluginInstances[item.pluginId];
|
|
if (!instance)
|
|
return [];
|
|
if (typeof instance.getContextMenuActions !== "function")
|
|
return [];
|
|
|
|
var actions = instance.getContextMenuActions(item.data);
|
|
if (!Array.isArray(actions))
|
|
return [];
|
|
|
|
return actions;
|
|
}
|
|
|
|
function executePluginAction(actionFunc) {
|
|
if (typeof actionFunc === "function") {
|
|
actionFunc();
|
|
}
|
|
controller?.performSearch();
|
|
hide();
|
|
}
|
|
|
|
readonly property var menuItems: {
|
|
var items = [];
|
|
|
|
if (isPluginItem) {
|
|
var pluginActions = getPluginContextMenuActions();
|
|
for (var i = 0; i < pluginActions.length; i++) {
|
|
var act = pluginActions[i];
|
|
items.push({
|
|
type: "item",
|
|
icon: act.icon || "play_arrow",
|
|
text: act.text || act.name || "",
|
|
pluginAction: act.action
|
|
});
|
|
}
|
|
return items;
|
|
}
|
|
|
|
if (!desktopEntry)
|
|
return items;
|
|
|
|
items.push({
|
|
type: "item",
|
|
icon: isPinned ? "keep_off" : "push_pin",
|
|
text: isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock"),
|
|
action: togglePin
|
|
});
|
|
|
|
if (isRegularApp) {
|
|
items.push({
|
|
type: "item",
|
|
icon: "visibility_off",
|
|
text: I18n.tr("Hide App"),
|
|
action: hideCurrentApp
|
|
});
|
|
items.push({
|
|
type: "item",
|
|
icon: "edit",
|
|
text: I18n.tr("Edit App"),
|
|
action: editCurrentApp
|
|
});
|
|
}
|
|
|
|
if (item?.actions && item.actions.length > 0) {
|
|
items.push({
|
|
type: "separator"
|
|
});
|
|
for (var i = 0; i < item.actions.length; i++) {
|
|
var act = item.actions[i];
|
|
items.push({
|
|
type: "item",
|
|
icon: act.icon || "play_arrow",
|
|
text: act.name || "",
|
|
actionData: act
|
|
});
|
|
}
|
|
}
|
|
|
|
items.push({
|
|
type: "separator"
|
|
});
|
|
items.push({
|
|
type: "item",
|
|
icon: "launch",
|
|
text: I18n.tr("Launch"),
|
|
action: launchApp
|
|
});
|
|
|
|
if (SessionService.nvidiaCommand) {
|
|
items.push({
|
|
type: "separator"
|
|
});
|
|
items.push({
|
|
type: "item",
|
|
icon: "memory",
|
|
text: I18n.tr("Launch on dGPU"),
|
|
action: launchWithNvidia
|
|
});
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
function show(x, y, spotlightItem, fromKeyboard) {
|
|
if (!spotlightItem?.data)
|
|
return;
|
|
item = spotlightItem;
|
|
selectedMenuIndex = fromKeyboard ? 0 : -1;
|
|
keyboardNavigation = fromKeyboard;
|
|
|
|
if (parentHandler)
|
|
parentHandler.enabled = false;
|
|
|
|
Qt.callLater(() => {
|
|
var parentW = parent?.width ?? 500;
|
|
var parentH = parent?.height ?? 600;
|
|
var menuW = width > 0 ? width : 200;
|
|
var menuH = height > 0 ? height : 200;
|
|
var margin = 8;
|
|
|
|
var posX = x + 4;
|
|
var posY = y + 4;
|
|
|
|
if (posX + menuW > parentW - margin) {
|
|
posX = Math.max(margin, parentW - menuW - margin);
|
|
}
|
|
if (posY + menuH > parentH - margin) {
|
|
posY = Math.max(margin, parentH - menuH - margin);
|
|
}
|
|
|
|
root.x = posX;
|
|
root.y = posY;
|
|
open();
|
|
});
|
|
}
|
|
|
|
function hide() {
|
|
if (parentHandler)
|
|
parentHandler.enabled = true;
|
|
close();
|
|
}
|
|
|
|
function togglePin() {
|
|
if (!appId)
|
|
return;
|
|
if (isPinned)
|
|
SessionData.removePinnedApp(appId);
|
|
else
|
|
SessionData.addPinnedApp(appId);
|
|
hide();
|
|
}
|
|
|
|
function hideCurrentApp() {
|
|
if (!appId)
|
|
return;
|
|
SessionData.hideApp(appId);
|
|
controller?.performSearch();
|
|
hide();
|
|
}
|
|
|
|
function editCurrentApp() {
|
|
if (!desktopEntry)
|
|
return;
|
|
editAppRequested(desktopEntry);
|
|
hide();
|
|
}
|
|
|
|
function launchApp() {
|
|
if (!desktopEntry)
|
|
return;
|
|
SessionService.launchDesktopEntry(desktopEntry);
|
|
AppUsageHistoryData.addAppUsage(desktopEntry);
|
|
controller?.itemExecuted();
|
|
hide();
|
|
}
|
|
|
|
function launchWithNvidia() {
|
|
if (!desktopEntry)
|
|
return;
|
|
SessionService.launchDesktopEntry(desktopEntry, true);
|
|
AppUsageHistoryData.addAppUsage(desktopEntry);
|
|
controller?.itemExecuted();
|
|
hide();
|
|
}
|
|
|
|
function executeDesktopAction(actionData) {
|
|
if (!desktopEntry || !actionData)
|
|
return;
|
|
SessionService.launchDesktopAction(desktopEntry, actionData.actionData || actionData);
|
|
AppUsageHistoryData.addAppUsage(desktopEntry);
|
|
controller?.itemExecuted();
|
|
hide();
|
|
}
|
|
|
|
property int selectedMenuIndex: 0
|
|
property bool keyboardNavigation: false
|
|
|
|
readonly property int visibleItemCount: {
|
|
var count = 0;
|
|
for (var i = 0; i < menuItems.length; i++) {
|
|
if (menuItems[i].type === "item")
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function selectNext() {
|
|
if (visibleItemCount > 0)
|
|
selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount;
|
|
}
|
|
|
|
function selectPrevious() {
|
|
if (visibleItemCount > 0)
|
|
selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount;
|
|
}
|
|
|
|
function activateSelected() {
|
|
var itemIndex = 0;
|
|
for (var i = 0; i < menuItems.length; i++) {
|
|
if (menuItems[i].type !== "item")
|
|
continue;
|
|
if (itemIndex === selectedMenuIndex) {
|
|
var menuItem = menuItems[i];
|
|
if (menuItem.action)
|
|
menuItem.action();
|
|
else if (menuItem.pluginAction)
|
|
executePluginAction(menuItem.pluginAction);
|
|
else if (menuItem.actionData)
|
|
executeDesktopAction(menuItem.actionData);
|
|
return;
|
|
}
|
|
itemIndex++;
|
|
}
|
|
}
|
|
|
|
width: menuContainer.implicitWidth
|
|
height: menuContainer.implicitHeight
|
|
padding: 0
|
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
modal: true
|
|
dim: false
|
|
background: Item {}
|
|
|
|
onOpened: {
|
|
Qt.callLater(() => keyboardHandler.forceActiveFocus());
|
|
}
|
|
|
|
onClosed: {
|
|
if (parentHandler)
|
|
parentHandler.enabled = true;
|
|
if (searchField?.visible) {
|
|
Qt.callLater(() => searchField.forceActiveFocus());
|
|
}
|
|
}
|
|
|
|
enter: Transition {
|
|
NumberAnimation {
|
|
property: "opacity"
|
|
from: 0
|
|
to: 1
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
exit: Transition {
|
|
NumberAnimation {
|
|
property: "opacity"
|
|
from: 1
|
|
to: 0
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
contentItem: Item {
|
|
id: keyboardHandler
|
|
focus: true
|
|
implicitWidth: menuContainer.implicitWidth
|
|
implicitHeight: menuContainer.implicitHeight
|
|
|
|
Keys.onPressed: event => {
|
|
switch (event.key) {
|
|
case Qt.Key_Down:
|
|
root.selectNext();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Up:
|
|
root.selectPrevious();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Return:
|
|
case Qt.Key_Enter:
|
|
root.activateSelected();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Escape:
|
|
case Qt.Key_Left:
|
|
root.hide();
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: menuContainer
|
|
anchors.fill: parent
|
|
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
|
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
|
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
|
radius: Theme.cornerRadius
|
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
border.width: 1
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
anchors.topMargin: 4
|
|
anchors.leftMargin: 2
|
|
anchors.rightMargin: -2
|
|
anchors.bottomMargin: -4
|
|
radius: parent.radius
|
|
color: Qt.rgba(0, 0, 0, 0.15)
|
|
z: -1
|
|
}
|
|
|
|
Column {
|
|
id: menuColumn
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingS
|
|
spacing: 1
|
|
|
|
Repeater {
|
|
model: root.menuItems
|
|
|
|
Item {
|
|
id: menuItemDelegate
|
|
required property var modelData
|
|
required property int index
|
|
|
|
width: menuColumn.width
|
|
height: modelData.type === "separator" ? 5 : 32
|
|
|
|
readonly property int itemIndex: {
|
|
var count = 0;
|
|
for (var i = 0; i < index; i++) {
|
|
if (root.menuItems[i].type === "item")
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
Rectangle {
|
|
visible: menuItemDelegate.modelData.type === "separator"
|
|
width: parent.width - Theme.spacingS * 2
|
|
height: parent.height
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
color: "transparent"
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: parent.width
|
|
height: 1
|
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: menuItemDelegate.modelData.type === "item"
|
|
width: parent.width
|
|
height: parent.height
|
|
radius: Theme.cornerRadius
|
|
color: {
|
|
if (root.keyboardNavigation && root.selectedMenuIndex === menuItemDelegate.itemIndex) {
|
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
|
}
|
|
return itemMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
|
}
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingS
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingS
|
|
|
|
Item {
|
|
width: Theme.iconSize - 2
|
|
height: Theme.iconSize - 2
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
DankIcon {
|
|
visible: (menuItemDelegate.modelData?.icon ?? "").length > 0
|
|
name: menuItemDelegate.modelData?.icon ?? ""
|
|
size: Theme.iconSize - 2
|
|
color: Theme.surfaceText
|
|
opacity: 0.7
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: menuItemDelegate.modelData.text || ""
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceText
|
|
font.weight: Font.Normal
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
elide: Text.ElideRight
|
|
width: parent.width - (Theme.iconSize - 2) - Theme.spacingS
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: itemMouseArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onEntered: {
|
|
root.keyboardNavigation = false;
|
|
root.selectedMenuIndex = menuItemDelegate.itemIndex;
|
|
}
|
|
onClicked: {
|
|
var menuItem = menuItemDelegate.modelData;
|
|
if (menuItem.action)
|
|
menuItem.action();
|
|
else if (menuItem.pluginAction)
|
|
root.executePluginAction(menuItem.pluginAction);
|
|
else if (menuItem.actionData)
|
|
root.executeDesktopAction(menuItem.actionData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|