mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
spotlight: fix clipping and add context menu keyboard navigation (#840)
* spotlight: fix clipping and add context menu keyboard navigation * prime: also detect nvidia-offload command * spotlight: fix review nitpicks
This commit is contained in:
@@ -11,8 +11,10 @@ Item {
|
||||
property alias appLauncher: appLauncher
|
||||
property alias searchField: searchField
|
||||
property alias fileSearchController: fileSearchController
|
||||
property alias resultsView: resultsView
|
||||
property var parentModal: null
|
||||
property string searchMode: "apps"
|
||||
property bool usePopupContextMenu: false
|
||||
|
||||
function resetScroll() {
|
||||
if (searchMode === "apps") {
|
||||
@@ -146,6 +148,18 @@ Item {
|
||||
fileSearchController.openSelected();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Menu) {
|
||||
if (searchMode === "apps" && appLauncher.model.count > 0) {
|
||||
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||
|
||||
if (selectedApp && menu && resultsView) {
|
||||
const itemPos = resultsView.getSelectedItemPosition();
|
||||
const contentPos = resultsView.mapToItem(spotlightKeyHandler, itemPos.x, itemPos.y);
|
||||
menu.show(contentPos.x, contentPos.y, selectedApp, true);
|
||||
}
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +192,52 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SpotlightContextMenuPopup {
|
||||
id: popupContextMenu
|
||||
|
||||
parent: spotlightKeyHandler
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
parentHandler: spotlightKeyHandler
|
||||
searchField: spotlightKeyHandler.searchField
|
||||
visible: false
|
||||
z: 1000
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: usePopupContextMenu && popupContextMenu.visible
|
||||
hoverEnabled: true
|
||||
z: 999
|
||||
onClicked: popupContextMenu.hide()
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: layerContextMenuLoader
|
||||
active: !spotlightKeyHandler.usePopupContextMenu
|
||||
asynchronous: false
|
||||
sourceComponent: Component {
|
||||
SpotlightContextMenu {
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
parentHandler: spotlightKeyHandler
|
||||
parentModal: spotlightKeyHandler.parentModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: parentModal
|
||||
function onSpotlightOpenChanged() {
|
||||
if (parentModal && !parentModal.spotlightOpen) {
|
||||
if (layerContextMenuLoader.item) {
|
||||
layerContextMenuLoader.item.hide();
|
||||
}
|
||||
popupContextMenu.hide();
|
||||
}
|
||||
}
|
||||
enabled: parentModal !== null
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
@@ -397,8 +457,22 @@ Item {
|
||||
id: resultsView
|
||||
anchors.fill: parent
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
contextMenu: contextMenu
|
||||
visible: searchMode === "apps"
|
||||
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||
|
||||
if (menu?.show) {
|
||||
const isPopup = menu.contentItem !== undefined;
|
||||
|
||||
if (isPopup) {
|
||||
const localPos = popupContextMenu.parent.mapFromItem(null, mouseX, mouseY);
|
||||
menu.show(localPos.x, localPos.y, modelData, false);
|
||||
} else {
|
||||
menu.show(mouseX, mouseY, modelData, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileSearchResults {
|
||||
@@ -410,31 +484,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SpotlightContextMenu {
|
||||
id: contextMenu
|
||||
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
parentHandler: spotlightKeyHandler
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: contextMenu.visible
|
||||
z: 999
|
||||
onClicked: () => {
|
||||
contextMenu.hide();
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
x: contextMenu.x
|
||||
y: contextMenu.y
|
||||
width: contextMenu.width
|
||||
height: contextMenu.height
|
||||
onClicked: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: filenameTooltipLoader
|
||||
|
||||
|
||||
@@ -1,338 +1,117 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modals.Spotlight
|
||||
|
||||
Popup {
|
||||
id: contextMenu
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight-context-menu"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
property var currentApp: null
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||
property var parentModal: null
|
||||
property real menuPositionX: 0
|
||||
property real menuPositionY: 0
|
||||
|
||||
readonly property real shadowBuffer: 5
|
||||
|
||||
screen: parentModal?.effectiveScreen
|
||||
|
||||
function show(x, y, app) {
|
||||
currentApp = app
|
||||
contextMenu.x = x + 4
|
||||
contextMenu.y = y + 4
|
||||
contextMenu.open()
|
||||
function show(x, y, app, fromKeyboard) {
|
||||
fromKeyboard = fromKeyboard || false;
|
||||
menuContent.currentApp = app;
|
||||
|
||||
let screenX = x;
|
||||
let screenY = y;
|
||||
|
||||
if (parentModal) {
|
||||
if (fromKeyboard) {
|
||||
screenX = x + parentModal.alignedX;
|
||||
screenY = y + parentModal.alignedY;
|
||||
} else {
|
||||
screenX = x + (parentModal.alignedX - shadowBuffer);
|
||||
screenY = y + (parentModal.alignedY - shadowBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
menuPositionX = screenX;
|
||||
menuPositionY = screenY;
|
||||
|
||||
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||
menuContent.keyboardNavigation = true;
|
||||
visible = true;
|
||||
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = false;
|
||||
}
|
||||
Qt.callLater(() => {
|
||||
menuContent.keyboardHandler.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
contextMenu.close()
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
visible = false;
|
||||
}
|
||||
|
||||
width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnPressOutside
|
||||
modal: false
|
||||
dim: false
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
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
|
||||
onVisibleChanged: {
|
||||
if (!visible && parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
SpotlightContextMenuContent {
|
||||
id: menuContent
|
||||
|
||||
x: {
|
||||
const left = 10;
|
||||
const right = root.width - width - 10;
|
||||
const want = menuPositionX;
|
||||
return Math.max(left, Math.min(right, want));
|
||||
}
|
||||
y: {
|
||||
const top = 10;
|
||||
const bottom = root.height - height - 10;
|
||||
const want = menuPositionY;
|
||||
return Math.max(top, Math.min(bottom, want));
|
||||
}
|
||||
|
||||
appLauncher: root.appLauncher
|
||||
|
||||
opacity: root.visible ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
onHideRequested: root.hide()
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: pinRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!desktopEntry)
|
||||
return "push_pin"
|
||||
|
||||
const appId = desktopEntry.id || desktopEntry.execString || ""
|
||||
return SessionData.isPinnedApp(appId) ? "keep_off" : "push_pin"
|
||||
}
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!desktopEntry)
|
||||
return I18n.tr("Pin to Dock")
|
||||
|
||||
const appId = desktopEntry.id || desktopEntry.execString || ""
|
||||
return SessionData.isPinnedApp(appId) ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pinMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
if (!desktopEntry)
|
||||
return
|
||||
|
||||
const appId = desktopEntry.id || desktopEntry.execString || ""
|
||||
if (SessionData.isPinnedApp(appId))
|
||||
SessionData.removePinnedApp(appId)
|
||||
else
|
||||
SessionData.addPinnedApp(appId)
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: desktopEntry && desktopEntry.actions ? desktopEntry.actions : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: actionRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.iconSize - 2
|
||||
height: Theme.iconSize - 2
|
||||
visible: modelData.icon && modelData.icon !== ""
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.name || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && desktopEntry) {
|
||||
SessionService.launchDesktopAction(desktopEntry, modelData)
|
||||
if (appLauncher && contextMenu.currentApp) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: desktopEntry && desktopEntry.actions && desktopEntry.actions.length > 0
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
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 {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: launchRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "launch"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Launch")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launchMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
if (contextMenu.currentApp && appLauncher)
|
||||
appLauncher.launchApp(contextMenu.currentApp)
|
||||
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
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: SessionService.hasPrimeRun
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
id: primeRunRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "memory"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Launch on dGPU")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: primeRunMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: () => {
|
||||
if (desktopEntry) {
|
||||
SessionService.launchDesktopEntry(desktopEntry, true)
|
||||
if (appLauncher && contextMenu.currentApp) {
|
||||
appLauncher.appLaunched(contextMenu.currentApp)
|
||||
}
|
||||
}
|
||||
contextMenu.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
z: -1
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
292
quickshell/Modals/Spotlight/SpotlightContextMenuContent.qml
Normal file
292
quickshell/Modals/Spotlight/SpotlightContextMenuContent.qml
Normal file
@@ -0,0 +1,292 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var currentApp: null
|
||||
property var appLauncher: null
|
||||
property int selectedMenuIndex: 0
|
||||
property bool keyboardNavigation: false
|
||||
|
||||
signal hideRequested()
|
||||
|
||||
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||
|
||||
readonly property var menuItems: {
|
||||
const items = [];
|
||||
const appId = desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : "";
|
||||
const isPinned = SessionData.isPinnedApp(appId);
|
||||
|
||||
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 (desktopEntry && desktopEntry.actions) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
for (let i = 0; i < desktopEntry.actions.length; i++) {
|
||||
const act = desktopEntry.actions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
text: act.name || "",
|
||||
action: () => launchAction(act)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: "separator",
|
||||
hidden: !desktopEntry || !desktopEntry.actions || desktopEntry.actions.length === 0
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "launch",
|
||||
text: I18n.tr("Launch"),
|
||||
action: launchCurrentApp
|
||||
});
|
||||
|
||||
if (SessionService.nvidiaCommand) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "memory",
|
||||
text: I18n.tr("Launch on dGPU"),
|
||||
action: launchWithNvidia
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
readonly property int visibleItemCount: {
|
||||
let count = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type === "item" && !menuItems[i].hidden) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (visibleItemCount > 0) {
|
||||
selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount;
|
||||
}
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (visibleItemCount > 0) {
|
||||
selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount;
|
||||
}
|
||||
}
|
||||
|
||||
function togglePin() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
const appId = desktopEntry.id || desktopEntry.execString || "";
|
||||
if (SessionData.isPinnedApp(appId))
|
||||
SessionData.removePinnedApp(appId);
|
||||
else
|
||||
SessionData.addPinnedApp(appId);
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function launchCurrentApp() {
|
||||
if (currentApp && appLauncher)
|
||||
appLauncher.launchApp(currentApp);
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function launchWithNvidia() {
|
||||
if (desktopEntry) {
|
||||
SessionService.launchDesktopEntry(desktopEntry, true);
|
||||
if (appLauncher && currentApp) {
|
||||
appLauncher.appLaunched(currentApp);
|
||||
}
|
||||
}
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function launchAction(action) {
|
||||
if (desktopEntry) {
|
||||
SessionService.launchDesktopAction(desktopEntry, action);
|
||||
if (appLauncher && currentApp) {
|
||||
appLauncher.appLaunched(currentApp);
|
||||
}
|
||||
}
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
let itemIndex = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type === "item" && !menuItems[i].hidden) {
|
||||
if (itemIndex === selectedMenuIndex) {
|
||||
menuItems[i].action();
|
||||
return;
|
||||
}
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property alias keyboardHandler: keyboardHandler
|
||||
|
||||
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
anchors.fill: parent
|
||||
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
|
||||
}
|
||||
|
||||
Item {
|
||||
id: keyboardHandler
|
||||
anchors.fill: parent
|
||||
focus: keyboardNavigation
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Down) {
|
||||
selectNext();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
selectPrevious();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
activateSelected();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
hideRequested();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
model: menuItems
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: modelData.type === "separator" ? 5 : 32
|
||||
visible: !modelData.hidden
|
||||
|
||||
property int itemIndex: {
|
||||
let count = 0;
|
||||
for (let i = 0; i < index; i++) {
|
||||
if (menuItems[i].type === "item" && !menuItems[i].hidden) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: 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: modelData.type === "item"
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (keyboardNavigation && selectedMenuIndex === itemIndex) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
||||
}
|
||||
return mouseArea.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: modelData.icon !== undefined && modelData.icon !== ""
|
||||
name: modelData.icon || ""
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: 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: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
keyboardNavigation = false;
|
||||
selectedMenuIndex = itemIndex;
|
||||
}
|
||||
onClicked: modelData.action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
quickshell/Modals/Spotlight/SpotlightContextMenuPopup.qml
Normal file
88
quickshell/Modals/Spotlight/SpotlightContextMenuPopup.qml
Normal file
@@ -0,0 +1,88 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
property var searchField: null
|
||||
|
||||
function show(x, y, app, fromKeyboard) {
|
||||
fromKeyboard = fromKeyboard || false;
|
||||
menuContent.currentApp = app;
|
||||
|
||||
root.x = x + 4;
|
||||
root.y = y + 4;
|
||||
|
||||
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||
menuContent.keyboardNavigation = true;
|
||||
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = false;
|
||||
}
|
||||
|
||||
open();
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
Qt.callLater(() => {
|
||||
menuContent.keyboardHandler.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
width: menuContent.implicitWidth
|
||||
height: menuContent.implicitHeight
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
modal: true
|
||||
dim: false
|
||||
background: Item {}
|
||||
|
||||
onClosed: {
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
if (searchField) {
|
||||
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: SpotlightContextMenuContent {
|
||||
id: menuContent
|
||||
appLauncher: root.appLauncher
|
||||
onHideRequested: root.hide()
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ Rectangle {
|
||||
id: resultsContainer
|
||||
|
||||
property var appLauncher: null
|
||||
property var contextMenu: null
|
||||
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.contentY = 0
|
||||
@@ -17,6 +18,24 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedItemPosition() {
|
||||
if (!appLauncher) return { x: 0, y: 0 };
|
||||
|
||||
const selectedIndex = appLauncher.selectedIndex;
|
||||
if (appLauncher.viewMode === "list") {
|
||||
const itemY = selectedIndex * (resultsList.itemHeight + resultsList.itemSpacing) - resultsList.contentY;
|
||||
return { x: resultsList.width / 2, y: itemY + resultsList.itemHeight / 2 };
|
||||
} else if (gridLoader.item) {
|
||||
const grid = gridLoader.item;
|
||||
const row = Math.floor(selectedIndex / grid.actualColumns);
|
||||
const col = selectedIndex % grid.actualColumns;
|
||||
const itemX = col * grid.cellWidth + grid.leftMargin + grid.cellWidth / 2;
|
||||
const itemY = row * grid.cellHeight - grid.contentY + grid.cellHeight / 2;
|
||||
return { x: itemX, y: itemY };
|
||||
}
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
clip: true
|
||||
@@ -67,9 +86,8 @@ Rectangle {
|
||||
appLauncher.launchApp(modelData)
|
||||
}
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
if (contextMenu)
|
||||
contextMenu.show(mouseX, mouseY, modelData)
|
||||
}
|
||||
resultsContainer.itemRightClicked(index, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: () => {
|
||||
if (appLauncher)
|
||||
appLauncher.keyboardNavigationActive = false
|
||||
@@ -87,8 +105,7 @@ Rectangle {
|
||||
iconUnicodeScale: 0.8
|
||||
onItemClicked: (idx, modelData) => resultsList.itemClicked(idx, modelData)
|
||||
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
|
||||
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
|
||||
resultsList.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
|
||||
resultsList.itemRightClicked(idx, modelData, mouseX, mouseY)
|
||||
}
|
||||
onKeyboardNavigationReset: resultsList.keyboardNavigationReset
|
||||
}
|
||||
@@ -103,6 +120,14 @@ Rectangle {
|
||||
anchors.margins: Theme.spacingS
|
||||
visible: appLauncher && appLauncher.viewMode === "grid"
|
||||
active: appLauncher && appLauncher.viewMode === "grid"
|
||||
asynchronous: false
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.appLauncher = Qt.binding(() => resultsContainer.appLauncher);
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
if (visible && Math.abs(width - _lastWidth) > 1) {
|
||||
_lastWidth = width
|
||||
@@ -116,6 +141,8 @@ Rectangle {
|
||||
DankGridView {
|
||||
id: resultsGrid
|
||||
|
||||
property var appLauncher: null
|
||||
|
||||
property int currentIndex: appLauncher ? appLauncher.selectedIndex : -1
|
||||
property int columns: appLauncher ? appLauncher.gridColumns : 4
|
||||
property bool adaptiveColumns: false
|
||||
@@ -167,9 +194,8 @@ Rectangle {
|
||||
appLauncher.launchApp(modelData)
|
||||
}
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
if (contextMenu)
|
||||
contextMenu.show(mouseX, mouseY, modelData)
|
||||
}
|
||||
resultsContainer.itemRightClicked(index, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: () => {
|
||||
if (appLauncher)
|
||||
appLauncher.keyboardNavigationActive = false
|
||||
@@ -188,8 +214,7 @@ Rectangle {
|
||||
currentIndex: resultsGrid.currentIndex
|
||||
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
|
||||
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
|
||||
const modalPos = resultsContainer.parent.mapFromItem(null, mouseX, mouseY)
|
||||
resultsGrid.itemRightClicked(idx, modelData, modalPos.x, modalPos.y)
|
||||
resultsGrid.itemRightClicked(idx, modelData, mouseX, mouseY)
|
||||
}
|
||||
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
|
||||
}
|
||||
|
||||
@@ -749,7 +749,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
visible: SessionService.nvidiaCommand
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -764,11 +764,11 @@ DankPopout {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: SessionService.hasPrimeRun
|
||||
visible: SessionService.nvidiaCommand
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: primeRunMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: nvidiaMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -794,7 +794,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: primeRunMouseArea
|
||||
id: nvidiaMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
@@ -356,7 +356,7 @@ PanelWindow {
|
||||
if (!root.desktopEntry?.actions || root.desktopEntry.actions.length === 0) {
|
||||
return false
|
||||
}
|
||||
return !root.hidePin || (!root.isDmsWindow && root.desktopEntry && SessionService.hasPrimeRun)
|
||||
return !root.hidePin || (!root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand)
|
||||
}
|
||||
width: parent.width
|
||||
height: 1
|
||||
@@ -405,10 +405,10 @@ PanelWindow {
|
||||
|
||||
Rectangle {
|
||||
visible: {
|
||||
const hasPrimeRun = !root.isDmsWindow && root.desktopEntry && SessionService.hasPrimeRun
|
||||
const hasNvidia = !root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand
|
||||
const hasWindow = root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
|
||||
const hasPinOption = !root.hidePin
|
||||
const hasContentAbove = hasPinOption || hasPrimeRun
|
||||
const hasContentAbove = hasPinOption || hasNvidia
|
||||
return hasContentAbove && hasWindow
|
||||
}
|
||||
width: parent.width
|
||||
@@ -417,11 +417,11 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: !root.isDmsWindow && root.desktopEntry && SessionService.hasPrimeRun
|
||||
visible: !root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand
|
||||
width: parent.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
color: primeRunArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: nvidiaArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
@@ -438,7 +438,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: primeRunArea
|
||||
id: nvidiaArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
@@ -235,6 +235,7 @@ Scope {
|
||||
id: spotlightContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
usePopupContextMenu: true
|
||||
|
||||
property var fakeParentModal: QtObject {
|
||||
property bool spotlightOpen: spotlightContainer.visible
|
||||
|
||||
@@ -18,7 +18,7 @@ Singleton {
|
||||
property bool inhibitorAvailable: true
|
||||
property bool idleInhibited: false
|
||||
property string inhibitReason: "Keep system awake"
|
||||
property bool hasPrimeRun: false
|
||||
property string nvidiaCommand: ""
|
||||
|
||||
readonly property bool nativeInhibitorAvailable: {
|
||||
try {
|
||||
@@ -109,7 +109,23 @@ Singleton {
|
||||
command: ["which", "prime-run"]
|
||||
|
||||
onExited: function (exitCode) {
|
||||
hasPrimeRun = (exitCode === 0);
|
||||
if (exitCode === 0) {
|
||||
nvidiaCommand = "prime-run"
|
||||
} else {
|
||||
detectNvidiaOffloadProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: detectNvidiaOffloadProcess
|
||||
running: false
|
||||
command: ["which", "nvidia-offload"]
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
nvidiaCommand = "nvidia-offload"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,10 +161,10 @@ Singleton {
|
||||
return /[;&|<>()$`\\"']/.test(prefix);
|
||||
}
|
||||
|
||||
function launchDesktopEntry(desktopEntry, usePrimeRun) {
|
||||
function launchDesktopEntry(desktopEntry, useNvidia) {
|
||||
let cmd = desktopEntry.command;
|
||||
if (usePrimeRun && hasPrimeRun) {
|
||||
cmd = ["prime-run"].concat(cmd);
|
||||
if (useNvidia && nvidiaCommand) {
|
||||
cmd = [nvidiaCommand].concat(cmd);
|
||||
}
|
||||
|
||||
const userPrefix = SettingsData.launchPrefix?.trim() || "";
|
||||
@@ -176,10 +192,10 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function launchDesktopAction(desktopEntry, action, usePrimeRun) {
|
||||
function launchDesktopAction(desktopEntry, action, useNvidia) {
|
||||
let cmd = action.command;
|
||||
if (usePrimeRun && hasPrimeRun) {
|
||||
cmd = ["prime-run"].concat(cmd);
|
||||
if (useNvidia && nvidiaCommand) {
|
||||
cmd = [nvidiaCommand].concat(cmd);
|
||||
}
|
||||
|
||||
const userPrefix = SettingsData.launchPrefix?.trim() || "";
|
||||
|
||||
@@ -95,10 +95,20 @@ Rectangle {
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.itemClicked(root.index, root.model)
|
||||
} else if (mouse.button === Qt.RightButton && !root.isPlugin) {
|
||||
}
|
||||
}
|
||||
onPressAndHold: mouse => {
|
||||
if (!root.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y)
|
||||
}
|
||||
}
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.RightButton && !root.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y)
|
||||
mouse.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +105,20 @@ Rectangle {
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.itemClicked(root.index, root.model)
|
||||
} else if (mouse.button === Qt.RightButton && !root.isPlugin) {
|
||||
}
|
||||
}
|
||||
onPressAndHold: mouse => {
|
||||
if (!root.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y)
|
||||
}
|
||||
}
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.RightButton && !root.isPlugin) {
|
||||
const globalPos = mapToItem(null, mouse.x, mouse.y)
|
||||
root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y)
|
||||
mouse.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user