mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
Compare commits
3 Commits
6735989455
...
6bf1438ef1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bf1438ef1 | ||
|
|
b819306ab6 | ||
|
|
b140afca8e |
@@ -185,6 +185,12 @@ func runChroma(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
source = string(content)
|
||||
} else {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) != 0 {
|
||||
_ = cmd.Help()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
|
||||
|
||||
@@ -277,6 +277,8 @@ Singleton {
|
||||
property bool spotlightCloseNiriOverview: true
|
||||
property var spotlightSectionViewModes: ({})
|
||||
onSpotlightSectionViewModesChanged: saveSettings()
|
||||
property var appDrawerSectionViewModes: ({})
|
||||
onAppDrawerSectionViewModesChanged: saveSettings()
|
||||
property bool niriOverviewOverlayEnabled: true
|
||||
property string dankLauncherV2Size: "compact"
|
||||
property bool dankLauncherV2BorderEnabled: false
|
||||
|
||||
@@ -135,6 +135,7 @@ var SPEC = {
|
||||
appLauncherGridColumns: { def: 4 },
|
||||
spotlightCloseNiriOverview: { def: true },
|
||||
spotlightSectionViewModes: { def: {} },
|
||||
appDrawerSectionViewModes: { def: {} },
|
||||
niriOverviewOverlayEnabled: { def: true },
|
||||
dankLauncherV2Size: { def: "compact" },
|
||||
dankLauncherV2BorderEnabled: { def: false },
|
||||
|
||||
@@ -6,7 +6,6 @@ import qs.Modals.Changelog
|
||||
import qs.Modals.Clipboard
|
||||
import qs.Modals.Greeter
|
||||
import qs.Modals.Settings
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modals.DankLauncherV2
|
||||
import qs.Modules
|
||||
import qs.Modules.AppDrawer
|
||||
@@ -509,29 +508,21 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SpotlightModal {
|
||||
id: spotlightModal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.spotlightModal = spotlightModal;
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: spotlightV2ModalLoader
|
||||
id: dankLauncherV2ModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.spotlightV2ModalLoader = spotlightV2ModalLoader;
|
||||
PopoutService.dankLauncherV2ModalLoader = dankLauncherV2ModalLoader;
|
||||
}
|
||||
|
||||
DankLauncherV2Modal {
|
||||
id: spotlightV2Modal
|
||||
id: dankLauncherV2Modal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.spotlightV2Modal = spotlightV2Modal;
|
||||
PopoutService._onSpotlightV2ModalLoaded();
|
||||
PopoutService.dankLauncherV2Modal = dankLauncherV2Modal;
|
||||
PopoutService._onDankLauncherV2ModalLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,49 +1025,94 @@ Item {
|
||||
target: "clipboard"
|
||||
}
|
||||
|
||||
// ! spotlight and launcher should be synonymous for backwards compat
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
PopoutService.openSpotlightV2();
|
||||
PopoutService.openDankLauncherV2();
|
||||
return "LAUNCHER_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
PopoutService.closeSpotlightV2();
|
||||
PopoutService.closeDankLauncherV2();
|
||||
return "LAUNCHER_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
PopoutService.toggleSpotlightV2();
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "LAUNCHER_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
function openWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "LAUNCHER_OPEN_FAILED: No mode specified";
|
||||
PopoutService.openSpotlightV2WithMode(mode);
|
||||
PopoutService.openDankLauncherV2WithMode(mode);
|
||||
return `LAUNCHER_OPEN_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function toggleWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "LAUNCHER_TOGGLE_FAILED: No mode specified";
|
||||
PopoutService.toggleSpotlightV2WithMode(mode);
|
||||
PopoutService.toggleDankLauncherV2WithMode(mode);
|
||||
return `LAUNCHER_TOGGLE_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
PopoutService.openSpotlightV2WithQuery(query);
|
||||
PopoutService.openDankLauncherV2WithQuery(query);
|
||||
return "LAUNCHER_OPEN_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
PopoutService.toggleSpotlightV2();
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "LAUNCHER_TOGGLE_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
target: "launcher"
|
||||
}
|
||||
|
||||
// ! spotlight and launcher should be synonymous for backwards compat
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
PopoutService.openDankLauncherV2();
|
||||
return "SPOTLIGHT_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
PopoutService.closeDankLauncherV2();
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
function openWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "SPOTLIGHT_OPEN_FAILED: No mode specified";
|
||||
PopoutService.openDankLauncherV2WithMode(mode);
|
||||
return `SPOTLIGHT_OPEN_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function toggleWith(mode: string): string {
|
||||
if (!mode)
|
||||
return "SPOTLIGHT_TOGGLE_FAILED: No mode specified";
|
||||
PopoutService.toggleDankLauncherV2WithMode(mode);
|
||||
return `SPOTLIGHT_TOGGLE_SUCCESS: ${mode}`;
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
PopoutService.openDankLauncherV2WithQuery(query);
|
||||
return "SPOTLIGHT_OPEN_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
FirstLaunchService.showWelcome();
|
||||
|
||||
@@ -24,8 +24,9 @@ Item {
|
||||
property bool keyboardNavigationActive: false
|
||||
property var sectionViewModes: ({})
|
||||
property var pluginViewPreferences: ({})
|
||||
property int gridColumns: 4
|
||||
property int gridColumns: SettingsData.appLauncherGridColumns
|
||||
property int viewModeVersion: 0
|
||||
property string viewModeContext: "spotlight"
|
||||
|
||||
signal itemExecuted
|
||||
signal searchCompleted
|
||||
@@ -96,7 +97,9 @@ Item {
|
||||
if (sectionViewModes[sectionId])
|
||||
return sectionViewModes[sectionId];
|
||||
|
||||
var savedModes = SettingsData.spotlightSectionViewModes || {};
|
||||
var savedModes = viewModeContext === "appDrawer"
|
||||
? (SettingsData.appDrawerSectionViewModes || {})
|
||||
: (SettingsData.spotlightSectionViewModes || {});
|
||||
if (savedModes[sectionId])
|
||||
return savedModes[sectionId];
|
||||
|
||||
@@ -116,10 +119,17 @@ Item {
|
||||
[sectionId]: mode
|
||||
});
|
||||
viewModeVersion++;
|
||||
if (viewModeContext === "appDrawer") {
|
||||
var savedModes = Object.assign({}, SettingsData.appDrawerSectionViewModes || {}, {
|
||||
[sectionId]: mode
|
||||
});
|
||||
SettingsData.appDrawerSectionViewModes = savedModes;
|
||||
} else {
|
||||
var savedModes = Object.assign({}, SettingsData.spotlightSectionViewModes || {}, {
|
||||
[sectionId]: mode
|
||||
});
|
||||
SettingsData.spotlightSectionViewModes = savedModes;
|
||||
}
|
||||
viewModeChanged(sectionId, mode);
|
||||
}
|
||||
|
||||
@@ -273,10 +283,11 @@ Item {
|
||||
activePluginName = "";
|
||||
clearActivePluginViewPreference();
|
||||
sections = cachedSections.map(function (s) {
|
||||
var copy = Object.assign({}, s);
|
||||
if (collapsedSections[s.id] !== undefined) {
|
||||
var copy = Object.assign({}, s, {
|
||||
items: s.items ? s.items.slice() : []
|
||||
});
|
||||
if (collapsedSections[s.id] !== undefined)
|
||||
copy.collapsed = collapsedSections[s.id];
|
||||
}
|
||||
return copy;
|
||||
});
|
||||
flatModel = Scorer.flattenSections(sections);
|
||||
@@ -344,6 +355,27 @@ Item {
|
||||
}
|
||||
|
||||
if (searchMode === "apps") {
|
||||
var cachedSections = AppSearchService.getCachedDefaultSections();
|
||||
if (cachedSections && !searchQuery) {
|
||||
var appSectionIds = ["favorites", "apps"];
|
||||
sections = cachedSections.filter(function (s) {
|
||||
return appSectionIds.indexOf(s.id) !== -1;
|
||||
}).map(function (s) {
|
||||
var copy = Object.assign({}, s, {
|
||||
items: s.items ? s.items.slice() : []
|
||||
});
|
||||
if (collapsedSections[s.id] !== undefined)
|
||||
copy.collapsed = collapsedSections[s.id];
|
||||
return copy;
|
||||
});
|
||||
flatModel = Scorer.flattenSections(sections);
|
||||
selectedFlatIndex = getFirstItemIndex();
|
||||
updateSelectedItem();
|
||||
isSearching = false;
|
||||
searchCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
var apps = searchApps(searchQuery);
|
||||
for (var i = 0; i < apps.length; i++) {
|
||||
allItems.push(apps[i]);
|
||||
|
||||
@@ -237,7 +237,20 @@ Item {
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
WlrLayershell.namespace: "dms:launcher"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.layer: {
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
|
||||
@@ -12,6 +12,7 @@ FocusScope {
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property var parentModal: null
|
||||
property string viewModeContext: "spotlight"
|
||||
property alias searchField: searchField
|
||||
property alias controller: controller
|
||||
property alias resultsList: resultsList
|
||||
@@ -87,6 +88,7 @@ FocusScope {
|
||||
|
||||
Controller {
|
||||
id: controller
|
||||
viewModeContext: root.viewModeContext
|
||||
|
||||
onItemExecuted: {
|
||||
if (root.parentModal) {
|
||||
|
||||
@@ -59,7 +59,7 @@ Item {
|
||||
itemHeight = 52;
|
||||
} else {
|
||||
var cols = controller.getGridColumns(sectionId);
|
||||
var cellWidth = mode === "tile" ? Math.floor(mainFlickable.width / 3) : Math.floor((mainFlickable.width - (root.gridColumns - 1) * 4) / root.gridColumns);
|
||||
var cellWidth = mode === "tile" ? Math.floor(mainFlickable.width / 3) : Math.floor(mainFlickable.width / root.gridColumns);
|
||||
var cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
var row = Math.floor(itemInSection / cols);
|
||||
itemY = row * cellHeight;
|
||||
@@ -88,7 +88,7 @@ Item {
|
||||
} else {
|
||||
var cols = controller?.getGridColumns(section.id) ?? root.gridColumns;
|
||||
var rows = Math.ceil((section.items?.length ?? 0) / cols);
|
||||
var cellWidth = mode === "tile" ? Math.floor(root.width / 3) : Math.floor((root.width - (cols - 1) * 4) / cols);
|
||||
var cellWidth = mode === "tile" ? Math.floor(root.width / 3) : Math.floor(root.width / cols);
|
||||
var cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
return 32 + rows * cellHeight;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ Item {
|
||||
itemH = 52;
|
||||
} else {
|
||||
var cols = controller.getGridColumns(entry.sectionId);
|
||||
var cellWidth = mode === "tile" ? Math.floor(width / 3) : Math.floor((width - (cols - 1) * 4) / cols);
|
||||
var cellWidth = mode === "tile" ? Math.floor(width / 3) : Math.floor(width / cols);
|
||||
var cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
var row = Math.floor(itemInSection / cols);
|
||||
var col = itemInSection % cols;
|
||||
@@ -245,7 +245,7 @@ Item {
|
||||
visible: sectionDelegate.isGridMode && !sectionDelegate.isCollapsed
|
||||
columns: sectionDelegate.currentViewMode === "tile" ? 3 : root.gridColumns
|
||||
|
||||
readonly property real cellWidth: sectionDelegate.currentViewMode === "tile" ? Math.floor(width / 3) : Math.floor((width - (root.gridColumns - 1) * 4) / root.gridColumns)
|
||||
readonly property real cellWidth: sectionDelegate.currentViewMode === "tile" ? Math.floor(width / 3) : Math.floor(width / root.gridColumns)
|
||||
readonly property real cellHeight: sectionDelegate.currentViewMode === "tile" ? cellWidth * 0.75 : cellWidth + 24
|
||||
|
||||
Repeater {
|
||||
|
||||
@@ -95,7 +95,7 @@ Item {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: Math.floor((contentLoader.width - (root.gridColumns - 1) * 4) / root.gridColumns)
|
||||
width: Math.floor(contentLoader.width / root.gridColumns)
|
||||
height: width + 24
|
||||
item: modelData
|
||||
isSelected: (root.startIndex + index) === root.controller?.selectedFlatIndex
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: controller
|
||||
|
||||
property string searchQuery: ""
|
||||
property alias model: fileModel
|
||||
property int selectedIndex: 0
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool isSearching: false
|
||||
property int totalResults: 0
|
||||
property string searchField: "filename"
|
||||
|
||||
signal searchCompleted
|
||||
|
||||
ListModel {
|
||||
id: fileModel
|
||||
}
|
||||
|
||||
function performSearch() {
|
||||
if (!DSearchService.dsearchAvailable) {
|
||||
model.clear()
|
||||
totalResults = 0
|
||||
isSearching = false
|
||||
return
|
||||
}
|
||||
|
||||
if (searchQuery.length === 0) {
|
||||
model.clear()
|
||||
totalResults = 0
|
||||
isSearching = false
|
||||
return
|
||||
}
|
||||
|
||||
isSearching = true
|
||||
const params = {
|
||||
"limit": 50,
|
||||
"fuzzy": true,
|
||||
"sort": "score",
|
||||
"desc": true
|
||||
}
|
||||
|
||||
if (searchField && searchField !== "all") {
|
||||
params.field = searchField
|
||||
}
|
||||
|
||||
DSearchService.search(searchQuery, params, response => {
|
||||
if (response.error) {
|
||||
model.clear()
|
||||
totalResults = 0
|
||||
isSearching = false
|
||||
return
|
||||
}
|
||||
|
||||
if (response.result) {
|
||||
updateModel(response.result)
|
||||
}
|
||||
|
||||
isSearching = false
|
||||
searchCompleted()
|
||||
})
|
||||
}
|
||||
|
||||
function updateModel(result) {
|
||||
model.clear()
|
||||
totalResults = result.total_hits || 0
|
||||
selectedIndex = 0
|
||||
keyboardNavigationActive = true
|
||||
|
||||
if (!result.hits || result.hits.length === 0) {
|
||||
selectedIndex = -1
|
||||
keyboardNavigationActive = false
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < result.hits.length; i++) {
|
||||
const hit = result.hits[i]
|
||||
const filePath = hit.id || ""
|
||||
const fileName = getFileName(filePath)
|
||||
const fileExt = getFileExtension(fileName)
|
||||
const fileType = determineFileType(fileName, filePath)
|
||||
const dirPath = getDirPath(filePath)
|
||||
|
||||
model.append({
|
||||
"filePath": filePath,
|
||||
"fileName": fileName,
|
||||
"fileExtension": fileExt,
|
||||
"fileType": fileType,
|
||||
"dirPath": dirPath,
|
||||
"score": hit.score || 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(path) {
|
||||
const parts = path.split('/')
|
||||
return parts[parts.length - 1] || path
|
||||
}
|
||||
|
||||
function getFileExtension(fileName) {
|
||||
const parts = fileName.split('.')
|
||||
if (parts.length > 1) {
|
||||
return parts[parts.length - 1].toLowerCase()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function getDirPath(path) {
|
||||
const lastSlash = path.lastIndexOf('/')
|
||||
if (lastSlash > 0) {
|
||||
return path.substring(0, lastSlash)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function determineFileType(fileName, filePath) {
|
||||
const ext = getFileExtension(fileName)
|
||||
|
||||
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]
|
||||
if (imageExts.includes(ext)) {
|
||||
return "image"
|
||||
}
|
||||
|
||||
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]
|
||||
if (videoExts.includes(ext)) {
|
||||
return "video"
|
||||
}
|
||||
|
||||
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
|
||||
if (audioExts.includes(ext)) {
|
||||
return "audio"
|
||||
}
|
||||
|
||||
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]
|
||||
if (codeExts.includes(ext)) {
|
||||
return "code"
|
||||
}
|
||||
|
||||
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]
|
||||
if (docExts.includes(ext)) {
|
||||
return "document"
|
||||
}
|
||||
|
||||
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]
|
||||
if (archiveExts.includes(ext)) {
|
||||
return "archive"
|
||||
}
|
||||
|
||||
if (!ext || fileName.indexOf('.') === -1) {
|
||||
return "binary"
|
||||
}
|
||||
|
||||
return "file"
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (model.count === 0) {
|
||||
return
|
||||
}
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.min(selectedIndex + 1, model.count - 1)
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (model.count === 0) {
|
||||
return
|
||||
}
|
||||
keyboardNavigationActive = true
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||
}
|
||||
|
||||
signal fileOpened
|
||||
|
||||
function openFile(filePath) {
|
||||
if (!filePath || filePath.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let url = filePath
|
||||
if (!url.startsWith("file://")) {
|
||||
url = "file://" + filePath
|
||||
}
|
||||
|
||||
Qt.openUrlExternally(url)
|
||||
fileOpened()
|
||||
}
|
||||
|
||||
function openFolder(filePath) {
|
||||
if (!filePath || filePath.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const lastSlash = filePath.lastIndexOf('/')
|
||||
if (lastSlash <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const dirPath = filePath.substring(0, lastSlash)
|
||||
let url = dirPath
|
||||
if (!url.startsWith("file://")) {
|
||||
url = "file://" + dirPath
|
||||
}
|
||||
|
||||
Qt.openUrlExternally(url)
|
||||
fileOpened()
|
||||
}
|
||||
|
||||
function openSelected() {
|
||||
if (model.count === 0 || selectedIndex < 0 || selectedIndex >= model.count) {
|
||||
return
|
||||
}
|
||||
|
||||
const item = model.get(selectedIndex)
|
||||
if (item && item.filePath) {
|
||||
openFile(item.filePath)
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
searchQuery = ""
|
||||
model.clear()
|
||||
selectedIndex = -1
|
||||
keyboardNavigationActive = false
|
||||
isSearching = false
|
||||
totalResults = 0
|
||||
}
|
||||
|
||||
onSearchQueryChanged: {
|
||||
performSearch()
|
||||
}
|
||||
|
||||
onSearchFieldChanged: {
|
||||
performSearch()
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: entry
|
||||
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property string fileExtension
|
||||
required property string fileType
|
||||
required property string dirPath
|
||||
required property bool isSelected
|
||||
required property int itemIndex
|
||||
|
||||
signal clicked()
|
||||
|
||||
readonly property int iconSize: 40
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Item {
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Image {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
source: fileType === "image" ? `file://${filePath}` : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
cache: true
|
||||
asynchronous: true
|
||||
visible: fileType === "image" && status === Image.Ready
|
||||
sourceSize.width: 128
|
||||
sourceSize.height: 128
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: imagePreview
|
||||
maskEnabled: true
|
||||
maskSource: imageMask
|
||||
visible: fileType === "image" && imagePreview.status === Image.Ready
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
|
||||
Item {
|
||||
id: imageMask
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: "black"
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: getFileTypeColor()
|
||||
visible: fileType !== "image" || imagePreview.status !== Image.Ready
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: getFileIconText()
|
||||
font.pixelSize: fileExtension.length > 0 ? (fileExtension.length > 3 ? Theme.fontSizeSmall - 2 : Theme.fontSizeSmall) : Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - iconSize - Theme.spacingL
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: fileName
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideMiddle
|
||||
wrapMode: Text.NoWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: dirPath
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: entry.clicked()
|
||||
}
|
||||
|
||||
function getFileTypeColor() {
|
||||
switch (fileType) {
|
||||
case "code":
|
||||
return Theme.codeFileColor || Theme.primarySelected
|
||||
case "document":
|
||||
return Theme.docFileColor || Theme.secondarySelected
|
||||
case "video":
|
||||
return Theme.videoFileColor || Theme.tertiarySelected
|
||||
case "audio":
|
||||
return Theme.audioFileColor || Theme.errorSelected
|
||||
case "archive":
|
||||
return Theme.archiveFileColor || Theme.warningSelected
|
||||
case "binary":
|
||||
return Theme.binaryFileColor || Theme.surfaceDim
|
||||
default:
|
||||
return Theme.surfaceLight
|
||||
}
|
||||
}
|
||||
|
||||
function getFileIconText() {
|
||||
if (fileType === "binary") {
|
||||
return "bin"
|
||||
}
|
||||
|
||||
if (fileExtension.length > 0) {
|
||||
return fileExtension
|
||||
}
|
||||
|
||||
return fileName.charAt(0).toUpperCase()
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: resultsContainer
|
||||
|
||||
property var fileSearchController: null
|
||||
|
||||
function resetScroll() {
|
||||
filesList.contentY = 0;
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 32
|
||||
z: 100
|
||||
visible: filesList.contentHeight > filesList.height && (filesList.currentIndex < filesList.count - 1 || filesList.contentY < filesList.contentHeight - filesList.height - 1)
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: filesList
|
||||
|
||||
property int itemHeight: 60
|
||||
property int itemSpacing: Theme.spacingS
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: fileSearchController ? fileSearchController.keyboardNavigationActive : false
|
||||
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index)
|
||||
signal itemRightClicked(int index)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return;
|
||||
const itemY = index * (itemHeight + itemSpacing);
|
||||
const itemBottom = itemY + itemHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastItem = index === count - 1;
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
model: fileSearchController ? fileSearchController.model : null
|
||||
currentIndex: fileSearchController ? fileSearchController.selectedIndex : -1
|
||||
clip: true
|
||||
spacing: itemSpacing
|
||||
focus: true
|
||||
interactive: true
|
||||
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
|
||||
reuseItems: true
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
}
|
||||
|
||||
onItemClicked: function (index) {
|
||||
if (fileSearchController) {
|
||||
const item = fileSearchController.model.get(index);
|
||||
fileSearchController.openFile(item.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
onItemRightClicked: function (index) {
|
||||
if (fileSearchController) {
|
||||
const item = fileSearchController.model.get(index);
|
||||
fileSearchController.openFolder(item.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardNavigationReset: {
|
||||
if (fileSearchController)
|
||||
fileSearchController.keyboardNavigationActive = false;
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property int index
|
||||
required property string filePath
|
||||
required property string fileName
|
||||
required property string fileExtension
|
||||
required property string fileType
|
||||
required property string dirPath
|
||||
|
||||
width: ListView.view.width
|
||||
height: filesList.itemHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: ListView.isCurrentItem ? Theme.widgetBaseHoverColor : fileMouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Item {
|
||||
width: 40
|
||||
height: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: Theme.surfaceLight
|
||||
visible: fileType !== "image"
|
||||
|
||||
DankNFIcon {
|
||||
id: nerdIcon
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const lowerName = fileName.toLowerCase();
|
||||
if (lowerName.startsWith("dockerfile"))
|
||||
return "docker";
|
||||
if (lowerName.startsWith("makefile"))
|
||||
return "makefile";
|
||||
if (lowerName.startsWith("license"))
|
||||
return "license";
|
||||
if (lowerName.startsWith("readme"))
|
||||
return "readme";
|
||||
return fileExtension.toLowerCase();
|
||||
}
|
||||
size: Theme.fontSizeXLarge
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: fileExtension ? (fileExtension.length > 4 ? fileExtension.substring(0, 4) : fileExtension) : "?"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
visible: !nerdIcon.visible
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: fileType === "image"
|
||||
sourceComponent: Image {
|
||||
anchors.fill: parent
|
||||
source: "file://" + filePath
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: false
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1.0
|
||||
maskSource: ShaderEffectSource {
|
||||
sourceItem: Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 40 - Theme.spacingL
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: fileName || ""
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: dirPath || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: fileMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (filesList.hoverUpdatesSelection && !filesList.keyboardNavigationActive)
|
||||
filesList.currentIndex = index;
|
||||
}
|
||||
onPositionChanged: {
|
||||
filesList.keyboardNavigationReset();
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
filesList.itemClicked(index);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
filesList.itemRightClicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: !fileSearchController || !fileSearchController.model || fileSearchController.model.count === 0
|
||||
|
||||
StyledText {
|
||||
property string displayText: {
|
||||
if (!fileSearchController) {
|
||||
return "";
|
||||
}
|
||||
if (!DSearchService.dsearchAvailable) {
|
||||
return I18n.tr("DankSearch not available");
|
||||
}
|
||||
if (fileSearchController.isSearching) {
|
||||
return I18n.tr("Searching...");
|
||||
}
|
||||
if (fileSearchController.searchQuery.length === 0) {
|
||||
return I18n.tr("Enter a search query");
|
||||
}
|
||||
if (!fileSearchController.model || fileSearchController.model.count === 0) {
|
||||
return I18n.tr("No files found");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
text: displayText
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
visible: displayText.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,858 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modules.AppDrawer
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: spotlightKeyHandler
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
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
|
||||
|
||||
property bool editMode: false
|
||||
property var editingApp: null
|
||||
property string editAppId: ""
|
||||
|
||||
function resetScroll() {
|
||||
if (searchMode === "apps") {
|
||||
resultsView.resetScroll();
|
||||
} else {
|
||||
fileSearchResults.resetScroll();
|
||||
}
|
||||
}
|
||||
|
||||
function updateSearchMode() {
|
||||
if (searchField.text.startsWith("/")) {
|
||||
if (searchMode !== "files") {
|
||||
searchMode = "files";
|
||||
}
|
||||
const query = searchField.text.substring(1);
|
||||
fileSearchController.searchQuery = query;
|
||||
} else {
|
||||
if (searchMode !== "apps") {
|
||||
searchMode = "apps";
|
||||
fileSearchController.reset();
|
||||
appLauncher.searchQuery = searchField.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openEditMode(app) {
|
||||
if (!app)
|
||||
return;
|
||||
editingApp = app;
|
||||
editAppId = app.id || app.execString || app.exec || "";
|
||||
const existing = SessionData.getAppOverride(editAppId);
|
||||
editNameField.text = existing?.name || "";
|
||||
editIconField.text = existing?.icon || "";
|
||||
editCommentField.text = existing?.comment || "";
|
||||
editEnvVarsField.text = existing?.envVars || "";
|
||||
editExtraFlagsField.text = existing?.extraFlags || "";
|
||||
editMode = true;
|
||||
Qt.callLater(() => editNameField.forceActiveFocus());
|
||||
}
|
||||
|
||||
function closeEditMode() {
|
||||
editMode = false;
|
||||
editingApp = null;
|
||||
editAppId = "";
|
||||
Qt.callLater(() => searchField.forceActiveFocus());
|
||||
}
|
||||
|
||||
function saveAppOverride() {
|
||||
const override = {};
|
||||
if (editNameField.text.trim())
|
||||
override.name = editNameField.text.trim();
|
||||
if (editIconField.text.trim())
|
||||
override.icon = editIconField.text.trim();
|
||||
if (editCommentField.text.trim())
|
||||
override.comment = editCommentField.text.trim();
|
||||
if (editEnvVarsField.text.trim())
|
||||
override.envVars = editEnvVarsField.text.trim();
|
||||
if (editExtraFlagsField.text.trim())
|
||||
override.extraFlags = editExtraFlagsField.text.trim();
|
||||
SessionData.setAppOverride(editAppId, override);
|
||||
closeEditMode();
|
||||
}
|
||||
|
||||
function resetAppOverride() {
|
||||
SessionData.clearAppOverride(editAppId);
|
||||
closeEditMode();
|
||||
}
|
||||
|
||||
onSearchModeChanged: {
|
||||
if (searchMode === "files") {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
} else {
|
||||
fileSearchController.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
clip: false
|
||||
Keys.onPressed: event => {
|
||||
if (editMode) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectNext();
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectPrevious();
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_J && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectNext();
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_K && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.selectPrevious();
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_L && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_H && event.modifiers & Qt.ControlModifier && searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||
I18n.isRtl ? appLauncher.selectNextInRow() : appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
} else {
|
||||
appLauncher.selectNext();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
} else {
|
||||
appLauncher.selectPrevious();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
} else {
|
||||
appLauncher.selectNext();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
} else {
|
||||
appLauncher.selectPrevious();
|
||||
}
|
||||
} else {
|
||||
fileSearchController.selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
if (searchMode === "apps") {
|
||||
appLauncher.launchSelected();
|
||||
} else if (searchMode === "files") {
|
||||
fileSearchController.openSelected();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Menu || event.key == Qt.Key_F10) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
AppLauncher {
|
||||
id: appLauncher
|
||||
viewMode: SettingsData.spotlightModalViewMode
|
||||
gridColumns: SettingsData.appLauncherGridColumns
|
||||
onAppLaunched: () => {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
if (SettingsData.spotlightCloseNiriOverview && NiriService.inOverview) {
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
onViewModeSelected: mode => {
|
||||
SettingsData.set("spotlightModalViewMode", mode);
|
||||
}
|
||||
}
|
||||
|
||||
FileSearchController {
|
||||
id: fileSearchController
|
||||
onFileOpened: () => {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
if (SettingsData.spotlightCloseNiriOverview && NiriService.inOverview) {
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (editMode)
|
||||
closeEditMode();
|
||||
}
|
||||
}
|
||||
enabled: parentModal !== null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: popupContextMenu
|
||||
function onEditAppRequested(app) {
|
||||
spotlightKeyHandler.openEditMode(app);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: layerContextMenuLoader.item
|
||||
function onEditAppRequested(app) {
|
||||
spotlightKeyHandler.openEditMode(app);
|
||||
}
|
||||
enabled: layerContextMenuLoader.item !== null
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
clip: false
|
||||
visible: !editMode
|
||||
|
||||
Item {
|
||||
id: searchRow
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 56
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DankTextField {
|
||||
id: searchField
|
||||
anchors.left: parent.left
|
||||
anchors.right: buttonsContainer.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: searchMode === "files" ? "folder" : "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
leftIconFocusedColor: Theme.primary
|
||||
showClearButton: true
|
||||
textColor: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: parentModal ? parentModal.spotlightOpen : true
|
||||
placeholderText: ""
|
||||
ignoreLeftRightKeys: appLauncher.viewMode !== "list"
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [spotlightKeyHandler]
|
||||
onTextChanged: {
|
||||
if (searchMode === "apps")
|
||||
appLauncher.searchQuery = text;
|
||||
}
|
||||
onTextEdited: updateSearchMode()
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (parentModal)
|
||||
parentModal.hide();
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
|
||||
if (searchMode === "apps") {
|
||||
if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0)
|
||||
appLauncher.launchSelected();
|
||||
else if (appLauncher.model.count > 0)
|
||||
appLauncher.launchApp(appLauncher.model.get(0));
|
||||
} else if (searchMode === "files") {
|
||||
if (fileSearchController.model.count > 0)
|
||||
fileSearchController.openSelected();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttonsContainer
|
||||
width: viewModeButtons.visible ? viewModeButtons.width : (fileSearchButtons.visible ? fileSearchButtons.width : 0)
|
||||
height: 36
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Row {
|
||||
id: viewModeButtons
|
||||
spacing: Theme.spacingXS
|
||||
visible: searchMode === "apps"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: appLauncher.viewMode === "list" ? Theme.primaryHover : listViewArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "view_list"
|
||||
size: 18
|
||||
color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: listViewArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: appLauncher.setViewMode("list")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: appLauncher.viewMode === "grid" ? Theme.primaryHover : gridViewArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "grid_view"
|
||||
size: 18
|
||||
color: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: gridViewArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: appLauncher.setViewMode("grid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: fileSearchButtons
|
||||
spacing: Theme.spacingXS
|
||||
visible: searchMode === "files"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: filenameFilterButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: fileSearchController.searchField === "filename" ? Theme.primaryHover : filenameFilterArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "title"
|
||||
size: 18
|
||||
color: fileSearchController.searchField === "filename" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: filenameFilterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: fileSearchController.searchField = "filename"
|
||||
onEntered: {
|
||||
filenameTooltipLoader.active = true;
|
||||
Qt.callLater(() => {
|
||||
if (filenameTooltipLoader.item) {
|
||||
const p = mapToItem(null, width / 2, height + Theme.spacingXS);
|
||||
filenameTooltipLoader.item.show(I18n.tr("Search filenames"), p.x, p.y, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
onExited: {
|
||||
if (filenameTooltipLoader.item)
|
||||
filenameTooltipLoader.item.hide();
|
||||
filenameTooltipLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: contentFilterButton
|
||||
width: 36
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: fileSearchController.searchField === "body" ? Theme.primaryHover : contentFilterArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "description"
|
||||
size: 18
|
||||
color: fileSearchController.searchField === "body" ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contentFilterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: fileSearchController.searchField = "body"
|
||||
onEntered: {
|
||||
contentTooltipLoader.active = true;
|
||||
Qt.callLater(() => {
|
||||
if (contentTooltipLoader.item) {
|
||||
const p = mapToItem(null, width / 2, height + Theme.spacingXS);
|
||||
contentTooltipLoader.item.show(I18n.tr("Search file contents"), p.x, p.y, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
onExited: {
|
||||
if (contentTooltipLoader.item)
|
||||
contentTooltipLoader.item.hide();
|
||||
contentTooltipLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
opacity: parentModal?.isClosing ? 0 : 1
|
||||
|
||||
SpotlightResults {
|
||||
id: resultsView
|
||||
anchors.fill: parent
|
||||
appLauncher: spotlightKeyHandler.appLauncher
|
||||
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 {
|
||||
id: fileSearchResults
|
||||
anchors.fill: parent
|
||||
fileSearchController: spotlightKeyHandler.fileSearchController
|
||||
visible: searchMode === "files"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: editView
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: editMode
|
||||
focus: editMode
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
case Qt.Key_S:
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
case Qt.Key_R:
|
||||
if ((event.modifiers & Qt.ControlModifier) && SessionData.getAppOverride(editAppId) !== null) {
|
||||
resetAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "arrow_back"
|
||||
size: 20
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: closeEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
width: 40
|
||||
height: 40
|
||||
source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable"
|
||||
sourceSize.width: 40
|
||||
sourceSize.height: 40
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Edit App")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: editingApp?.name || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
}
|
||||
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: parent.height - y - buttonsRow.height - Theme.spacingM
|
||||
contentHeight: editFieldsColumn.height
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: editFieldsColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Name")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editNameField
|
||||
width: parent.width
|
||||
placeholderText: editingApp?.name || ""
|
||||
keyNavigationTab: editIconField
|
||||
keyNavigationBacktab: editExtraFlagsField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Icon")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editIconField
|
||||
width: parent.width
|
||||
placeholderText: editingApp?.icon || ""
|
||||
keyNavigationTab: editCommentField
|
||||
keyNavigationBacktab: editNameField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Description")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editCommentField
|
||||
width: parent.width
|
||||
placeholderText: editingApp?.comment || ""
|
||||
keyNavigationTab: editEnvVarsField
|
||||
keyNavigationBacktab: editIconField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Environment Variables")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "KEY=value KEY2=value2"
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editEnvVarsField
|
||||
width: parent.width
|
||||
placeholderText: "VAR=value"
|
||||
keyNavigationTab: editExtraFlagsField
|
||||
keyNavigationBacktab: editCommentField
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Extra Arguments")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: editExtraFlagsField
|
||||
width: parent.width
|
||||
placeholderText: "--flag --option=value"
|
||||
keyNavigationTab: editNameField
|
||||
keyNavigationBacktab: editEnvVarsField
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonsRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
id: resetButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: resetButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||
visible: SessionData.getAppOverride(editAppId) !== null
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Reset")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.error
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: resetButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: resetAppOverride()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: cancelButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: cancelButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Cancel")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cancelButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: closeEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: saveButton
|
||||
width: 90
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Save")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primaryText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: saveButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: saveAppOverride()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: filenameTooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentTooltipLoader
|
||||
active: false
|
||||
sourceComponent: DankTooltip {}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight-context-menu"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
property var parentModal: null
|
||||
property real menuPositionX: 0
|
||||
property real menuPositionY: 0
|
||||
|
||||
signal editAppRequested(var app)
|
||||
|
||||
readonly property real shadowBuffer: 5
|
||||
|
||||
screen: parentModal?.effectiveScreen
|
||||
|
||||
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() {
|
||||
if (parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
visible = false;
|
||||
}
|
||||
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible && parentHandler) {
|
||||
parentHandler.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
onEditAppRequested: app => root.editAppRequested(app)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
@@ -1,411 +0,0 @@
|
||||
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 actualItem: (currentApp && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||
|
||||
function getPluginContextMenuActions() {
|
||||
if (!currentApp || !currentApp.isPlugin || !actualItem)
|
||||
return [];
|
||||
|
||||
const pluginId = appLauncher.getPluginIdForItem(actualItem);
|
||||
if (!pluginId) {
|
||||
console.log("[ContextMenu] No pluginId found for item:", JSON.stringify(actualItem.categories));
|
||||
return [];
|
||||
}
|
||||
|
||||
const instance = PluginService.pluginInstances[pluginId];
|
||||
if (!instance) {
|
||||
console.log("[ContextMenu] No instance for pluginId:", pluginId);
|
||||
return [];
|
||||
}
|
||||
if (typeof instance.getContextMenuActions !== "function") {
|
||||
console.log("[ContextMenu] Instance has no getContextMenuActions:", pluginId);
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions = instance.getContextMenuActions(actualItem);
|
||||
if (!Array.isArray(actions))
|
||||
return [];
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
function executePluginAction(actionData) {
|
||||
if (!currentApp || !actualItem)
|
||||
return;
|
||||
|
||||
const pluginId = appLauncher.getPluginIdForItem(actualItem);
|
||||
if (!pluginId)
|
||||
return;
|
||||
|
||||
const instance = PluginService.pluginInstances[pluginId];
|
||||
if (!instance)
|
||||
return;
|
||||
|
||||
if (typeof actionData === "function") {
|
||||
actionData();
|
||||
} else if (typeof instance.executeContextMenuAction === "function") {
|
||||
instance.executeContextMenuAction(actualItem, actionData);
|
||||
}
|
||||
|
||||
if (appLauncher)
|
||||
appLauncher.updateFilteredModel();
|
||||
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
readonly property bool isRegularApp: desktopEntry && !currentApp?.isPlugin && !currentApp?.isCore && !currentApp?.isAction && !currentApp?.isBuiltInLauncher
|
||||
|
||||
signal editAppRequested(var app)
|
||||
|
||||
function hideCurrentApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
const appId = desktopEntry.id || desktopEntry.execString || "";
|
||||
SessionData.hideApp(appId);
|
||||
if (appLauncher)
|
||||
appLauncher.updateFilteredModel();
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function editCurrentApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
editAppRequested(desktopEntry);
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
readonly property var menuItems: {
|
||||
const items = [];
|
||||
|
||||
if (currentApp && currentApp.isPlugin) {
|
||||
const pluginActions = getPluginContextMenuActions();
|
||||
for (let i = 0; i < pluginActions.length; i++) {
|
||||
const act = pluginActions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "",
|
||||
text: act.text || act.name || "",
|
||||
action: () => executePluginAction(act.action)
|
||||
});
|
||||
}
|
||||
if (items.length === 0) {
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "content_copy",
|
||||
text: I18n.tr("Copy"),
|
||||
action: launchCurrentApp
|
||||
});
|
||||
}
|
||||
return 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 (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 (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 => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Down:
|
||||
selectNext();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Up:
|
||||
selectPrevious();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
activateSelected();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Escape:
|
||||
case Qt.Key_Left:
|
||||
hideRequested();
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property var appLauncher: null
|
||||
property var parentHandler: null
|
||||
property var searchField: null
|
||||
|
||||
signal editAppRequested(var app)
|
||||
|
||||
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?.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: SpotlightContextMenuContent {
|
||||
id: menuContent
|
||||
appLauncher: root.appLauncher
|
||||
onHideRequested: root.hide()
|
||||
onEditAppRequested: app => root.editAppRequested(app)
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
|
||||
DankModal {
|
||||
id: spotlightModal
|
||||
|
||||
layerNamespace: "dms:spotlight"
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [spotlightModal.contentWindow]
|
||||
active: spotlightModal.useHyprlandFocusGrab && spotlightModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
property bool spotlightOpen: false
|
||||
property alias spotlightContent: spotlightContentInstance
|
||||
property bool openedFromOverview: false
|
||||
property bool isClosing: false
|
||||
|
||||
function resetContent() {
|
||||
if (!spotlightContent)
|
||||
return;
|
||||
if (spotlightContent.appLauncher)
|
||||
spotlightContent.appLauncher.reset();
|
||||
if (spotlightContent.fileSearchController)
|
||||
spotlightContent.fileSearchController.reset();
|
||||
if (spotlightContent.resetScroll)
|
||||
spotlightContent.resetScroll();
|
||||
if (spotlightContent.searchField)
|
||||
spotlightContent.searchField.text = "";
|
||||
spotlightContent.searchMode = "apps";
|
||||
}
|
||||
|
||||
function show() {
|
||||
openedFromOverview = false;
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
spotlightOpen = true;
|
||||
open();
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent?.appLauncher)
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
if (spotlightContent?.searchField)
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function showWithQuery(query) {
|
||||
openedFromOverview = false;
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
spotlightOpen = true;
|
||||
if (spotlightContent?.searchField)
|
||||
spotlightContent.searchField.text = query;
|
||||
open();
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent?.appLauncher) {
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
spotlightContent.appLauncher.searchQuery = query;
|
||||
}
|
||||
if (spotlightContent?.searchField)
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
|
||||
function showWithEditApp(app) {
|
||||
openedFromOverview = false;
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
spotlightOpen = true;
|
||||
open();
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent?.appLauncher)
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
if (spotlightContent?.openEditMode)
|
||||
spotlightContent.openEditMode(app);
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
spotlightOpen = false;
|
||||
close();
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
isClosing = false;
|
||||
resetContent();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (spotlightOpen) {
|
||||
hide();
|
||||
} else {
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeVisible: spotlightOpen
|
||||
modalWidth: 500
|
||||
modalHeight: 600
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
cornerRadius: Theme.cornerRadius
|
||||
borderColor: Theme.outlineMedium
|
||||
borderWidth: 1
|
||||
enableShadow: true
|
||||
keepContentLoaded: true
|
||||
animationScaleCollapsed: 0.96
|
||||
animationDuration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
animationExitCurve: Theme.expressiveCurves.emphasized
|
||||
onVisibleChanged: () => {
|
||||
if (visible && !spotlightOpen) {
|
||||
show();
|
||||
}
|
||||
if (visible && spotlightContent) {
|
||||
Qt.callLater(() => {
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
onBackgroundClicked: () => {
|
||||
return hide();
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onCloseAllModalsExcept(excludedModal) {
|
||||
if (excludedModal !== spotlightModal && !allowStacking && spotlightOpen) {
|
||||
spotlightOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
target: ModalManager
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
spotlightModal.show();
|
||||
return "SPOTLIGHT_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
spotlightModal.hide();
|
||||
return "SPOTLIGHT_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
spotlightModal.toggle();
|
||||
return "SPOTLIGHT_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
spotlightModal.showWithQuery(query);
|
||||
return "SPOTLIGHT_OPEN_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
if (spotlightModal.spotlightOpen) {
|
||||
spotlightModal.hide();
|
||||
} else {
|
||||
spotlightModal.showWithQuery(query);
|
||||
}
|
||||
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
target: "spotlight"
|
||||
}
|
||||
|
||||
SpotlightContent {
|
||||
id: spotlightContentInstance
|
||||
|
||||
parentModal: spotlightModal
|
||||
}
|
||||
|
||||
directContent: spotlightContentInstance
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: resultsContainer
|
||||
|
||||
property var appLauncher: null
|
||||
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.contentY = 0;
|
||||
if (gridLoader.item) {
|
||||
gridLoader.item.contentY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 32
|
||||
z: 100
|
||||
visible: {
|
||||
if (!appLauncher)
|
||||
return false;
|
||||
const view = appLauncher.viewMode === "list" ? resultsList : (gridLoader.item || resultsList);
|
||||
const isLastItem = appLauncher.viewMode === "list" ? view.currentIndex >= view.count - 1 : (gridLoader.item ? Math.floor(view.currentIndex / view.actualColumns) >= Math.floor((view.count - 1) / view.actualColumns) : false);
|
||||
const hasOverflow = view.contentHeight > view.height;
|
||||
const atBottom = view.contentY >= view.contentHeight - view.height - 1;
|
||||
return hasOverflow && (!isLastItem || !atBottom);
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: resultsList
|
||||
|
||||
property int itemHeight: 60
|
||||
property int iconSize: 40
|
||||
property bool showDescription: true
|
||||
property int itemSpacing: Theme.spacingS
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
|
||||
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return;
|
||||
const itemY = index * (itemHeight + itemSpacing);
|
||||
const itemBottom = itemY + itemHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastItem = index === count - 1;
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
visible: appLauncher && appLauncher.viewMode === "list"
|
||||
model: appLauncher ? appLauncher.model : null
|
||||
currentIndex: appLauncher ? appLauncher.selectedIndex : -1
|
||||
clip: true
|
||||
spacing: itemSpacing
|
||||
focus: true
|
||||
interactive: true
|
||||
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
|
||||
reuseItems: true
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
}
|
||||
onItemClicked: (index, modelData) => {
|
||||
if (appLauncher)
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
resultsContainer.itemRightClicked(index, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: () => {
|
||||
if (appLauncher)
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
|
||||
delegate: AppLauncherListDelegate {
|
||||
listView: resultsList
|
||||
itemHeight: resultsList.itemHeight
|
||||
iconSize: resultsList.iconSize
|
||||
showDescription: resultsList.showDescription
|
||||
hoverUpdatesSelection: resultsList.hoverUpdatesSelection
|
||||
keyboardNavigationActive: resultsList.keyboardNavigationActive
|
||||
isCurrentItem: ListView.isCurrentItem
|
||||
iconMaterialSizeAdjustment: 0
|
||||
iconUnicodeScale: 0.8
|
||||
onItemClicked: (idx, modelData) => resultsList.itemClicked(idx, modelData)
|
||||
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
|
||||
resultsList.itemRightClicked(idx, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: resultsList.keyboardNavigationReset
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: gridLoader
|
||||
|
||||
property real _lastWidth: 0
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: 1
|
||||
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;
|
||||
active = false;
|
||||
Qt.callLater(() => {
|
||||
active = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
sourceComponent: Component {
|
||||
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
|
||||
property int minCellWidth: 120
|
||||
property int maxCellWidth: 160
|
||||
property real iconSizeRatio: 0.55
|
||||
property int maxIconSize: 48
|
||||
property int minIconSize: 32
|
||||
property bool hoverUpdatesSelection: false
|
||||
property bool keyboardNavigationActive: appLauncher ? appLauncher.keyboardNavigationActive : false
|
||||
property real baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : width / columns
|
||||
property real baseCellHeight: baseCellWidth + 20
|
||||
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
|
||||
property int remainingSpace: width - (actualColumns * cellWidth)
|
||||
|
||||
signal keyboardNavigationReset
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
return;
|
||||
const itemY = Math.floor(index / actualColumns) * cellHeight;
|
||||
const itemBottom = itemY + cellHeight;
|
||||
const fadeHeight = 32;
|
||||
const isLastRow = Math.floor(index / actualColumns) >= Math.floor((count - 1) / actualColumns);
|
||||
if (itemY < contentY)
|
||||
contentY = itemY;
|
||||
else if (itemBottom > contentY + height - (isLastRow ? 0 : fadeHeight))
|
||||
contentY = Math.min(itemBottom - height + (isLastRow ? 0 : fadeHeight), contentHeight - height);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
model: appLauncher ? appLauncher.model : null
|
||||
clip: true
|
||||
cellWidth: baseCellWidth
|
||||
cellHeight: baseCellHeight
|
||||
focus: true
|
||||
interactive: true
|
||||
cacheBuffer: Math.max(0, Math.min(height * 2, 1000))
|
||||
reuseItems: true
|
||||
onCurrentIndexChanged: {
|
||||
if (keyboardNavigationActive)
|
||||
ensureVisible(currentIndex);
|
||||
}
|
||||
onItemClicked: (index, modelData) => {
|
||||
if (appLauncher)
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||
resultsContainer.itemRightClicked(index, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: () => {
|
||||
if (appLauncher)
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
|
||||
delegate: AppLauncherGridDelegate {
|
||||
gridView: resultsGrid
|
||||
cellWidth: resultsGrid.cellWidth
|
||||
cellHeight: resultsGrid.cellHeight
|
||||
minIconSize: resultsGrid.minIconSize
|
||||
maxIconSize: resultsGrid.maxIconSize
|
||||
iconSizeRatio: resultsGrid.iconSizeRatio
|
||||
hoverUpdatesSelection: resultsGrid.hoverUpdatesSelection
|
||||
keyboardNavigationActive: resultsGrid.keyboardNavigationActive
|
||||
currentIndex: resultsGrid.currentIndex
|
||||
onItemClicked: (idx, modelData) => resultsGrid.itemClicked(idx, modelData)
|
||||
onItemRightClicked: (idx, modelData, mouseX, mouseY) => {
|
||||
resultsGrid.itemRightClicked(idx, modelData, mouseX, mouseY);
|
||||
}
|
||||
onKeyboardNavigationReset: resultsGrid.keyboardNavigationReset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,456 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon).
|
||||
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
|
||||
// likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa.
|
||||
|
||||
property string searchQuery: ""
|
||||
property string selectedCategory: I18n.tr("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
|
||||
property bool keyboardNavigationActive: false
|
||||
property bool suppressUpdatesWhileLaunching: false
|
||||
property var categories: []
|
||||
readonly property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
|
||||
property var appUsageRanking: AppUsageHistoryData.appUsageRanking || {}
|
||||
property alias model: filteredModel
|
||||
property var _uniqueApps: []
|
||||
property bool _initialized: false
|
||||
property bool _isTriggered: false
|
||||
property string _triggeredCategory: ""
|
||||
property bool _updatingFromTrigger: false
|
||||
|
||||
signal appLaunched(var app)
|
||||
signal categorySelected(string category)
|
||||
signal viewModeSelected(string mode)
|
||||
|
||||
function ensureInitialized() {
|
||||
if (_initialized)
|
||||
return;
|
||||
_initialized = true;
|
||||
updateCategories();
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
function updateCategories() {
|
||||
const allCategories = AppSearchService.getAllCategories().filter(cat => cat !== "Education" && cat !== "Science");
|
||||
const result = [I18n.tr("All")];
|
||||
categories = result.concat(allCategories.filter(cat => cat !== I18n.tr("All")));
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
function onPluginLoaded() {
|
||||
updateCategories();
|
||||
}
|
||||
function onPluginUnloaded() {
|
||||
updateCategories();
|
||||
}
|
||||
function onPluginListUpdated() {
|
||||
updateCategories();
|
||||
}
|
||||
function onRequestLauncherUpdate(pluginId) {
|
||||
// Only update if we are actually looking at this plugin or in All category
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onSortAppsAlphabeticallyChanged() {
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionData
|
||||
function onHiddenAppsChanged() {
|
||||
updateFilteredModel();
|
||||
}
|
||||
function onAppOverridesChanged() {
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilteredModel() {
|
||||
if (suppressUpdatesWhileLaunching) {
|
||||
suppressUpdatesWhileLaunching = false;
|
||||
return;
|
||||
}
|
||||
filteredModel.clear();
|
||||
selectedIndex = 0;
|
||||
keyboardNavigationActive = false;
|
||||
|
||||
const triggerResult = checkPluginTriggers(searchQuery);
|
||||
if (triggerResult.triggered) {
|
||||
console.log("AppLauncher: Plugin trigger detected:", triggerResult.trigger, "for plugin:", triggerResult.pluginId);
|
||||
}
|
||||
|
||||
let apps = [];
|
||||
const allCategory = I18n.tr("All");
|
||||
const emptyTriggerPlugins = typeof PluginService !== "undefined" ? PluginService.getPluginsWithEmptyTrigger() : [];
|
||||
|
||||
if (triggerResult.triggered) {
|
||||
_isTriggered = true;
|
||||
_triggeredCategory = triggerResult.pluginCategory;
|
||||
_updatingFromTrigger = true;
|
||||
selectedCategory = triggerResult.pluginCategory;
|
||||
_updatingFromTrigger = false;
|
||||
if (triggerResult.isBuiltIn) {
|
||||
apps = AppSearchService.getBuiltInLauncherItems(triggerResult.pluginId, triggerResult.query);
|
||||
} else {
|
||||
apps = AppSearchService.getPluginItems(triggerResult.pluginCategory, triggerResult.query);
|
||||
}
|
||||
} else {
|
||||
if (_isTriggered) {
|
||||
_updatingFromTrigger = true;
|
||||
selectedCategory = allCategory;
|
||||
_updatingFromTrigger = false;
|
||||
_isTriggered = false;
|
||||
_triggeredCategory = "";
|
||||
}
|
||||
if (searchQuery.length === 0) {
|
||||
if (selectedCategory === allCategory) {
|
||||
let emptyTriggerItems = [];
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId);
|
||||
const pluginCategory = plugin.name || pluginId;
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, "");
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
const builtInEmptyTrigger = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
|
||||
builtInEmptyTrigger.forEach(pluginId => {
|
||||
const items = AppSearchService.getBuiltInLauncherItems(pluginId, "");
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
const coreItems = AppSearchService.getCoreApps("");
|
||||
apps = AppSearchService.getVisibleApplications().concat(emptyTriggerItems).concat(coreItems);
|
||||
} else {
|
||||
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults);
|
||||
const coreItems = AppSearchService.getCoreApps("").filter(app => app.categories.includes(selectedCategory));
|
||||
apps = apps.concat(coreItems);
|
||||
}
|
||||
} else {
|
||||
if (selectedCategory === allCategory) {
|
||||
apps = AppSearchService.searchApplications(searchQuery);
|
||||
|
||||
let emptyTriggerItems = [];
|
||||
emptyTriggerPlugins.forEach(pluginId => {
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId);
|
||||
const pluginCategory = plugin.name || pluginId;
|
||||
const items = AppSearchService.getPluginItems(pluginCategory, searchQuery);
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
const builtInEmptyTrigger = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
|
||||
builtInEmptyTrigger.forEach(pluginId => {
|
||||
const items = AppSearchService.getBuiltInLauncherItems(pluginId, searchQuery);
|
||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||
});
|
||||
|
||||
const coreItems = AppSearchService.getCoreApps(searchQuery);
|
||||
apps = apps.concat(emptyTriggerItems).concat(coreItems);
|
||||
} else {
|
||||
const categoryApps = AppSearchService.getAppsInCategory(selectedCategory);
|
||||
if (categoryApps.length > 0) {
|
||||
const allSearchResults = AppSearchService.searchApplications(searchQuery);
|
||||
const categoryNames = new Set(categoryApps.map(app => app.name));
|
||||
apps = allSearchResults.filter(searchApp => categoryNames.has(searchApp.name)).slice(0, maxResults);
|
||||
} else {
|
||||
apps = [];
|
||||
}
|
||||
|
||||
const coreItems = AppSearchService.getCoreApps(searchQuery).filter(app => app.categories.includes(selectedCategory));
|
||||
apps = apps.concat(coreItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchQuery.length === 0) {
|
||||
if (SettingsData.sortAppsAlphabetically) {
|
||||
apps = apps.sort((a, b) => {
|
||||
return (a.name || "").localeCompare(b.name || "");
|
||||
});
|
||||
} else {
|
||||
apps = apps.sort((a, b) => {
|
||||
const aId = a.id || a.execString || a.exec || "";
|
||||
const bId = b.id || b.execString || b.exec || "";
|
||||
const aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0;
|
||||
const bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0;
|
||||
if (aUsage !== bUsage) {
|
||||
return bUsage - aUsage;
|
||||
}
|
||||
return (a.name || "").localeCompare(b.name || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const seenNames = new Set();
|
||||
const uniqueApps = [];
|
||||
apps.forEach(app => {
|
||||
if (app) {
|
||||
const itemKey = app.name + "|" + (app.execString || app.exec || app.action || "");
|
||||
if (seenNames.has(itemKey)) {
|
||||
return;
|
||||
}
|
||||
seenNames.add(itemKey);
|
||||
uniqueApps.push(app);
|
||||
|
||||
const isPluginItem = app.isCore ? false : (app.action !== undefined);
|
||||
filteredModel.append({
|
||||
"name": app.name || "",
|
||||
"exec": app.execString || app.exec || app.action || "",
|
||||
"icon": app.icon !== undefined ? String(app.icon) : (isPluginItem ? "" : "application-x-executable"),
|
||||
"comment": app.comment || "",
|
||||
"categories": app.categories || [],
|
||||
"isPlugin": isPluginItem,
|
||||
"isCore": app.isCore === true,
|
||||
"isBuiltInLauncher": app.isBuiltInLauncher === true,
|
||||
"isAction": app.isAction === true,
|
||||
"appIndex": uniqueApps.length - 1,
|
||||
"pinned": app._pinned === true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
root._uniqueApps = uniqueApps;
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (filteredModel.count === 0) {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = viewMode === "grid" ? Math.min(selectedIndex + gridColumns, filteredModel.count - 1) : Math.min(selectedIndex + 1, filteredModel.count - 1);
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (filteredModel.count === 0) {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = viewMode === "grid" ? Math.max(selectedIndex - gridColumns, 0) : Math.max(selectedIndex - 1, 0);
|
||||
}
|
||||
|
||||
function selectNextInRow() {
|
||||
if (filteredModel.count === 0 || viewMode !== "grid") {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
|
||||
}
|
||||
|
||||
function selectPreviousInRow() {
|
||||
if (filteredModel.count === 0 || viewMode !== "grid") {
|
||||
return;
|
||||
}
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||
}
|
||||
|
||||
function launchSelected() {
|
||||
if (filteredModel.count === 0 || selectedIndex < 0 || selectedIndex >= filteredModel.count) {
|
||||
return;
|
||||
}
|
||||
const selectedApp = filteredModel.get(selectedIndex);
|
||||
launchApp(selectedApp);
|
||||
}
|
||||
|
||||
function launchApp(appData) {
|
||||
if (!appData || typeof appData.appIndex === "undefined" || appData.appIndex < 0 || appData.appIndex >= _uniqueApps.length)
|
||||
return;
|
||||
suppressUpdatesWhileLaunching = true;
|
||||
|
||||
const actualApp = _uniqueApps[appData.appIndex];
|
||||
|
||||
if (appData.isBuiltInLauncher) {
|
||||
AppSearchService.executeBuiltInLauncherItem(actualApp);
|
||||
appLaunched(appData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (appData.isCore) {
|
||||
AppSearchService.executeCoreApp(actualApp);
|
||||
appLaunched(appData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (appData.isPlugin) {
|
||||
const pluginId = getPluginIdForItem(actualApp);
|
||||
if (pluginId) {
|
||||
AppSearchService.executePluginItem(actualApp, pluginId);
|
||||
appLaunched(appData);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (appData.isAction && actualApp.parentApp && actualApp.actionData) {
|
||||
SessionService.launchDesktopAction(actualApp.parentApp, actualApp.actionData);
|
||||
appLaunched(appData);
|
||||
AppUsageHistoryData.addAppUsage(actualApp.parentApp);
|
||||
return;
|
||||
}
|
||||
|
||||
SessionService.launchDesktopEntry(actualApp);
|
||||
appLaunched(appData);
|
||||
AppUsageHistoryData.addAppUsage(actualApp);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
suppressUpdatesWhileLaunching = false;
|
||||
searchQuery = "";
|
||||
selectedIndex = 0;
|
||||
setCategory(I18n.tr("All"));
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
function setCategory(category) {
|
||||
selectedCategory = category;
|
||||
categorySelected(category);
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
viewMode = mode;
|
||||
viewModeSelected(mode);
|
||||
}
|
||||
|
||||
onSearchQueryChanged: {
|
||||
if (!_initialized)
|
||||
return;
|
||||
if (debounceSearch) {
|
||||
searchDebounceTimer.restart();
|
||||
} else {
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedCategoryChanged: {
|
||||
if (_updatingFromTrigger || !_initialized)
|
||||
return;
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
onAppUsageRankingChanged: {
|
||||
if (_initialized)
|
||||
updateFilteredModel();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DesktopEntries
|
||||
function onApplicationsChanged() {
|
||||
if (!root._initialized)
|
||||
return;
|
||||
root.updateCategories();
|
||||
root.updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: filteredModel
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchDebounceTimer
|
||||
|
||||
interval: root.debounceInterval
|
||||
repeat: false
|
||||
onTriggered: updateFilteredModel()
|
||||
}
|
||||
|
||||
function checkPluginTriggers(query) {
|
||||
if (!query)
|
||||
return {
|
||||
triggered: false,
|
||||
pluginCategory: "",
|
||||
query: ""
|
||||
};
|
||||
|
||||
const builtInTriggers = AppSearchService.getBuiltInLauncherTriggers();
|
||||
for (const trigger in builtInTriggers) {
|
||||
if (!query.startsWith(trigger))
|
||||
continue;
|
||||
const pluginId = builtInTriggers[trigger];
|
||||
const plugin = AppSearchService.builtInPlugins[pluginId];
|
||||
if (!plugin)
|
||||
continue;
|
||||
return {
|
||||
triggered: true,
|
||||
pluginId: pluginId,
|
||||
pluginCategory: plugin.name,
|
||||
query: query.substring(trigger.length).trim(),
|
||||
trigger: trigger,
|
||||
isBuiltIn: true
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof PluginService === "undefined")
|
||||
return {
|
||||
triggered: false,
|
||||
pluginCategory: "",
|
||||
query: ""
|
||||
};
|
||||
|
||||
const triggers = PluginService.getAllPluginTriggers();
|
||||
for (const trigger in triggers) {
|
||||
if (!query.startsWith(trigger))
|
||||
continue;
|
||||
const pluginId = triggers[trigger];
|
||||
const plugin = PluginService.getLauncherPlugin(pluginId);
|
||||
if (!plugin)
|
||||
continue;
|
||||
return {
|
||||
triggered: true,
|
||||
pluginId: pluginId,
|
||||
pluginCategory: plugin.name || pluginId,
|
||||
query: query.substring(trigger.length).trim(),
|
||||
trigger: trigger,
|
||||
isBuiltIn: false
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
triggered: false,
|
||||
pluginCategory: "",
|
||||
query: ""
|
||||
};
|
||||
}
|
||||
|
||||
function getPluginIdForItem(item) {
|
||||
if (!item || !item.categories || typeof PluginService === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins();
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId];
|
||||
const pluginCategory = plugin.name || pluginId;
|
||||
|
||||
let hasCategory = false;
|
||||
if (Array.isArray(item.categories)) {
|
||||
hasCategory = item.categories.includes(pluginCategory);
|
||||
} else if (item.categories && typeof item.categories.count !== "undefined") {
|
||||
for (let i = 0; i < item.categories.count; i++) {
|
||||
if (item.categories.get(i) === pluginCategory) {
|
||||
hasCategory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCategory) {
|
||||
return pluginId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var categories: []
|
||||
property string selectedCategory: I18n.tr("All")
|
||||
property bool compact: false
|
||||
|
||||
signal categorySelected(string category)
|
||||
|
||||
readonly property int maxCompactItems: 8
|
||||
readonly property int itemHeight: 36
|
||||
readonly property color selectedBorderColor: "transparent"
|
||||
readonly property color unselectedBorderColor: "transparent"
|
||||
|
||||
function handleCategoryClick(category) {
|
||||
categorySelected(category)
|
||||
}
|
||||
|
||||
function getButtonWidth(itemCount, containerWidth) {
|
||||
return itemCount > 0 ? (containerWidth - (itemCount - 1) * Theme.spacingS) / itemCount : 0
|
||||
}
|
||||
|
||||
height: compact ? itemHeight : (itemHeight * 2 + Theme.spacingS)
|
||||
|
||||
Row {
|
||||
visible: compact
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: categories ? categories.slice(0, Math.min(categories.length || 0, maxCompactItems)) : []
|
||||
|
||||
Rectangle {
|
||||
property int itemCount: Math.min(categories ? categories.length || 0 : 0, maxCompactItems)
|
||||
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
|
||||
StyledText {
|
||||
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: root.handleCategoryClick(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
visible: !compact
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: categories ? categories.slice(0, Math.min(4, categories.length || 0)) : []
|
||||
|
||||
Rectangle {
|
||||
property int itemCount: Math.min(4, categories ? categories.length || 0 : 0)
|
||||
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
|
||||
|
||||
StyledText {
|
||||
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: root.handleCategoryClick(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: categories && categories.length > 4
|
||||
|
||||
Repeater {
|
||||
model: categories && categories.length > 4 ? categories.slice(4) : []
|
||||
|
||||
Rectangle {
|
||||
property int itemCount: categories && categories.length > 4 ? categories.length - 4 : 0
|
||||
|
||||
height: root.itemHeight
|
||||
width: root.getButtonWidth(itemCount, parent.width)
|
||||
radius: Theme.cornerRadius
|
||||
color: selectedCategory === modelData ? Theme.primary : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: selectedCategory === modelData ? selectedBorderColor : unselectedBorderColor
|
||||
|
||||
StyledText {
|
||||
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: root.handleCategoryClick(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ Scope {
|
||||
property string searchActiveScreen: ""
|
||||
property bool isClosing: false
|
||||
property bool releaseKeyboard: false
|
||||
readonly property bool spotlightModalOpen: PopoutService.spotlightModal?.spotlightOpen ?? false
|
||||
readonly property bool spotlightModalOpen: PopoutService.dankLauncherV2Modal?.spotlightOpen ?? false
|
||||
property bool overlayActive: NiriService.inOverview || searchActive
|
||||
|
||||
function showSpotlight(screenName) {
|
||||
|
||||
@@ -41,7 +41,7 @@ property var popoutService: null
|
||||
| ------------------ | ------------------------- | ------------------------- | -------------------------------------------------- |
|
||||
| Settings | `openSettings()` | `closeSettings()` | Full settings interface |
|
||||
| Clipboard History | `openClipboardHistory()` | `closeClipboardHistory()` | Clipboard integration |
|
||||
| Spotlight | `openSpotlight()` | `closeSpotlight()` | Command launcher |
|
||||
| Launcher | `openDankLauncherV2()` | `closeDankLauncherV2()` | Command launcher, also has `toggleDankLauncherV2()` |
|
||||
| Power Menu | `openPowerMenu()` | `closePowerMenu()` | Also has `togglePowerMenu()` |
|
||||
| Process List Modal | `showProcessListModal()` | `hideProcessListModal()` | Fullscreen version, has `toggleProcessListModal()` |
|
||||
| Color Picker | `showColorPicker()` | `hideColorPicker()` | Theme color selection |
|
||||
|
||||
@@ -28,20 +28,62 @@ PluginSettings {
|
||||
label: "Popout to Open"
|
||||
description: "Select which popout or modal opens when you click the widget"
|
||||
options: [
|
||||
{label: "Control Center", value: "controlCenter"},
|
||||
{label: "Notification Center", value: "notificationCenter"},
|
||||
{label: "App Drawer", value: "appDrawer"},
|
||||
{label: "Process List", value: "processList"},
|
||||
{label: "DankDash", value: "dankDash"},
|
||||
{label: "Battery Info", value: "battery"},
|
||||
{label: "VPN", value: "vpn"},
|
||||
{label: "System Update", value: "systemUpdate"},
|
||||
{label: "Settings", value: "settings"},
|
||||
{label: "Clipboard History", value: "clipboardHistory"},
|
||||
{label: "Spotlight", value: "spotlight"},
|
||||
{label: "Power Menu", value: "powerMenu"},
|
||||
{label: "Color Picker", value: "colorPicker"},
|
||||
{label: "Notepad", value: "notepad"}
|
||||
{
|
||||
label: "Control Center",
|
||||
value: "controlCenter"
|
||||
},
|
||||
{
|
||||
label: "Notification Center",
|
||||
value: "notificationCenter"
|
||||
},
|
||||
{
|
||||
label: "App Drawer",
|
||||
value: "appDrawer"
|
||||
},
|
||||
{
|
||||
label: "Process List",
|
||||
value: "processList"
|
||||
},
|
||||
{
|
||||
label: "DankDash",
|
||||
value: "dankDash"
|
||||
},
|
||||
{
|
||||
label: "Battery Info",
|
||||
value: "battery"
|
||||
},
|
||||
{
|
||||
label: "VPN",
|
||||
value: "vpn"
|
||||
},
|
||||
{
|
||||
label: "System Update",
|
||||
value: "systemUpdate"
|
||||
},
|
||||
{
|
||||
label: "Settings",
|
||||
value: "settings"
|
||||
},
|
||||
{
|
||||
label: "Clipboard History",
|
||||
value: "clipboardHistory"
|
||||
},
|
||||
{
|
||||
label: "Spotlight",
|
||||
value: "spotlight"
|
||||
},
|
||||
{
|
||||
label: "Power Menu",
|
||||
value: "powerMenu"
|
||||
},
|
||||
{
|
||||
label: "Color Picker",
|
||||
value: "colorPicker"
|
||||
},
|
||||
{
|
||||
label: "Notepad",
|
||||
value: "notepad"
|
||||
}
|
||||
]
|
||||
defaultValue: "controlCenter"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
@@ -23,7 +21,7 @@ PluginComponent {
|
||||
"systemUpdate": (x, y, w, s, scr) => popoutService?.toggleSystemUpdate(x, y, w, s, scr),
|
||||
"settings": () => popoutService?.openSettings(),
|
||||
"clipboardHistory": () => popoutService?.openClipboardHistory(),
|
||||
"spotlight": () => popoutService?.openSpotlight(),
|
||||
"spotlight": () => popoutService?.toggleDankLauncherV2(),
|
||||
"powerMenu": () => popoutService?.togglePowerMenu(),
|
||||
"colorPicker": () => popoutService?.showColorPicker(),
|
||||
"notepad": () => popoutService?.toggleNotepad()
|
||||
@@ -48,7 +46,7 @@ PluginComponent {
|
||||
|
||||
pillClickAction: (x, y, width, section, screen) => {
|
||||
if (popoutActions[selectedPopout]) {
|
||||
popoutActions[selectedPopout](x, y, width, section, screen)
|
||||
popoutActions[selectedPopout](x, y, width, section, screen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,10 +84,11 @@ popoutService.openClipboardHistory()
|
||||
popoutService.closeClipboardHistory()
|
||||
```
|
||||
|
||||
#### Spotlight Modal
|
||||
#### Launcher Modal
|
||||
```qml
|
||||
popoutService.openSpotlight()
|
||||
popoutService.closeSpotlight()
|
||||
popoutService.openDankLauncherV2()
|
||||
popoutService.closeDankLauncherV2()
|
||||
popoutService.toggleDankLauncherV2()
|
||||
```
|
||||
|
||||
#### Power Menu Modal
|
||||
|
||||
@@ -85,7 +85,9 @@ Singleton {
|
||||
|
||||
function setCachedDefaultSections(sections, flatModel) {
|
||||
_cachedDefaultSections = sections.map(function (s) {
|
||||
return Object.assign({}, s);
|
||||
return Object.assign({}, s, {
|
||||
items: s.items ? s.items.slice() : []
|
||||
});
|
||||
});
|
||||
_cachedDefaultFlatModel = flatModel.slice();
|
||||
_defaultCacheValid = true;
|
||||
|
||||
@@ -20,9 +20,8 @@ Singleton {
|
||||
property var settingsModal: null
|
||||
property var settingsModalLoader: null
|
||||
property var clipboardHistoryModal: null
|
||||
property var spotlightModal: null
|
||||
property var spotlightV2Modal: null
|
||||
property var spotlightV2ModalLoader: null
|
||||
property var dankLauncherV2Modal: null
|
||||
property var dankLauncherV2ModalLoader: null
|
||||
property var powerMenuModal: null
|
||||
property var processListModal: null
|
||||
property var processListModalLoader: null
|
||||
@@ -355,97 +354,89 @@ Singleton {
|
||||
clipboardHistoryModal?.close();
|
||||
}
|
||||
|
||||
function openSpotlight() {
|
||||
spotlightModal?.show();
|
||||
}
|
||||
property bool _dankLauncherV2WantsOpen: false
|
||||
property bool _dankLauncherV2WantsToggle: false
|
||||
property string _dankLauncherV2PendingQuery: ""
|
||||
property string _dankLauncherV2PendingMode: ""
|
||||
|
||||
function closeSpotlight() {
|
||||
spotlightModal?.close();
|
||||
}
|
||||
|
||||
property bool _spotlightV2WantsOpen: false
|
||||
property bool _spotlightV2WantsToggle: false
|
||||
property string _spotlightV2PendingQuery: ""
|
||||
property string _spotlightV2PendingMode: ""
|
||||
|
||||
function openSpotlightV2() {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.show();
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2WantsOpen = true;
|
||||
_spotlightV2WantsToggle = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
function openDankLauncherV2() {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.show();
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2WantsOpen = true;
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function openSpotlightV2WithQuery(query: string) {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.showWithQuery(query);
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2PendingQuery = query;
|
||||
_spotlightV2WantsOpen = true;
|
||||
_spotlightV2WantsToggle = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
function openDankLauncherV2WithQuery(query: string) {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.showWithQuery(query);
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2PendingQuery = query;
|
||||
_dankLauncherV2WantsOpen = true;
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function openSpotlightV2WithMode(mode: string) {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.showWithMode(mode);
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2PendingMode = mode;
|
||||
_spotlightV2WantsOpen = true;
|
||||
_spotlightV2WantsToggle = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
function openDankLauncherV2WithMode(mode: string) {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.showWithMode(mode);
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2PendingMode = mode;
|
||||
_dankLauncherV2WantsOpen = true;
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function closeSpotlightV2() {
|
||||
spotlightV2Modal?.hide();
|
||||
function closeDankLauncherV2() {
|
||||
dankLauncherV2Modal?.hide();
|
||||
}
|
||||
|
||||
function toggleSpotlightV2() {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.toggle();
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2WantsToggle = true;
|
||||
_spotlightV2WantsOpen = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
function toggleDankLauncherV2() {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.toggle();
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2WantsToggle = true;
|
||||
_dankLauncherV2WantsOpen = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSpotlightV2WithMode(mode: string) {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.toggleWithMode(mode);
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2PendingMode = mode;
|
||||
_spotlightV2WantsToggle = true;
|
||||
_spotlightV2WantsOpen = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
function toggleDankLauncherV2WithMode(mode: string) {
|
||||
if (dankLauncherV2Modal) {
|
||||
dankLauncherV2Modal.toggleWithMode(mode);
|
||||
} else if (dankLauncherV2ModalLoader) {
|
||||
_dankLauncherV2PendingMode = mode;
|
||||
_dankLauncherV2WantsToggle = true;
|
||||
_dankLauncherV2WantsOpen = false;
|
||||
dankLauncherV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function _onSpotlightV2ModalLoaded() {
|
||||
if (_spotlightV2WantsOpen) {
|
||||
_spotlightV2WantsOpen = false;
|
||||
if (_spotlightV2PendingQuery) {
|
||||
spotlightV2Modal?.showWithQuery(_spotlightV2PendingQuery);
|
||||
_spotlightV2PendingQuery = "";
|
||||
} else if (_spotlightV2PendingMode) {
|
||||
spotlightV2Modal?.showWithMode(_spotlightV2PendingMode);
|
||||
_spotlightV2PendingMode = "";
|
||||
function _onDankLauncherV2ModalLoaded() {
|
||||
if (_dankLauncherV2WantsOpen) {
|
||||
_dankLauncherV2WantsOpen = false;
|
||||
if (_dankLauncherV2PendingQuery) {
|
||||
dankLauncherV2Modal?.showWithQuery(_dankLauncherV2PendingQuery);
|
||||
_dankLauncherV2PendingQuery = "";
|
||||
} else if (_dankLauncherV2PendingMode) {
|
||||
dankLauncherV2Modal?.showWithMode(_dankLauncherV2PendingMode);
|
||||
_dankLauncherV2PendingMode = "";
|
||||
} else {
|
||||
spotlightV2Modal?.show();
|
||||
dankLauncherV2Modal?.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_spotlightV2WantsToggle) {
|
||||
_spotlightV2WantsToggle = false;
|
||||
if (_spotlightV2PendingMode) {
|
||||
spotlightV2Modal?.toggleWithMode(_spotlightV2PendingMode);
|
||||
_spotlightV2PendingMode = "";
|
||||
if (_dankLauncherV2WantsToggle) {
|
||||
_dankLauncherV2WantsToggle = false;
|
||||
if (_dankLauncherV2PendingMode) {
|
||||
dankLauncherV2Modal?.toggleWithMode(_dankLauncherV2PendingMode);
|
||||
_dankLauncherV2PendingMode = "";
|
||||
} else {
|
||||
spotlightV2Modal?.toggle();
|
||||
dankLauncherV2Modal?.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user