mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-04 04:42:05 -04:00
With programs like rofi, pressing the tab key advances to the next item in the list. This change makes the Launcher behave in the same way, moving the action cycling to Ctrl+Tab (and Ctrl+Shift+Tab for reverse.
257 lines
8.4 KiB
QML
257 lines
8.4 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Rectangle {
|
|
id: root
|
|
|
|
property var selectedItem: null
|
|
property var controller: null
|
|
property bool expanded: false
|
|
property int selectedActionIndex: 0
|
|
|
|
function getPluginContextMenuActions() {
|
|
if (selectedItem?.type !== "plugin" || !selectedItem?.pluginId)
|
|
return [];
|
|
var instance = PluginService.pluginInstances[selectedItem.pluginId];
|
|
if (!instance)
|
|
return [];
|
|
if (typeof instance.getContextMenuActions !== "function")
|
|
return [];
|
|
var actions = instance.getContextMenuActions(selectedItem.data);
|
|
if (!Array.isArray(actions))
|
|
return [];
|
|
return actions;
|
|
}
|
|
|
|
readonly property var actions: {
|
|
var result = [];
|
|
if (selectedItem?.primaryAction) {
|
|
result.push(selectedItem.primaryAction);
|
|
}
|
|
|
|
switch (selectedItem?.type) {
|
|
case "plugin":
|
|
var pluginActions = getPluginContextMenuActions();
|
|
for (var i = 0; i < pluginActions.length; i++) {
|
|
var act = pluginActions[i];
|
|
result.push({
|
|
name: act.text || act.name || "",
|
|
icon: act.icon || "play_arrow",
|
|
action: "plugin_action",
|
|
pluginAction: act.action
|
|
});
|
|
}
|
|
break;
|
|
case "plugin_browse":
|
|
if (selectedItem?.actions) {
|
|
for (var i = 0; i < selectedItem.actions.length; i++) {
|
|
result.push(selectedItem.actions[i]);
|
|
}
|
|
}
|
|
break;
|
|
case "app":
|
|
if (selectedItem?.isCore)
|
|
break;
|
|
if (SessionService.nvidiaCommand) {
|
|
result.push({
|
|
name: I18n.tr("Launch on dGPU"),
|
|
icon: "memory",
|
|
action: "launch_dgpu"
|
|
});
|
|
}
|
|
if (selectedItem?.actions) {
|
|
for (var i = 0; i < selectedItem.actions.length; i++) {
|
|
result.push(selectedItem.actions[i]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
readonly property bool hasActions: {
|
|
switch (selectedItem?.type) {
|
|
case "app":
|
|
return !selectedItem?.isCore;
|
|
case "plugin":
|
|
return getPluginContextMenuActions().length > 0;
|
|
case "plugin_browse":
|
|
return selectedItem?.actions?.length > 0;
|
|
default:
|
|
return actions.length > 1;
|
|
}
|
|
}
|
|
|
|
width: parent?.width ?? 200
|
|
height: expanded && hasActions ? 52 : 0
|
|
color: Theme.surfaceContainerHigh
|
|
radius: Theme.cornerRadius
|
|
|
|
clip: true
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.standardEasing
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.top: parent.top
|
|
width: parent.width
|
|
height: 1
|
|
color: Theme.outlineMedium
|
|
}
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingS
|
|
|
|
Flickable {
|
|
id: actionsFlickable
|
|
anchors.left: parent.left
|
|
anchors.right: tabHint.left
|
|
anchors.rightMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
height: parent.height
|
|
contentWidth: actionsRow.width
|
|
contentHeight: height
|
|
clip: true
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
flickableDirection: Flickable.HorizontalFlick
|
|
|
|
Row {
|
|
id: actionsRow
|
|
height: parent.height
|
|
spacing: Theme.spacingS
|
|
|
|
Repeater {
|
|
model: root.actions
|
|
|
|
Rectangle {
|
|
id: actionButton
|
|
|
|
required property var modelData
|
|
required property int index
|
|
|
|
width: actionContent.implicitWidth + Theme.spacingM * 2
|
|
height: actionsRow.height
|
|
radius: Theme.cornerRadius
|
|
color: index === root.selectedActionIndex ? Theme.primaryHover : actionArea.containsMouse ? Theme.surfaceHover : "transparent"
|
|
|
|
Row {
|
|
id: actionContent
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingXS
|
|
|
|
DankIcon {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
name: actionButton.modelData?.icon ?? "play_arrow"
|
|
size: 16
|
|
color: actionButton.index === root.selectedActionIndex ? Theme.primary : Theme.surfaceText
|
|
}
|
|
|
|
StyledText {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: actionButton.modelData?.name ?? ""
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.Medium
|
|
color: actionButton.index === root.selectedActionIndex ? Theme.primary : Theme.surfaceText
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: actionArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
if (root.controller && root.selectedItem) {
|
|
root.controller.executeAction(root.selectedItem, actionButton.modelData);
|
|
}
|
|
}
|
|
onEntered: root.selectedActionIndex = actionButton.index
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
id: tabHint
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: root.hasActions
|
|
text: "Tab"
|
|
font.pixelSize: Theme.fontSizeSmall - 2
|
|
color: Theme.outlineButton
|
|
}
|
|
}
|
|
|
|
function toggle() {
|
|
expanded = !expanded;
|
|
selectedActionIndex = 0;
|
|
}
|
|
|
|
function show() {
|
|
expanded = true;
|
|
selectedActionIndex = actions.length > 1 ? 1 : 0;
|
|
}
|
|
|
|
function hide() {
|
|
expanded = false;
|
|
selectedActionIndex = 0;
|
|
}
|
|
|
|
function cycleAction(reverse = false) {
|
|
if (actions.length > 0) {
|
|
if (! reverse)
|
|
selectedActionIndex = (selectedActionIndex + 1) % actions.length;
|
|
else
|
|
selectedActionIndex = (selectedActionIndex - 1) % actions.length;
|
|
ensureSelectedVisible();
|
|
}
|
|
}
|
|
|
|
function ensureSelectedVisible() {
|
|
if (selectedActionIndex < 0 || !actionsRow.children || selectedActionIndex >= actionsRow.children.length)
|
|
return;
|
|
var buttonX = 0;
|
|
for (var i = 0; i < selectedActionIndex; i++) {
|
|
var child = actionsRow.children[i];
|
|
if (child)
|
|
buttonX += child.width + actionsRow.spacing;
|
|
}
|
|
|
|
var button = actionsRow.children[selectedActionIndex];
|
|
if (!button)
|
|
return;
|
|
var buttonRight = buttonX + button.width;
|
|
var viewLeft = actionsFlickable.contentX;
|
|
var viewRight = viewLeft + actionsFlickable.width;
|
|
|
|
if (buttonX < viewLeft) {
|
|
actionsFlickable.contentX = Math.max(0, buttonX - Theme.spacingS);
|
|
} else if (buttonRight > viewRight) {
|
|
actionsFlickable.contentX = Math.min(actionsFlickable.contentWidth - actionsFlickable.width, buttonRight - actionsFlickable.width + Theme.spacingS);
|
|
}
|
|
}
|
|
|
|
function executeSelectedAction() {
|
|
if (!controller || !selectedItem || selectedActionIndex >= actions.length)
|
|
return;
|
|
var action = actions[selectedActionIndex];
|
|
if (action.action === "plugin_action" && typeof action.pluginAction === "function") {
|
|
action.pluginAction();
|
|
controller.performSearch();
|
|
controller.itemExecuted();
|
|
} else {
|
|
controller.executeAction(selectedItem, action);
|
|
}
|
|
}
|
|
}
|