mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-08 04:09:15 -04:00
feat: (Launcher/Spotlight): Updated w/New Settings & QOL features
- New Spotlight toggle to show/hide chips, off by default - Updated blur effects on all launcher inputs and footers - Fixed previous queries resurfacing - Upated Spotlight keyboard navigation - Added functionality to show and shortcut to keybinds from the Launcher tab
This commit is contained in:
@@ -8,9 +8,9 @@ const ACTION_TYPES = [
|
||||
];
|
||||
|
||||
const DMS_ACTIONS = [
|
||||
{ id: "spawn dms ipc call spotlight toggle", label: "App Launcher: Toggle" },
|
||||
{ id: "spawn dms ipc call spotlight open", label: "App Launcher: Open" },
|
||||
{ id: "spawn dms ipc call spotlight close", label: "App Launcher: Close" },
|
||||
{ id: "spawn dms ipc call spotlight toggle", label: "Default Launcher: Toggle" },
|
||||
{ id: "spawn dms ipc call spotlight open", label: "Default Launcher: Open" },
|
||||
{ id: "spawn dms ipc call spotlight close", label: "Default Launcher: Close" },
|
||||
{ id: "spawn dms ipc call spotlight-bar toggle", label: "Spotlight Bar: Toggle" },
|
||||
{ id: "spawn dms ipc call spotlight-bar open", label: "Spotlight Bar: Open" },
|
||||
{ id: "spawn dms ipc call spotlight-bar close", label: "Spotlight Bar: Close" },
|
||||
|
||||
@@ -450,6 +450,7 @@ Singleton {
|
||||
property bool dankLauncherV2IncludeFoldersInAll: false
|
||||
property bool launcherUseOverlayLayer: false
|
||||
property string launcherStyle: "full"
|
||||
property bool spotlightBarShowModeChips: false
|
||||
|
||||
property string _legacyWeatherLocation: "New York, NY"
|
||||
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
|
||||
|
||||
@@ -216,6 +216,7 @@ var SPEC = {
|
||||
dankLauncherV2IncludeFoldersInAll: { def: false },
|
||||
launcherUseOverlayLayer: { def: false },
|
||||
launcherStyle: { def: "full" },
|
||||
spotlightBarShowModeChips: { def: false },
|
||||
|
||||
useAutoLocation: { def: false },
|
||||
weatherEnabled: { def: true },
|
||||
|
||||
@@ -10,10 +10,11 @@ Rectangle {
|
||||
|
||||
property var entry: null
|
||||
property string cachedImageData: ""
|
||||
property string cachedMimeType: ""
|
||||
property var _requestedEntryId: null
|
||||
|
||||
readonly property bool canLoadImage: !!entry?.isImage && (entry?.mimeType ?? "").startsWith("image/")
|
||||
readonly property string sourceUrl: cachedImageData.length > 0 ? "data:" + (entry?.mimeType ?? "image/png") + ";base64," + cachedImageData : ""
|
||||
readonly property string sourceUrl: resolvedSourceUrl(cachedImageData, cachedMimeType || (entry?.mimeType ?? ""))
|
||||
|
||||
radius: Math.max(6, Theme.cornerRadius - 2)
|
||||
clip: true
|
||||
@@ -24,8 +25,24 @@ Rectangle {
|
||||
onEntryChanged: reloadPreview()
|
||||
Component.onCompleted: reloadPreview()
|
||||
|
||||
function isImageMimeType(mimeType) {
|
||||
return (mimeType || "").toString().toLowerCase().startsWith("image/");
|
||||
}
|
||||
|
||||
function resolvedSourceUrl(data, mimeType) {
|
||||
const rawData = (data || "").toString();
|
||||
if (rawData.length === 0)
|
||||
return "";
|
||||
if (rawData.startsWith("data:"))
|
||||
return rawData.startsWith("data:image/") ? rawData : "";
|
||||
if (!isImageMimeType(mimeType))
|
||||
return "";
|
||||
return "data:" + mimeType + ";base64," + rawData;
|
||||
}
|
||||
|
||||
function reloadPreview() {
|
||||
cachedImageData = "";
|
||||
cachedMimeType = "";
|
||||
if (!canLoadImage || !entry?.id) {
|
||||
_requestedEntryId = null;
|
||||
return;
|
||||
@@ -40,8 +57,12 @@ Rectangle {
|
||||
return;
|
||||
if (response.error)
|
||||
return;
|
||||
const data = response.result?.data ?? "";
|
||||
if (data.length > 0)
|
||||
const result = response.result ?? {};
|
||||
const mimeType = (result.mimeType ?? entry?.mimeType ?? "").toString();
|
||||
const data = (result.data ?? "").toString();
|
||||
if (data.length === 0 || !resolvedSourceUrl(data, mimeType))
|
||||
return;
|
||||
cachedMimeType = mimeType;
|
||||
cachedImageData = data;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ Item {
|
||||
property int gridColumns: SettingsData.appLauncherGridColumns
|
||||
property int viewModeVersion: 0
|
||||
property string viewModeContext: "spotlight"
|
||||
property bool forceLinearNavigation: false
|
||||
|
||||
signal itemExecuted
|
||||
signal searchCompleted
|
||||
@@ -43,6 +44,10 @@ Item {
|
||||
signal viewModeChanged(string sectionId, string mode)
|
||||
signal searchQueryRequested(string query)
|
||||
|
||||
Ref {
|
||||
service: AppSearchService
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) {
|
||||
SessionData.addLauncherHistory(searchQuery);
|
||||
@@ -87,15 +92,23 @@ Item {
|
||||
Connections {
|
||||
target: ClipboardService
|
||||
function onLauncherSearchReady(query) {
|
||||
if (!active || !clipboardSearchEnabledInAll())
|
||||
if (!active)
|
||||
return;
|
||||
if (searchMode !== "all")
|
||||
|
||||
const clipboardBuiltInActive = activePluginId === "dms_clipboard_search";
|
||||
if (!clipboardBuiltInActive && !clipboardSearchEnabledInAll())
|
||||
return;
|
||||
if (!clipboardBuiltInActive && searchMode !== "all")
|
||||
return;
|
||||
|
||||
const trimmed = (searchQuery || "").trim();
|
||||
if (trimmed.length < 2 && query.length > 0)
|
||||
return;
|
||||
if (query !== trimmed)
|
||||
const triggerMatch = detectTrigger(trimmed);
|
||||
const effectiveQuery = clipboardBuiltInActive && triggerMatch.pluginId === "dms_clipboard_search" ? triggerMatch.query : trimmed;
|
||||
if (query !== effectiveQuery)
|
||||
return;
|
||||
|
||||
searchDebounce.restart();
|
||||
}
|
||||
}
|
||||
@@ -1711,7 +1724,9 @@ Item {
|
||||
function selectNext() {
|
||||
keyboardNavigationActive = true;
|
||||
_cancelPendingSelectionReset();
|
||||
var newIndex = Nav.calculateNextIndex(flatModel, selectedFlatIndex, null, null, gridColumns, getSectionViewMode);
|
||||
var newIndex = forceLinearNavigation ? Nav.findNextNonHeaderIndex(flatModel, selectedFlatIndex + 1) : Nav.calculateNextIndex(flatModel, selectedFlatIndex, null, null, gridColumns, getSectionViewMode);
|
||||
if (newIndex === -1)
|
||||
newIndex = selectedFlatIndex;
|
||||
if (newIndex !== selectedFlatIndex) {
|
||||
selectedFlatIndex = newIndex;
|
||||
updateSelectedItem();
|
||||
@@ -1721,7 +1736,9 @@ Item {
|
||||
function selectPrevious() {
|
||||
keyboardNavigationActive = true;
|
||||
_cancelPendingSelectionReset();
|
||||
var newIndex = Nav.calculatePrevIndex(flatModel, selectedFlatIndex, null, null, gridColumns, getSectionViewMode);
|
||||
var newIndex = forceLinearNavigation ? Nav.findPrevNonHeaderIndex(flatModel, selectedFlatIndex - 1) : Nav.calculatePrevIndex(flatModel, selectedFlatIndex, null, null, gridColumns, getSectionViewMode);
|
||||
if (newIndex === -1)
|
||||
newIndex = selectedFlatIndex;
|
||||
if (newIndex !== selectedFlatIndex) {
|
||||
selectedFlatIndex = newIndex;
|
||||
updateSelectedItem();
|
||||
|
||||
@@ -388,6 +388,7 @@ Item {
|
||||
if (!spotlightContent)
|
||||
return;
|
||||
contentVisible = true;
|
||||
spotlightContent.closeTransientUi?.();
|
||||
// NOTE: forceActiveFocus() is deliberately NOT called here.
|
||||
// It is deferred to after animation starts to avoid compositor IPC stalls.
|
||||
|
||||
@@ -480,6 +481,7 @@ Item {
|
||||
function hide() {
|
||||
if (!spotlightOpen)
|
||||
return;
|
||||
spotlightContent?.closeTransientUi?.();
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
|
||||
|
||||
@@ -47,9 +47,9 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int _openDuration: 80
|
||||
readonly property int _closeDuration: 70
|
||||
readonly property int _motionDuration: 90
|
||||
readonly property int _openDuration: 50
|
||||
readonly property int _closeDuration: 40
|
||||
readonly property int _motionDuration: 60
|
||||
|
||||
// Connected frame mode clamps the centered surface inside frame insets.
|
||||
readonly property bool frameConnected: CompositorService.usesConnectedFrameChromeForScreen(effectiveScreen)
|
||||
@@ -142,6 +142,7 @@ Item {
|
||||
if (!spotlightContent)
|
||||
return;
|
||||
contentVisible = true;
|
||||
spotlightContent.closeTransientUi?.();
|
||||
|
||||
const targetQuery = query || (SettingsData.rememberLastQuery ? (SessionData.launcherLastQuery || "") : "");
|
||||
const targetMode = mode || SessionData.launcherLastMode || "all";
|
||||
@@ -202,6 +203,7 @@ Item {
|
||||
function hide() {
|
||||
if (!spotlightOpen)
|
||||
return;
|
||||
spotlightContent?.closeTransientUi?.();
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
contentVisible = false;
|
||||
|
||||
@@ -133,6 +133,7 @@ Item {
|
||||
if (!spotlightContent)
|
||||
return;
|
||||
contentVisible = true;
|
||||
spotlightContent.closeTransientUi?.();
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
|
||||
var targetQuery = "";
|
||||
@@ -211,6 +212,7 @@ Item {
|
||||
function hide() {
|
||||
if (!spotlightOpen)
|
||||
return;
|
||||
spotlightContent?.closeTransientUi?.();
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
contentVisible = false;
|
||||
@@ -368,7 +370,7 @@ Item {
|
||||
WindowBlur {
|
||||
targetWindow: launcherWindow
|
||||
readonly property real s: Math.min(1, modalContainer.publishedScale)
|
||||
readonly property real op: Math.max(0, Math.min(1, (modalContainer.opacity - 0.06) * 2))
|
||||
readonly property real op: Math.max(0, Math.min(1, (modalContainer.publishedOpacity - 0.06) * 2))
|
||||
blurX: modalContainer.x + modalContainer.width * (1 - s * op) * 0.5
|
||||
blurY: modalContainer.y + modalContainer.height * (1 - s * op) * 0.5
|
||||
blurWidth: contentVisible ? modalContainer.width * s * op : 0
|
||||
@@ -447,6 +449,7 @@ Item {
|
||||
|
||||
property bool _renderActive: contentVisible
|
||||
property real publishedScale: contentVisible ? 1 : 0.96
|
||||
property real publishedOpacity: contentVisible ? 1 : 0
|
||||
|
||||
opacity: contentVisible ? 1 : 0
|
||||
scale: contentVisible ? 1 : 0.96
|
||||
@@ -478,6 +481,14 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on publishedOpacity {
|
||||
NumberAnimation {
|
||||
easing.type: Easing.BezierSpline
|
||||
duration: Theme.modalAnimationDuration
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onContentVisibleChanged() {
|
||||
|
||||
@@ -21,6 +21,17 @@ FocusScope {
|
||||
property bool editMode: false
|
||||
property var editingApp: null
|
||||
property string editAppId: ""
|
||||
readonly property bool _blurActive: Theme.blurForegroundLayers || Theme.transparentBlurLayers
|
||||
readonly property real _launcherFieldAlpha: {
|
||||
if (Theme.transparentBlurLayers)
|
||||
return 0.28;
|
||||
if (Theme.blurForegroundLayers)
|
||||
return Math.max(Theme.popupTransparency, 0.62);
|
||||
return Theme.popupTransparency;
|
||||
}
|
||||
readonly property color _launcherSearchFieldColor: Theme.withAlpha(Theme.surfaceContainerHigh, _launcherFieldAlpha)
|
||||
readonly property color _launcherSearchBorderColor: Theme.withAlpha(Theme.outline, _blurActive ? 0.16 : Theme.layerOutlineOpacity)
|
||||
readonly property color _launcherSearchFocusedBorderColor: Theme.withAlpha(Theme.primary, _blurActive ? 0.72 : 1.0)
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.resetScroll();
|
||||
@@ -30,6 +41,12 @@ FocusScope {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
function closeTransientUi() {
|
||||
contextMenu.hide();
|
||||
actionPanel.hide();
|
||||
root.enabled = true;
|
||||
}
|
||||
|
||||
function openEditMode(app) {
|
||||
if (!app)
|
||||
return;
|
||||
@@ -111,6 +128,21 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.parentModal
|
||||
ignoreUnknownSignals: true
|
||||
|
||||
function onSpotlightOpenChanged() {
|
||||
if (!root.parentModal?.spotlightOpen)
|
||||
root.closeTransientUi();
|
||||
}
|
||||
|
||||
function onContentVisibleChanged() {
|
||||
if (!root.parentModal?.contentVisible)
|
||||
root.closeTransientUi();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (editMode) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
@@ -284,7 +316,7 @@ FocusScope {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.leftMargin: root.parentModal?.borderWidth ?? 1
|
||||
anchors.rightMargin: root.parentModal?.borderWidth ?? 1
|
||||
anchors.bottomMargin: _connectedBottomEmerge ? Theme.spacingS : (root.parentModal?.borderWidth ?? 1)
|
||||
anchors.bottomMargin: _connectedBottomEmerge ? 0 : (root.parentModal?.borderWidth ?? 1)
|
||||
height: showFooter ? (_connectedArcAtFooter ? 76 : 36) : 0
|
||||
visible: showFooter
|
||||
clip: true
|
||||
@@ -293,7 +325,7 @@ FocusScope {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: -Theme.cornerRadius
|
||||
// In connected mode the launcher provides the surface so update the toolbar for arcs
|
||||
visible: !(root.parentModal?.frameOwnsConnectedChrome ?? false)
|
||||
visible: !(root.parentModal?.frameOwnsConnectedChrome ?? false) && !root._blurActive
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
@@ -458,9 +490,11 @@ FocusScope {
|
||||
id: searchField
|
||||
width: parent.width - (pluginBadge.visible ? pluginBadge.width + Theme.spacingS : 0)
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
backgroundColor: root._launcherSearchFieldColor
|
||||
normalBorderColor: root._launcherSearchBorderColor
|
||||
focusedBorderColor: root._launcherSearchFocusedBorderColor
|
||||
borderWidth: 1
|
||||
focusedBorderWidth: 2
|
||||
leftIconName: controller.activePluginId ? "extension" : controller.searchQuery.startsWith("/") ? "folder" : "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
|
||||
@@ -1,35 +1,72 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
visible: false
|
||||
width: 0
|
||||
height: 0
|
||||
|
||||
property var item: null
|
||||
property var controller: null
|
||||
property var searchField: null
|
||||
property var parentHandler: null
|
||||
property bool allowEditActions: true
|
||||
property real menuMargin: 8
|
||||
property var targetScreen: null
|
||||
property real anchorX: 0
|
||||
property real anchorY: 0
|
||||
property bool openState: false
|
||||
property bool renderActive: false
|
||||
readonly property bool blurActive: renderActive && openState && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
|
||||
|
||||
readonly property real minMenuWidth: 180
|
||||
readonly property real maxMenuWidth: Math.max(0, (targetScreen?.width ?? 500) - menuMargin * 2)
|
||||
readonly property real maxMenuHeight: Math.max(0, (targetScreen?.height ?? 600) - menuMargin * 2)
|
||||
readonly property string longestMenuText: {
|
||||
let longest = "";
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
const text = menuItems[i].text || "";
|
||||
if (text.length > longest.length)
|
||||
longest = text;
|
||||
}
|
||||
return longest;
|
||||
}
|
||||
readonly property real naturalMenuWidth: Math.max(minMenuWidth, menuTextMetrics.width + Theme.iconSize + Theme.spacingS * 5)
|
||||
readonly property real effectiveMenuWidth: Math.max(0, Math.min(maxMenuWidth, naturalMenuWidth))
|
||||
readonly property real naturalMenuHeight: menuItemsHeight() + Theme.spacingS * 2
|
||||
readonly property real effectiveMenuHeight: Math.min(maxMenuHeight, naturalMenuHeight)
|
||||
readonly property bool menuScrolls: naturalMenuHeight > effectiveMenuHeight + 0.5
|
||||
|
||||
signal hideRequested
|
||||
signal editAppRequested(var app)
|
||||
|
||||
TextMetrics {
|
||||
id: menuTextMetrics
|
||||
text: root.longestMenuText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Normal
|
||||
}
|
||||
|
||||
function hasContextMenuActions(spotlightItem) {
|
||||
if (!spotlightItem)
|
||||
return false;
|
||||
if (spotlightItem.type === "app")
|
||||
return true;
|
||||
if (spotlightItem.type === "plugin" && spotlightItem.pluginId) {
|
||||
var instance = PluginService.pluginInstances[spotlightItem.pluginId];
|
||||
const instance = PluginService.pluginInstances[spotlightItem.pluginId];
|
||||
if (!instance)
|
||||
return false;
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return false;
|
||||
var actions = instance.getContextMenuActions(spotlightItem.data);
|
||||
const actions = instance.getContextMenuActions(spotlightItem.data);
|
||||
return Array.isArray(actions) && actions.length > 0;
|
||||
}
|
||||
if (spotlightItem.actions && spotlightItem.actions.length > 0)
|
||||
@@ -54,13 +91,13 @@ Popup {
|
||||
if (!isPluginItem || !item?.pluginId)
|
||||
return [];
|
||||
|
||||
var instance = PluginService.pluginInstances[item.pluginId];
|
||||
const instance = PluginService.pluginInstances[item.pluginId];
|
||||
if (!instance)
|
||||
return [];
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return [];
|
||||
|
||||
var actions = instance.getContextMenuActions(item.data);
|
||||
const actions = instance.getContextMenuActions(item.data);
|
||||
if (!Array.isArray(actions))
|
||||
return [];
|
||||
|
||||
@@ -68,8 +105,8 @@ Popup {
|
||||
}
|
||||
|
||||
function executePluginAction(actionOrObj) {
|
||||
var actionFunc = typeof actionOrObj === "function" ? actionOrObj : actionOrObj?.action;
|
||||
var closeLauncher = typeof actionOrObj === "object" && actionOrObj?.closeLauncher;
|
||||
const actionFunc = typeof actionOrObj === "function" ? actionOrObj : actionOrObj?.action;
|
||||
const closeLauncher = typeof actionOrObj === "object" && actionOrObj?.closeLauncher;
|
||||
|
||||
if (typeof actionFunc === "function")
|
||||
actionFunc();
|
||||
@@ -90,12 +127,12 @@ Popup {
|
||||
}
|
||||
|
||||
readonly property var menuItems: {
|
||||
var items = [];
|
||||
const items = [];
|
||||
|
||||
if (isPluginItem) {
|
||||
var pluginActions = getPluginContextMenuActions();
|
||||
for (var i = 0; i < pluginActions.length; i++) {
|
||||
var act = pluginActions[i];
|
||||
const pluginActions = getPluginContextMenuActions();
|
||||
for (let i = 0; i < pluginActions.length; i++) {
|
||||
const act = pluginActions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "play_arrow",
|
||||
@@ -107,8 +144,8 @@ Popup {
|
||||
}
|
||||
|
||||
if (item?.type !== "app" && item?.actions && item.actions.length > 0) {
|
||||
for (var i = 0; i < item.actions.length; i++) {
|
||||
var genericAct = item.actions[i];
|
||||
for (let i = 0; i < item.actions.length; i++) {
|
||||
const genericAct = item.actions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: genericAct.icon || "play_arrow",
|
||||
@@ -149,8 +186,8 @@ Popup {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
for (var i = 0; i < item.actions.length; i++) {
|
||||
var act = item.actions[i];
|
||||
for (let i = 0; i < item.actions.length; i++) {
|
||||
const act = item.actions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "play_arrow",
|
||||
@@ -183,43 +220,52 @@ Popup {
|
||||
return items;
|
||||
}
|
||||
|
||||
function menuItemsHeight() {
|
||||
let h = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
h += menuItems[i].type === "separator" ? 5 : 32;
|
||||
}
|
||||
if (menuItems.length > 1)
|
||||
h += menuItems.length - 1;
|
||||
return h;
|
||||
}
|
||||
|
||||
function show(x, y, spotlightItem, fromKeyboard) {
|
||||
if (!spotlightItem?.data)
|
||||
return;
|
||||
|
||||
item = spotlightItem;
|
||||
selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||
keyboardNavigation = fromKeyboard;
|
||||
|
||||
const modal = parentHandler?.parentModal ?? null;
|
||||
const screenRef = modal?.effectiveScreen ?? parentHandler?.Window?.window?.screen ?? searchField?.Window?.window?.screen ?? null;
|
||||
const screenX = screenRef?.x || 0;
|
||||
const screenY = screenRef?.y || 0;
|
||||
const screenRelativeX = modal ? ((modal.alignedX ?? 0) + x) : ((parentHandler ? parentHandler.mapToGlobal(x, y).x : x) - screenX);
|
||||
const screenRelativeY = modal ? ((modal.alignedY ?? 0) + y) : ((parentHandler ? parentHandler.mapToGlobal(x, y).y : y) - screenY);
|
||||
|
||||
targetScreen = screenRef;
|
||||
anchorX = screenRelativeX + 4;
|
||||
anchorY = screenRelativeY + 4;
|
||||
renderActive = true;
|
||||
openState = true;
|
||||
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = false;
|
||||
|
||||
Qt.callLater(() => {
|
||||
var parentW = parent?.width ?? 500;
|
||||
var parentH = parent?.height ?? 600;
|
||||
var menuW = width > 0 ? width : 200;
|
||||
var menuH = height > 0 ? height : 200;
|
||||
var margin = 8;
|
||||
|
||||
var posX = x + 4;
|
||||
var posY = y + 4;
|
||||
|
||||
if (posX + menuW > parentW - margin) {
|
||||
posX = Math.max(margin, parentW - menuW - margin);
|
||||
}
|
||||
if (posY + menuH > parentH - margin) {
|
||||
posY = Math.max(margin, parentH - menuH - margin);
|
||||
}
|
||||
|
||||
root.x = posX;
|
||||
root.y = posY;
|
||||
open();
|
||||
menuFlickable.contentY = 0;
|
||||
keyboardHandler.forceActiveFocus();
|
||||
ensureSelectedVisible();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = true;
|
||||
close();
|
||||
if (!renderActive)
|
||||
return;
|
||||
openState = false;
|
||||
hideRequested();
|
||||
}
|
||||
|
||||
function togglePin() {
|
||||
@@ -286,8 +332,8 @@ Popup {
|
||||
property bool keyboardNavigation: false
|
||||
|
||||
readonly property int visibleItemCount: {
|
||||
var count = 0;
|
||||
for (var i = 0; i < menuItems.length; i++) {
|
||||
let count = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type === "item")
|
||||
count++;
|
||||
}
|
||||
@@ -295,22 +341,62 @@ Popup {
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (visibleItemCount > 0)
|
||||
if (visibleItemCount > 0) {
|
||||
keyboardNavigation = true;
|
||||
selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount;
|
||||
ensureSelectedVisible();
|
||||
}
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (visibleItemCount > 0)
|
||||
if (visibleItemCount > 0) {
|
||||
keyboardNavigation = true;
|
||||
selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount;
|
||||
ensureSelectedVisible();
|
||||
}
|
||||
}
|
||||
|
||||
function selectedDelegateIndex() {
|
||||
let itemIndex = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type !== "item")
|
||||
continue;
|
||||
if (itemIndex === selectedMenuIndex)
|
||||
return i;
|
||||
itemIndex++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function ensureSelectedVisible() {
|
||||
Qt.callLater(() => {
|
||||
if (!menuFlickable || !menuRepeater)
|
||||
return;
|
||||
const delegateIndex = selectedDelegateIndex();
|
||||
if (delegateIndex < 0)
|
||||
return;
|
||||
const delegate = menuRepeater.itemAt(delegateIndex);
|
||||
if (!delegate)
|
||||
return;
|
||||
const top = delegate.y;
|
||||
const bottom = top + delegate.height;
|
||||
const viewTop = menuFlickable.contentY;
|
||||
const viewBottom = viewTop + menuFlickable.height;
|
||||
if (top < viewTop) {
|
||||
menuFlickable.contentY = Math.max(0, top);
|
||||
} else if (bottom > viewBottom) {
|
||||
menuFlickable.contentY = Math.min(Math.max(0, menuFlickable.contentHeight - menuFlickable.height), bottom - menuFlickable.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
var itemIndex = 0;
|
||||
for (var i = 0; i < menuItems.length; i++) {
|
||||
let itemIndex = 0;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type !== "item")
|
||||
continue;
|
||||
if (itemIndex === selectedMenuIndex) {
|
||||
var menuItem = menuItems[i];
|
||||
const menuItem = menuItems[i];
|
||||
if (menuItem.action)
|
||||
menuItem.action();
|
||||
else if (menuItem.pluginAction)
|
||||
@@ -325,51 +411,45 @@ Popup {
|
||||
}
|
||||
}
|
||||
|
||||
width: menuContainer.implicitWidth
|
||||
height: menuContainer.implicitHeight
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
modal: true
|
||||
dim: false
|
||||
background: Item {}
|
||||
PanelWindow {
|
||||
id: menuWindow
|
||||
|
||||
onOpened: {
|
||||
Qt.callLater(() => keyboardHandler.forceActiveFocus());
|
||||
screen: root.targetScreen
|
||||
visible: root.renderActive
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:launcher-context-menu"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: root.renderActive ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = true;
|
||||
if (searchField?.visible) {
|
||||
Qt.callLater(() => searchField.forceActiveFocus());
|
||||
}
|
||||
WindowBlur {
|
||||
targetWindow: menuWindow
|
||||
blurX: root.blurActive ? menuContainer.x : 0
|
||||
blurY: root.blurActive ? menuContainer.y : 0
|
||||
blurWidth: root.blurActive ? menuContainer.width : 0
|
||||
blurHeight: root.blurActive ? menuContainer.height : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
enabled: root.renderActive
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
Item {
|
||||
id: keyboardHandler
|
||||
focus: true
|
||||
implicitWidth: menuContainer.implicitWidth
|
||||
implicitHeight: menuContainer.implicitHeight
|
||||
anchors.fill: parent
|
||||
focus: root.openState
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
@@ -396,13 +476,31 @@ Popup {
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
anchors.fill: parent
|
||||
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
color: Theme.floatingSurface
|
||||
x: Math.max(root.menuMargin, Math.min(menuWindow.width - width - root.menuMargin, root.anchorX))
|
||||
y: Math.max(root.menuMargin, Math.min(menuWindow.height - height - root.menuMargin, root.anchorY))
|
||||
width: root.effectiveMenuWidth
|
||||
height: root.effectiveMenuHeight
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||
opacity: root.openState ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
onRunningChanged: {
|
||||
if (!running && !root.openState) {
|
||||
root.renderActive = false;
|
||||
if (root.parentHandler)
|
||||
root.parentHandler.enabled = true;
|
||||
if (root.searchField?.visible)
|
||||
Qt.callLater(() => root.searchField.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -415,13 +513,23 @@ Popup {
|
||||
z: -1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
Flickable {
|
||||
id: menuFlickable
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: menuColumn.implicitHeight
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
interactive: root.menuScrolls
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
width: menuFlickable.width
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
id: menuRepeater
|
||||
model: root.menuItems
|
||||
|
||||
Item {
|
||||
@@ -433,8 +541,8 @@ Popup {
|
||||
height: modelData.type === "separator" ? 5 : 32
|
||||
|
||||
readonly property int itemIndex: {
|
||||
var count = 0;
|
||||
for (var i = 0; i < index; i++) {
|
||||
let count = 0;
|
||||
for (let i = 0; i < index; i++) {
|
||||
if (root.menuItems[i].type === "item")
|
||||
count++;
|
||||
}
|
||||
@@ -519,7 +627,7 @@ Popup {
|
||||
}
|
||||
onPressed: mouse => menuItemRipple.trigger(mouse.x, mouse.y)
|
||||
onClicked: {
|
||||
var menuItem = menuItemDelegate.modelData;
|
||||
const menuItem = menuItemDelegate.modelData;
|
||||
if (menuItem.action)
|
||||
menuItem.action();
|
||||
else if (menuItem.pluginAction)
|
||||
@@ -537,3 +645,5 @@ Popup {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,34 @@ FocusScope {
|
||||
readonly property real _resultsH: _hasQuery ? Math.min(_resultsContentH, _maxResultsH) : 0
|
||||
readonly property int _fastDuration: 90
|
||||
readonly property int _resizeDuration: 110
|
||||
readonly property bool _blurActive: Theme.blurForegroundLayers || Theme.transparentBlurLayers
|
||||
readonly property real _searchSurfaceAlpha: {
|
||||
if (Theme.transparentBlurLayers)
|
||||
return _hasQuery ? 0.34 : 0.28;
|
||||
if (Theme.blurForegroundLayers)
|
||||
return Math.max(Theme.popupTransparency, _hasQuery ? 0.68 : 0.74);
|
||||
return _hasQuery ? Theme.popupTransparency : Math.max(0.68, Theme.popupTransparency * 0.9);
|
||||
}
|
||||
readonly property color _searchSurfaceColor: Theme.withAlpha(_hasQuery ? Theme.surfaceContainerHigh : Theme.surfaceContainer, _searchSurfaceAlpha)
|
||||
readonly property color _searchWellColor: {
|
||||
if (searchInput.activeFocus)
|
||||
return Theme.withAlpha(Theme.primaryContainer, Theme.transparentBlurLayers ? 0.42 : 1.0);
|
||||
if (Theme.transparentBlurLayers)
|
||||
return Theme.ccPillInactiveBg;
|
||||
return Theme.surfaceContainer;
|
||||
}
|
||||
|
||||
implicitHeight: _searchAreaH + (_resultsH > 0 ? 1 + _resultsH : 0)
|
||||
implicitHeight: _searchAreaH + _resultsH
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.resetScroll();
|
||||
}
|
||||
|
||||
function closeTransientUi() {
|
||||
contextMenu.hide();
|
||||
root.enabled = true;
|
||||
}
|
||||
|
||||
function _buildRows() {
|
||||
const flat = searchController.flatModel || [];
|
||||
const sections = searchController.sections || [];
|
||||
@@ -121,12 +142,10 @@ FocusScope {
|
||||
}
|
||||
break;
|
||||
case Qt.Key_Tab:
|
||||
if (_hasQuery)
|
||||
_cycleCategory(false);
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Backtab:
|
||||
if (_hasQuery)
|
||||
_cycleCategory(true);
|
||||
event.accepted = true;
|
||||
return;
|
||||
@@ -192,6 +211,7 @@ FocusScope {
|
||||
id: searchController
|
||||
active: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
|
||||
viewModeContext: "spotlight"
|
||||
forceLinearNavigation: true
|
||||
|
||||
onItemExecuted: {
|
||||
root.parentModal?.hide();
|
||||
@@ -209,6 +229,21 @@ FocusScope {
|
||||
allowEditActions: false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.parentModal
|
||||
ignoreUnknownSignals: true
|
||||
|
||||
function onSpotlightOpenChanged() {
|
||||
if (!root.parentModal?.spotlightOpen)
|
||||
root.closeTransientUi();
|
||||
}
|
||||
|
||||
function onContentVisibleChanged() {
|
||||
if (!root.parentModal?.contentVisible)
|
||||
root.closeTransientUi();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: searchController
|
||||
function onModeChanged(mode) {
|
||||
@@ -233,7 +268,7 @@ FocusScope {
|
||||
id: searchBarSurface
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(root._hasQuery ? Theme.surfaceContainerHigh : Theme.surfaceContainer, root._hasQuery ? Theme.popupTransparency : Math.max(0.68, Theme.popupTransparency * 0.9))
|
||||
color: root._searchSurfaceColor
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
@@ -250,7 +285,7 @@ FocusScope {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: searchInput.activeFocus ? Theme.primaryContainer : Theme.surfaceContainer
|
||||
color: root._searchWellColor
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -269,8 +304,8 @@ FocusScope {
|
||||
|
||||
Row {
|
||||
id: categoryRow
|
||||
visible: SettingsData.spotlightBarShowModeChips || root._hasQuery
|
||||
spacing: Theme.spacingXS
|
||||
visible: root._hasQuery
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Repeater {
|
||||
@@ -376,26 +411,9 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: searchBarItem.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
opacity: root._resultsH > 0 ? 0.55 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: root._fastDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: resultsContainer
|
||||
anchors.top: searchBarItem.bottom
|
||||
anchors.topMargin: 1
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
clip: true
|
||||
|
||||
@@ -12,6 +12,7 @@ Item {
|
||||
property var controller: null
|
||||
property bool hasQuery: false
|
||||
property var rows: []
|
||||
readonly property real bottomInset: Theme.spacingS
|
||||
|
||||
signal itemRightClicked(int index, var item, real mouseX, real mouseY)
|
||||
|
||||
@@ -53,7 +54,11 @@ Item {
|
||||
|
||||
DankListView {
|
||||
id: mainListView
|
||||
anchors.fill: parent
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.bottomInset
|
||||
clip: true
|
||||
visible: root.rows.length > 0
|
||||
|
||||
@@ -64,11 +69,6 @@ Item {
|
||||
objectProp: "_rowId"
|
||||
}
|
||||
|
||||
add: null
|
||||
remove: null
|
||||
displaced: null
|
||||
move: null
|
||||
|
||||
delegate: Item {
|
||||
id: delegateRoot
|
||||
required property var modelData
|
||||
@@ -103,7 +103,11 @@ Item {
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.bottomInset
|
||||
visible: root.hasQuery && root.rows.length === 0
|
||||
|
||||
Row {
|
||||
|
||||
@@ -64,6 +64,7 @@ FocusScope {
|
||||
|
||||
sourceComponent: KeybindsTab {
|
||||
parentModal: root.parentModal
|
||||
requestedSearchQuery: root.parentModal?.keybindSearchQuery ?? ""
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
|
||||
@@ -37,6 +37,7 @@ FloatingWindow {
|
||||
property bool isCompactMode: width < 700
|
||||
property bool menuVisible: !isCompactMode
|
||||
property bool enableAnimations: true
|
||||
property string keybindSearchQuery: ""
|
||||
|
||||
signal closingModal
|
||||
|
||||
@@ -73,6 +74,11 @@ FloatingWindow {
|
||||
return sidebar.resolveTabIndex(tabName);
|
||||
}
|
||||
|
||||
function showKeybindsSearch(query: string) {
|
||||
keybindSearchQuery = query || "";
|
||||
showWithTabName("keybinds");
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
enableAnimations = true;
|
||||
menuVisible = !menuVisible;
|
||||
|
||||
@@ -16,6 +16,7 @@ Item {
|
||||
property var parentModal: null
|
||||
property string selectedCategory: ""
|
||||
property string searchQuery: ""
|
||||
property string requestedSearchQuery: ""
|
||||
property string expandedKey: ""
|
||||
property bool showingNewBind: false
|
||||
|
||||
@@ -206,13 +207,34 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: _ensureCurrentProvider()
|
||||
function _applyRequestedSearch() {
|
||||
if (!requestedSearchQuery)
|
||||
return;
|
||||
const query = requestedSearchQuery;
|
||||
selectedCategory = "";
|
||||
searchField.text = query;
|
||||
searchQuery = query;
|
||||
_updateFiltered();
|
||||
if (parentModal?.keybindSearchQuery === query)
|
||||
parentModal.keybindSearchQuery = "";
|
||||
Qt.callLater(scrollToTop);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
_ensureCurrentProvider();
|
||||
Qt.callLater(_applyRequestedSearch);
|
||||
}
|
||||
|
||||
onRequestedSearchQueryChanged: Qt.callLater(_applyRequestedSearch)
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible)
|
||||
return;
|
||||
Qt.callLater(scrollToTop);
|
||||
_ensureCurrentProvider();
|
||||
Qt.callLater(() => {
|
||||
_applyRequestedSearch();
|
||||
scrollToTop();
|
||||
});
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
|
||||
@@ -9,6 +9,37 @@ Item {
|
||||
id: root
|
||||
|
||||
property var parentModal: null
|
||||
readonly property string defaultLauncherAction: "spawn dms ipc call spotlight toggle"
|
||||
readonly property string spotlightBarAction: "spawn dms ipc call spotlight-bar toggle"
|
||||
readonly property int keybindDataVersion: KeybindsService._dataVersion
|
||||
readonly property bool keybindsAvailable: KeybindsService.available
|
||||
readonly property string defaultLauncherKeybindSearch: "spotlight toggle"
|
||||
readonly property string spotlightBarKeybindSearch: "spotlight-bar"
|
||||
|
||||
function openKeybindsSearch(query) {
|
||||
if (!root.parentModal)
|
||||
return;
|
||||
if (typeof root.parentModal.showKeybindsSearch === "function") {
|
||||
root.parentModal.showKeybindsSearch(query);
|
||||
} else {
|
||||
root.parentModal.showWithTabName("keybinds");
|
||||
}
|
||||
}
|
||||
|
||||
function keysLabel(actionId) {
|
||||
void (keybindDataVersion);
|
||||
if (!keybindsAvailable)
|
||||
return I18n.tr("Manual config");
|
||||
const keys = KeybindsService.keysForAction(actionId);
|
||||
if (!keys || keys.length === 0)
|
||||
return I18n.tr("Not bound");
|
||||
return keys.join(", ");
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (KeybindsService.available)
|
||||
KeybindsService.loadBinds(false);
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: logoFileBrowser
|
||||
@@ -35,20 +66,20 @@ Item {
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "search"
|
||||
title: I18n.tr("Launcher Style")
|
||||
title: I18n.tr("Default Launcher")
|
||||
settingKey: "launcherStyle"
|
||||
|
||||
SettingsControlledByFrame {
|
||||
visible: SettingsData.connectedFrameModeActive
|
||||
parentModal: root.parentModal
|
||||
settingLabel: I18n.tr("Launcher Style")
|
||||
reason: I18n.tr("Managed by Frame Mode")
|
||||
settingLabel: I18n.tr("Default Launcher")
|
||||
reason: I18n.tr("Connected Frame Mode uses the connected launcher for default launcher shortcuts.")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
visible: !SettingsData.connectedFrameModeActive
|
||||
text: SettingsData.launcherStyle === "spotlight" ? I18n.tr("Minimal Spotlight-style bar: appears instantly at the top of the screen and expands as you type.") : I18n.tr("Full-featured launcher with mode tabs, grid view, and action panel.")
|
||||
text: SettingsData.launcherStyle === "spotlight" ? I18n.tr("Default launcher shortcuts open the minimal Spotlight Bar. The dedicated Spotlight Bar shortcut below stays independent.") : I18n.tr("Default launcher shortcuts open the full launcher with mode tabs, grid view, and action panel.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
@@ -57,8 +88,8 @@ Item {
|
||||
SettingsButtonGroupRow {
|
||||
visible: !SettingsData.connectedFrameModeActive
|
||||
settingKey: "launcherStyleSelector"
|
||||
tags: ["launcher", "style", "spotlight", "full", "minimal"]
|
||||
text: I18n.tr("Style")
|
||||
tags: ["launcher", "style", "default", "spotlight", "full", "minimal"]
|
||||
text: I18n.tr("Default Opens")
|
||||
model: [I18n.tr("Full"), I18n.tr("Spotlight")]
|
||||
currentIndex: SettingsData.launcherStyle === "spotlight" ? 1 : 0
|
||||
onSelectionChanged: (index, selected) => {
|
||||
@@ -68,6 +99,76 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: defaultShortcutCard
|
||||
width: parent.width
|
||||
height: defaultShortcutRow.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: defaultShortcutMouse.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.48) : Theme.withAlpha(Theme.surfaceContainer, 0.35)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
id: defaultShortcutRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "keyboard"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: Math.max(0, parent.width - Theme.iconSize - defaultShortcutValue.width - Theme.spacingM * 2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Default Launcher Shortcut")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: !root.keybindsAvailable ? I18n.tr("Bind the spotlight IPC action in your compositor config.") : SettingsData.connectedFrameModeActive ? I18n.tr("Opens the connected launcher in Connected Frame Mode.") : I18n.tr("Follows the default launcher choice selected above.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: defaultShortcutValue
|
||||
text: root.keysLabel(root.defaultLauncherAction)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
width: Math.min(170, implicitWidth)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: defaultShortcutMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.openKeybindsSearch(root.defaultLauncherKeybindSearch)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "launcherUseOverlayLayer"
|
||||
tags: ["launcher", "fullscreen", "overlay", "layer"]
|
||||
@@ -78,6 +179,100 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "search"
|
||||
title: I18n.tr("Spotlight Bar")
|
||||
settingKey: "spotlightBarLauncher"
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("A separate minimal launcher action that works in Standalone, Separate Frame Mode, and Connected Frame Mode.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: spotlightShortcutCard
|
||||
width: parent.width
|
||||
height: spotlightShortcutRow.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: spotlightShortcutMouse.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.48) : Theme.withAlpha(Theme.surfaceContainer, 0.35)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
id: spotlightShortcutRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "keyboard"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
width: Math.max(0, parent.width - Theme.iconSize - spotlightShortcutValue.width - Theme.spacingM * 2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Spotlight Bar Shortcut")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: !root.keybindsAvailable ? I18n.tr("Bind the spotlight-bar IPC action in your compositor config.") : I18n.tr("Uses the spotlight-bar IPC action and always opens the minimal bar.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: spotlightShortcutValue
|
||||
text: root.keysLabel(root.spotlightBarAction)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
width: Math.min(170, implicitWidth)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: spotlightShortcutMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.openKeybindsSearch(root.spotlightBarKeybindSearch)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "spotlightBarShowModeChips"
|
||||
tags: ["launcher", "spotlight", "bar", "chips", "tabs", "modes"]
|
||||
text: I18n.tr("Show Mode Chips")
|
||||
description: I18n.tr("Show All, Apps, Files, and Plugins chips beside the Spotlight Bar input.")
|
||||
checked: SettingsData.spotlightBarShowModeChips
|
||||
onToggled: checked => SettingsData.set("spotlightBarShowModeChips", checked)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "apps"
|
||||
|
||||
@@ -9,6 +9,7 @@ import qs.Services
|
||||
Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("AppSearchService")
|
||||
property int refCount: 0
|
||||
|
||||
property var applications: []
|
||||
property var _cachedCategories: null
|
||||
@@ -297,7 +298,7 @@ Singleton {
|
||||
function getBuiltInLauncherItems(pluginId, query) {
|
||||
if (pluginId === "dms_clipboard_search") {
|
||||
const trimmed = (query || "").toString().trim();
|
||||
const entries = ClipboardService.getCachedLauncherSearchEntries(trimmed, 20);
|
||||
const entries = ClipboardService.internalEntries.length > 0 ? ClipboardService.getLauncherEntries(trimmed, 20, 0) : ClipboardService.getCachedLauncherSearchEntries(trimmed, 20);
|
||||
return entries.map(entry => ({
|
||||
type: "clipboard",
|
||||
data: entry
|
||||
|
||||
@@ -463,6 +463,24 @@ Singleton {
|
||||
return _flatCache;
|
||||
}
|
||||
|
||||
function keysForAction(actionId) {
|
||||
if (!actionId)
|
||||
return [];
|
||||
for (let i = 0; i < _flatCache.length; i++) {
|
||||
const group = _flatCache[i];
|
||||
if (!group || group.action !== actionId || !Array.isArray(group.keys))
|
||||
continue;
|
||||
const keys = [];
|
||||
for (let k = 0; k < group.keys.length; k++) {
|
||||
const key = group.keys[k]?.key || "";
|
||||
if (key)
|
||||
keys.push(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function saveBind(originalKey, bindData) {
|
||||
if (!bindData.key || !Actions.isValidAction(bindData.action))
|
||||
return;
|
||||
|
||||
@@ -205,6 +205,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function launchDesktopEntry(desktopEntry, useNvidia) {
|
||||
if (!desktopEntry || !desktopEntry.command)
|
||||
return;
|
||||
let cmd = desktopEntry.command;
|
||||
|
||||
const appId = desktopEntry.id || desktopEntry.execString || desktopEntry.exec || "";
|
||||
@@ -261,6 +263,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function launchDesktopAction(desktopEntry, action, useNvidia) {
|
||||
if (!desktopEntry || !action || !action.command)
|
||||
return;
|
||||
let cmd = action.command;
|
||||
|
||||
const appId = desktopEntry.id || desktopEntry.execString || desktopEntry.exec || "";
|
||||
|
||||
@@ -40,8 +40,8 @@ StyledRect {
|
||||
property color focusedBorderColor: Theme.primary
|
||||
property color normalBorderColor: Theme.outlineMedium
|
||||
property color placeholderColor: Theme.outlineButton
|
||||
property int borderWidth: 1
|
||||
property int focusedBorderWidth: 2
|
||||
property real borderWidth: 1
|
||||
property real focusedBorderWidth: 2
|
||||
property real cornerRadius: Theme.cornerRadius
|
||||
readonly property real leftPadding: Theme.spacingM + (leftIconName ? leftIconSize + Theme.spacingM : 0)
|
||||
readonly property real rightPadding: {
|
||||
|
||||
@@ -939,7 +939,7 @@
|
||||
"topbar",
|
||||
"wayland"
|
||||
],
|
||||
"description": "Place this bar on the Wayland overlay layer"
|
||||
"description": "Place the bar on the Wayland overlay layer"
|
||||
},
|
||||
{
|
||||
"section": "barVisibility",
|
||||
@@ -967,7 +967,7 @@
|
||||
"wayland"
|
||||
],
|
||||
"icon": "visibility_off",
|
||||
"description": "Place this bar on the Wayland overlay layer"
|
||||
"description": "Place the bar on the Wayland overlay layer"
|
||||
},
|
||||
{
|
||||
"section": "workspaceDragReorder",
|
||||
@@ -2052,6 +2052,50 @@
|
||||
],
|
||||
"icon": "extension"
|
||||
},
|
||||
{
|
||||
"section": "launcherStyle",
|
||||
"label": "Default Launcher",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"app drawer",
|
||||
"app menu",
|
||||
"applications",
|
||||
"default",
|
||||
"drawer",
|
||||
"full",
|
||||
"launcher",
|
||||
"layer",
|
||||
"menu",
|
||||
"minimal",
|
||||
"opening",
|
||||
"overlay",
|
||||
"spotlight",
|
||||
"start",
|
||||
"start menu",
|
||||
"style"
|
||||
],
|
||||
"icon": "search",
|
||||
"description": "Use the overlay layer when opening the launcher"
|
||||
},
|
||||
{
|
||||
"section": "launcherStyleSelector",
|
||||
"label": "Default Opens",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"default",
|
||||
"drawer",
|
||||
"full",
|
||||
"launcher",
|
||||
"menu",
|
||||
"minimal",
|
||||
"opens",
|
||||
"spotlight",
|
||||
"start",
|
||||
"style"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "niriOverviewOverlayEnabled",
|
||||
"label": "Enable Overview Overlay",
|
||||
@@ -2228,31 +2272,6 @@
|
||||
],
|
||||
"icon": "apps"
|
||||
},
|
||||
{
|
||||
"section": "launcherStyle",
|
||||
"label": "Launcher Style",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"app drawer",
|
||||
"app menu",
|
||||
"applications",
|
||||
"drawer",
|
||||
"full",
|
||||
"launcher",
|
||||
"layer",
|
||||
"menu",
|
||||
"minimal",
|
||||
"opening",
|
||||
"overlay",
|
||||
"spotlight",
|
||||
"start",
|
||||
"start menu",
|
||||
"style"
|
||||
],
|
||||
"icon": "search",
|
||||
"description": "Use the overlay layer when opening the launcher"
|
||||
},
|
||||
{
|
||||
"section": "spotlightCloseNiriOverview",
|
||||
"label": "Niri Integration",
|
||||
@@ -2394,6 +2413,40 @@
|
||||
],
|
||||
"description": "Show mode tabs and keyboard hints at the bottom."
|
||||
},
|
||||
{
|
||||
"section": "spotlightBarShowModeChips",
|
||||
"label": "Show Mode Chips",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"addons",
|
||||
"apps",
|
||||
"bar",
|
||||
"beside",
|
||||
"chips",
|
||||
"day",
|
||||
"drawer",
|
||||
"extensions",
|
||||
"files",
|
||||
"input",
|
||||
"launcher",
|
||||
"light mode",
|
||||
"menu",
|
||||
"mode",
|
||||
"modes",
|
||||
"panel",
|
||||
"plugins",
|
||||
"show",
|
||||
"spotlight",
|
||||
"start",
|
||||
"statusbar",
|
||||
"tabs",
|
||||
"taskbar",
|
||||
"topbar",
|
||||
"widgets"
|
||||
],
|
||||
"description": "Show All, Apps, Files, and Plugins chips beside the Spotlight Bar input."
|
||||
},
|
||||
{
|
||||
"section": "launcherLogoSizeOffset",
|
||||
"label": "Size Offset",
|
||||
@@ -2458,20 +2511,38 @@
|
||||
"description": "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency."
|
||||
},
|
||||
{
|
||||
"section": "launcherStyleSelector",
|
||||
"label": "Style",
|
||||
"section": "spotlightBarLauncher",
|
||||
"label": "Spotlight Bar",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"addons",
|
||||
"apps",
|
||||
"bar",
|
||||
"beside",
|
||||
"chips",
|
||||
"day",
|
||||
"drawer",
|
||||
"full",
|
||||
"extensions",
|
||||
"files",
|
||||
"input",
|
||||
"launcher",
|
||||
"light mode",
|
||||
"menu",
|
||||
"minimal",
|
||||
"modes",
|
||||
"panel",
|
||||
"plugins",
|
||||
"show",
|
||||
"spotlight",
|
||||
"start",
|
||||
"style"
|
||||
]
|
||||
"statusbar",
|
||||
"tabs",
|
||||
"taskbar",
|
||||
"topbar",
|
||||
"widgets"
|
||||
],
|
||||
"icon": "search",
|
||||
"description": "Show All, Apps, Files, and Plugins chips beside the Spotlight Bar input."
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2BorderThickness",
|
||||
|
||||
Reference in New Issue
Block a user