mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
Compare commits
5 Commits
f2be6cfeb1
...
80ce6aa19c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80ce6aa19c | ||
|
|
2b2977de4a | ||
|
|
1d5d876e16 | ||
|
|
3c39162016 | ||
|
|
d38767fb5a |
@@ -215,8 +215,8 @@ func (cd *ConfigDeployer) deployNiriDmsConfigs(dmsDir, terminalCommand string) e
|
||||
|
||||
for _, cfg := range configs {
|
||||
path := filepath.Join(dmsDir, cfg.name)
|
||||
// Skip if file already exists to preserve user modifications
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Skip if file already exists and is not empty to preserve user modifications
|
||||
if info, err := os.Stat(path); err == nil && info.Size() > 0 {
|
||||
cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
|
||||
continue
|
||||
}
|
||||
@@ -567,7 +567,8 @@ func (cd *ConfigDeployer) deployHyprlandDmsConfigs(dmsDir string, terminalComman
|
||||
|
||||
for _, cfg := range configs {
|
||||
path := filepath.Join(dmsDir, cfg.name)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Skip if file already exists and is not empty to preserve user modifications
|
||||
if info, err := os.Stat(path); err == nil && info.Size() > 0 {
|
||||
cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -236,7 +236,13 @@ Singleton {
|
||||
property bool sortAppsAlphabetically: false
|
||||
property int appLauncherGridColumns: 4
|
||||
property bool spotlightCloseNiriOverview: true
|
||||
property var spotlightSectionViewModes: ({})
|
||||
property bool niriOverviewOverlayEnabled: true
|
||||
property string dankLauncherV2Size: "compact"
|
||||
property bool dankLauncherV2BorderEnabled: false
|
||||
property int dankLauncherV2BorderThickness: 2
|
||||
property string dankLauncherV2BorderColor: "primary"
|
||||
property bool dankLauncherV2ShowFooter: true
|
||||
|
||||
property string _legacyWeatherLocation: "New York, NY"
|
||||
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
|
||||
|
||||
@@ -134,7 +134,13 @@ var SPEC = {
|
||||
sortAppsAlphabetically: { def: false },
|
||||
appLauncherGridColumns: { def: 4 },
|
||||
spotlightCloseNiriOverview: { def: true },
|
||||
spotlightSectionViewModes: { def: {} },
|
||||
niriOverviewOverlayEnabled: { def: true },
|
||||
dankLauncherV2Size: { def: "compact" },
|
||||
dankLauncherV2BorderEnabled: { def: false },
|
||||
dankLauncherV2BorderThickness: { def: 2 },
|
||||
dankLauncherV2BorderColor: { def: "primary" },
|
||||
dankLauncherV2ShowFooter: { def: true },
|
||||
|
||||
useAutoLocation: { def: false },
|
||||
weatherEnabled: { def: true },
|
||||
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
import qs.Modules.DankDash
|
||||
@@ -514,6 +515,25 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: spotlightV2ModalLoader
|
||||
|
||||
active: false
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.spotlightV2ModalLoader = spotlightV2ModalLoader;
|
||||
}
|
||||
|
||||
DankLauncherV2Modal {
|
||||
id: spotlightV2Modal
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.spotlightV2Modal = spotlightV2Modal;
|
||||
PopoutService._onSpotlightV2ModalLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardHistoryModal {
|
||||
id: clipboardHistoryModalPopup
|
||||
|
||||
|
||||
@@ -1025,6 +1025,35 @@ Item {
|
||||
target: "clipboard"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
PopoutService.openSpotlightV2();
|
||||
return "LAUNCHER_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
PopoutService.closeSpotlightV2();
|
||||
return "LAUNCHER_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
PopoutService.toggleSpotlightV2();
|
||||
return "LAUNCHER_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
function openQuery(query: string): string {
|
||||
PopoutService.openSpotlightV2WithQuery(query);
|
||||
return "LAUNCHER_OPEN_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
function toggleQuery(query: string): string {
|
||||
PopoutService.toggleSpotlightV2();
|
||||
return "LAUNCHER_TOGGLE_QUERY_SUCCESS";
|
||||
}
|
||||
|
||||
target: "launcher"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
FirstLaunchService.showWelcome();
|
||||
|
||||
231
quickshell/Modals/DankLauncherV2/ActionPanel.qml
Normal file
231
quickshell/Modals/DankLauncherV2/ActionPanel.qml
Normal file
@@ -0,0 +1,231 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var selectedItem: null
|
||||
property var controller: null
|
||||
property bool expanded: false
|
||||
property int selectedActionIndex: 0
|
||||
|
||||
function getPluginContextMenuActions() {
|
||||
if (selectedItem?.type !== "plugin" || !selectedItem?.pluginId)
|
||||
return [];
|
||||
var instance = PluginService.pluginInstances[selectedItem.pluginId];
|
||||
if (!instance)
|
||||
return [];
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return [];
|
||||
var actions = instance.getContextMenuActions(selectedItem.data);
|
||||
if (!Array.isArray(actions))
|
||||
return [];
|
||||
return actions;
|
||||
}
|
||||
|
||||
readonly property var actions: {
|
||||
var result = [];
|
||||
if (selectedItem?.primaryAction) {
|
||||
result.push(selectedItem.primaryAction);
|
||||
}
|
||||
|
||||
if (selectedItem?.type === "plugin") {
|
||||
var pluginActions = getPluginContextMenuActions();
|
||||
for (var i = 0; i < pluginActions.length; i++) {
|
||||
var act = pluginActions[i];
|
||||
result.push({
|
||||
name: act.text || act.name || "",
|
||||
icon: act.icon || "play_arrow",
|
||||
action: "plugin_action",
|
||||
pluginAction: act.action
|
||||
});
|
||||
}
|
||||
} else if (selectedItem?.type === "app" && !selectedItem?.isCore) {
|
||||
if (selectedItem?.actions) {
|
||||
for (var i = 0; i < selectedItem.actions.length; i++) {
|
||||
result.push(selectedItem.actions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
readonly property bool hasActions: {
|
||||
if (selectedItem?.type === "app" && !selectedItem?.isCore)
|
||||
return true;
|
||||
if (selectedItem?.type === "plugin") {
|
||||
var pluginActions = getPluginContextMenuActions();
|
||||
return pluginActions.length > 0;
|
||||
}
|
||||
return actions.length > 1;
|
||||
}
|
||||
|
||||
width: parent?.width ?? 200
|
||||
height: expanded && hasActions ? 52 : 0
|
||||
color: Theme.surfaceContainerHigh
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
clip: true
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
|
||||
Flickable {
|
||||
id: actionsFlickable
|
||||
anchors.left: parent.left
|
||||
anchors.right: tabHint.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: parent.height
|
||||
contentWidth: actionsRow.width
|
||||
contentHeight: height
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickableDirection: Flickable.HorizontalFlick
|
||||
|
||||
Row {
|
||||
id: actionsRow
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
|
||||
Rectangle {
|
||||
id: actionButton
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: actionContent.implicitWidth + Theme.spacingM * 2
|
||||
height: actionsRow.height
|
||||
radius: Theme.cornerRadius
|
||||
color: index === root.selectedActionIndex ? Theme.primaryHover : actionArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
Row {
|
||||
id: actionContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: actionButton.modelData?.icon ?? "play_arrow"
|
||||
size: 16
|
||||
color: actionButton.index === root.selectedActionIndex ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: actionButton.modelData?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: actionButton.index === root.selectedActionIndex ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.controller && root.selectedItem) {
|
||||
root.controller.executeAction(root.selectedItem, actionButton.modelData);
|
||||
}
|
||||
}
|
||||
onEntered: root.selectedActionIndex = actionButton.index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: tabHint
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActions
|
||||
text: "Tab"
|
||||
font.pixelSize: Theme.fontSizeSmall - 2
|
||||
color: Theme.outlineButton
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
expanded = !expanded;
|
||||
selectedActionIndex = 0;
|
||||
}
|
||||
|
||||
function show() {
|
||||
expanded = true;
|
||||
selectedActionIndex = actions.length > 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
expanded = false;
|
||||
selectedActionIndex = 0;
|
||||
}
|
||||
|
||||
function cycleAction() {
|
||||
if (actions.length > 0) {
|
||||
selectedActionIndex = (selectedActionIndex + 1) % actions.length;
|
||||
ensureSelectedVisible();
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSelectedVisible() {
|
||||
if (selectedActionIndex < 0 || !actionsRow.children || selectedActionIndex >= actionsRow.children.length)
|
||||
return;
|
||||
var buttonX = 0;
|
||||
for (var i = 0; i < selectedActionIndex; i++) {
|
||||
var child = actionsRow.children[i];
|
||||
if (child)
|
||||
buttonX += child.width + actionsRow.spacing;
|
||||
}
|
||||
|
||||
var button = actionsRow.children[selectedActionIndex];
|
||||
if (!button)
|
||||
return;
|
||||
var buttonRight = buttonX + button.width;
|
||||
var viewLeft = actionsFlickable.contentX;
|
||||
var viewRight = viewLeft + actionsFlickable.width;
|
||||
|
||||
if (buttonX < viewLeft) {
|
||||
actionsFlickable.contentX = Math.max(0, buttonX - Theme.spacingS);
|
||||
} else if (buttonRight > viewRight) {
|
||||
actionsFlickable.contentX = Math.min(actionsFlickable.contentWidth - actionsFlickable.width, buttonRight - actionsFlickable.width + Theme.spacingS);
|
||||
}
|
||||
}
|
||||
|
||||
function executeSelectedAction() {
|
||||
if (!controller || !selectedItem || selectedActionIndex >= actions.length)
|
||||
return;
|
||||
var action = actions[selectedActionIndex];
|
||||
if (action.action === "plugin_action" && typeof action.pluginAction === "function") {
|
||||
action.pluginAction();
|
||||
controller.performSearch();
|
||||
controller.itemExecuted();
|
||||
} else {
|
||||
controller.executeAction(selectedItem, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
1570
quickshell/Modals/DankLauncherV2/Controller.qml
Normal file
1570
quickshell/Modals/DankLauncherV2/Controller.qml
Normal file
File diff suppressed because it is too large
Load Diff
283
quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml
Normal file
283
quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml
Normal file
@@ -0,0 +1,283 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool spotlightOpen: false
|
||||
property bool keyboardActive: false
|
||||
property bool contentVisible: false
|
||||
property alias spotlightContent: launcherContent
|
||||
property bool openedFromOverview: false
|
||||
property bool isClosing: false
|
||||
|
||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||
readonly property var effectiveScreen: launcherWindow.screen
|
||||
readonly property real screenWidth: effectiveScreen?.width ?? 1920
|
||||
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
||||
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||
|
||||
readonly property int baseWidth: SettingsData.dankLauncherV2Size === "medium" ? 600 : SettingsData.dankLauncherV2Size === "large" ? 720 : 500
|
||||
readonly property int baseHeight: SettingsData.dankLauncherV2Size === "medium" ? 680 : SettingsData.dankLauncherV2Size === "large" ? 820 : 560
|
||||
readonly property int modalWidth: Math.min(baseWidth, screenWidth - 100)
|
||||
readonly property int modalHeight: Math.min(baseHeight, screenHeight - 100)
|
||||
readonly property real modalX: (screenWidth - modalWidth) / 2
|
||||
readonly property real modalY: (screenHeight - modalHeight) / 2
|
||||
|
||||
readonly property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
readonly property real cornerRadius: Theme.cornerRadius
|
||||
readonly property color borderColor: {
|
||||
if (!SettingsData.dankLauncherV2BorderEnabled)
|
||||
return Theme.outlineMedium;
|
||||
switch (SettingsData.dankLauncherV2BorderColor) {
|
||||
case "primary":
|
||||
return Theme.primary;
|
||||
case "secondary":
|
||||
return Theme.secondary;
|
||||
case "outline":
|
||||
return Theme.outline;
|
||||
case "surfaceText":
|
||||
return Theme.surfaceText;
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 1
|
||||
|
||||
signal dialogClosed
|
||||
|
||||
function _initializeAndShow(query) {
|
||||
contentVisible = true;
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.text = query;
|
||||
}
|
||||
if (spotlightContent.controller) {
|
||||
spotlightContent.controller.searchMode = "all";
|
||||
spotlightContent.controller.activePluginId = "";
|
||||
spotlightContent.controller.activePluginName = "";
|
||||
spotlightContent.controller.pluginFilter = "";
|
||||
spotlightContent.controller.collapsedSections = {};
|
||||
if (query) {
|
||||
spotlightContent.controller.setSearchQuery(query);
|
||||
} else {
|
||||
spotlightContent.controller.searchQuery = "";
|
||||
spotlightContent.controller.performSearch();
|
||||
}
|
||||
}
|
||||
if (spotlightContent.resetScroll) {
|
||||
spotlightContent.resetScroll();
|
||||
}
|
||||
if (spotlightContent.actionPanel) {
|
||||
spotlightContent.actionPanel.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
closeCleanupTimer.stop();
|
||||
isClosing = false;
|
||||
openedFromOverview = false;
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen)
|
||||
launcherWindow.screen = focusedScreen;
|
||||
|
||||
spotlightOpen = true;
|
||||
keyboardActive = true;
|
||||
ModalManager.openModal(root);
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
_initializeAndShow("");
|
||||
}
|
||||
|
||||
function showWithQuery(query) {
|
||||
closeCleanupTimer.stop();
|
||||
isClosing = false;
|
||||
openedFromOverview = false;
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen)
|
||||
launcherWindow.screen = focusedScreen;
|
||||
|
||||
spotlightOpen = true;
|
||||
keyboardActive = true;
|
||||
ModalManager.openModal(root);
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
_initializeAndShow(query);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (!spotlightOpen)
|
||||
return;
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
contentVisible = false;
|
||||
|
||||
keyboardActive = false;
|
||||
spotlightOpen = false;
|
||||
focusGrab.active = false;
|
||||
ModalManager.closeModal(root);
|
||||
|
||||
closeCleanupTimer.start();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
spotlightOpen ? hide() : show();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeCleanupTimer
|
||||
interval: Theme.expressiveDurations.expressiveFastSpatial + 50
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
isClosing = false;
|
||||
dialogClosed();
|
||||
}
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
windows: [launcherWindow]
|
||||
active: false
|
||||
|
||||
onCleared: {
|
||||
if (spotlightOpen) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onCloseAllModalsExcept(excludedModal) {
|
||||
if (excludedModal !== root && spotlightOpen) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: launcherWindow
|
||||
visible: true
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
WlrLayershell.namespace: "dms:launcher"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: spotlightOpen ? fullScreenMask : null
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fullScreenMask
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: backgroundDarken
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: contentVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
||||
visible: contentVisible || opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveFastSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: spotlightOpen
|
||||
onClicked: mouse => {
|
||||
var contentX = modalContainer.x;
|
||||
var contentY = modalContainer.y;
|
||||
var contentW = modalContainer.width;
|
||||
var contentH = modalContainer.height;
|
||||
|
||||
if (mouse.x < contentX || mouse.x > contentX + contentW || mouse.y < contentY || mouse.y > contentY + contentH) {
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: modalContainer
|
||||
x: root.modalX
|
||||
y: root.modalY
|
||||
width: root.modalWidth
|
||||
height: root.modalHeight
|
||||
visible: contentVisible || opacity > 0
|
||||
|
||||
opacity: contentVisible ? 1 : 0
|
||||
scale: contentVisible ? 1 : 0.96
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
}
|
||||
}
|
||||
|
||||
DankRectangle {
|
||||
anchors.fill: parent
|
||||
color: root.backgroundColor
|
||||
borderColor: root.borderColor
|
||||
borderWidth: root.borderWidth
|
||||
radius: root.cornerRadius
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: mouse => mouse.accepted = true
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: keyboardActive
|
||||
|
||||
LauncherContent {
|
||||
id: launcherContent
|
||||
anchors.fill: parent
|
||||
parentModal: root
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.hide();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
quickshell/Modals/DankLauncherV2/GridItem.qml
Normal file
139
quickshell/Modals/DankLauncherV2/GridItem.qml
Normal file
@@ -0,0 +1,139 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property bool isSelected: false
|
||||
property bool isHovered: itemArea.containsMouse
|
||||
property var controller: null
|
||||
property int flatIndex: -1
|
||||
|
||||
signal clicked
|
||||
signal rightClicked(real mouseX, real mouseY)
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
width: parent.width - Theme.spacingM
|
||||
|
||||
Item {
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
property int iconSize: Math.min(48, Math.max(32, root.width * 0.45))
|
||||
|
||||
Image {
|
||||
id: appIcon
|
||||
anchors.fill: parent
|
||||
visible: root.item?.iconType === "image"
|
||||
asynchronous: true
|
||||
source: root.item?.iconType === "image" ? "image://icon/" + (root.item?.icon || "application-x-executable") : ""
|
||||
sourceSize.width: parent.iconSize
|
||||
sourceSize.height: parent.iconSize
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: false
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: root.item?.iconType === "material" || root.item?.iconType === "nerd"
|
||||
name: root.item?.icon ?? "apps"
|
||||
size: parent.iconSize * 0.7
|
||||
color: root.isSelected ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: root.item?.iconType === "composite"
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
source: {
|
||||
if (!root.item || root.item.iconType !== "composite")
|
||||
return "";
|
||||
var iconFull = root.item.iconFull || "";
|
||||
if (iconFull.startsWith("svg+corner:")) {
|
||||
var parts = iconFull.substring(11).split("|");
|
||||
return parts[0] || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
sourceSize.width: parent.width
|
||||
sourceSize.height: parent.height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Theme.surfaceContainer
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!root.item || root.item.iconType !== "composite")
|
||||
return "";
|
||||
var iconFull = root.item.iconFull || "";
|
||||
if (iconFull.startsWith("svg+corner:")) {
|
||||
var parts = iconFull.substring(11).split("|");
|
||||
return parts[1] || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
size: 12
|
||||
color: root.isSelected ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.item?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: root.isSelected ? Theme.primary : Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var scenePos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.rightClicked(scenePos.x, scenePos.y);
|
||||
} else {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (root.controller) {
|
||||
root.controller.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
817
quickshell/Modals/DankLauncherV2/LauncherContent.qml
Normal file
817
quickshell/Modals/DankLauncherV2/LauncherContent.qml
Normal file
@@ -0,0 +1,817 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property var parentModal: null
|
||||
property alias searchField: searchField
|
||||
property alias controller: controller
|
||||
property alias resultsList: resultsList
|
||||
property alias actionPanel: actionPanel
|
||||
|
||||
property bool editMode: false
|
||||
property var editingApp: null
|
||||
property string editAppId: ""
|
||||
|
||||
function resetScroll() {
|
||||
resultsList.resetScroll();
|
||||
}
|
||||
|
||||
function focusSearchField() {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
|
||||
function openEditMode(app) {
|
||||
if (!app)
|
||||
return;
|
||||
editingApp = app;
|
||||
editAppId = app.id || app.execString || app.exec || "";
|
||||
var 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() {
|
||||
var 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();
|
||||
}
|
||||
|
||||
function showContextMenu(item, x, y, fromKeyboard) {
|
||||
if (!item)
|
||||
return;
|
||||
if (item.isCore)
|
||||
return;
|
||||
if (!contextMenu.hasContextMenuActions(item))
|
||||
return;
|
||||
contextMenu.show(x, y, item, fromKeyboard);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Controller {
|
||||
id: controller
|
||||
|
||||
onItemExecuted: {
|
||||
if (root.parentModal) {
|
||||
root.parentModal.hide();
|
||||
}
|
||||
if (SettingsData.spotlightCloseNiriOverview && NiriService.inOverview) {
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LauncherContextMenu {
|
||||
id: contextMenu
|
||||
parent: root
|
||||
controller: root.controller
|
||||
searchField: root.searchField
|
||||
parentHandler: root
|
||||
|
||||
onEditAppRequested: app => {
|
||||
root.openEditMode(app);
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (editMode) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var hasCtrl = event.modifiers & Qt.ControlModifier;
|
||||
event.accepted = true;
|
||||
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
if (actionPanel.expanded) {
|
||||
actionPanel.hide();
|
||||
return;
|
||||
}
|
||||
if (controller.clearPluginFilter())
|
||||
return;
|
||||
if (root.parentModal)
|
||||
root.parentModal.hide();
|
||||
return;
|
||||
case Qt.Key_Backspace:
|
||||
if (searchField.text.length === 0 && controller.clearPluginFilter())
|
||||
return;
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Down:
|
||||
controller.selectNext();
|
||||
return;
|
||||
case Qt.Key_Up:
|
||||
controller.selectPrevious();
|
||||
return;
|
||||
case Qt.Key_PageDown:
|
||||
controller.selectPageDown(8);
|
||||
return;
|
||||
case Qt.Key_PageUp:
|
||||
controller.selectPageUp(8);
|
||||
return;
|
||||
case Qt.Key_Right:
|
||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||
controller.selectRight();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Left:
|
||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||
controller.selectLeft();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_J:
|
||||
if (hasCtrl) {
|
||||
controller.selectNext();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_K:
|
||||
if (hasCtrl) {
|
||||
controller.selectPrevious();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_N:
|
||||
if (hasCtrl) {
|
||||
controller.selectNextSection();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_P:
|
||||
if (hasCtrl) {
|
||||
controller.selectPreviousSection();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Tab:
|
||||
if (actionPanel.hasActions) {
|
||||
actionPanel.expanded ? actionPanel.cycleAction() : actionPanel.show();
|
||||
}
|
||||
return;
|
||||
case Qt.Key_Backtab:
|
||||
if (actionPanel.expanded)
|
||||
actionPanel.hide();
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
|
||||
actionPanel.executeSelectedAction();
|
||||
} else {
|
||||
controller.executeSelected();
|
||||
}
|
||||
return;
|
||||
case Qt.Key_Menu:
|
||||
case Qt.Key_F10:
|
||||
if (contextMenu.hasContextMenuActions(controller.selectedItem)) {
|
||||
var scenePos = resultsList.getSelectedItemPosition();
|
||||
var localPos = root.mapFromItem(null, scenePos.x, scenePos.y);
|
||||
showContextMenu(controller.selectedItem, localPos.x, localPos.y, true);
|
||||
}
|
||||
return;
|
||||
case Qt.Key_1:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("all");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_2:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("apps");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_3:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("files");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_4:
|
||||
if (hasCtrl) {
|
||||
controller.setMode("plugins");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Slash:
|
||||
if (event.modifiers === Qt.NoModifier && searchField.text.length === 0) {
|
||||
controller.setMode("files");
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
default:
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: !editMode
|
||||
|
||||
Rectangle {
|
||||
id: footerBar
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: SettingsData.dankLauncherV2ShowFooter ? 32 : 0
|
||||
visible: SettingsData.dankLauncherV2ShowFooter
|
||||
color: Theme.surfaceContainerHigh
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Row {
|
||||
id: modeButtonsRow
|
||||
x: I18n.isRtl ? parent.width - width - Theme.spacingS : Theme.spacingS
|
||||
y: (parent.height - height) / 2
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
id: "all",
|
||||
label: I18n.tr("All"),
|
||||
icon: "search",
|
||||
shortcut: "⌃1"
|
||||
},
|
||||
{
|
||||
id: "apps",
|
||||
label: I18n.tr("Apps"),
|
||||
icon: "apps",
|
||||
shortcut: "⌃2"
|
||||
},
|
||||
{
|
||||
id: "files",
|
||||
label: I18n.tr("Files"),
|
||||
icon: "folder",
|
||||
shortcut: "⌃3"
|
||||
},
|
||||
{
|
||||
id: "plugins",
|
||||
label: I18n.tr("Plugins"),
|
||||
icon: "extension",
|
||||
shortcut: "⌃4"
|
||||
}
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: modeButtonMetrics.width + Theme.spacingM * 2
|
||||
height: footerBar.height - 4
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: controller.searchMode === modelData.id || modeArea.containsMouse ? Theme.primaryContainer : "transparent"
|
||||
|
||||
TextMetrics {
|
||||
id: modeButtonMetrics
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
text: modelData.label + " " + modelData.shortcut
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: modelData.icon
|
||||
size: 14
|
||||
color: controller.searchMode === modelData.id ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: controller.searchMode === modelData.id ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.shortcut
|
||||
font.pixelSize: Theme.fontSizeSmall - 2
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: modeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: controller.setMode(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: hintsRow
|
||||
x: I18n.isRtl ? Theme.spacingS : parent.width - width - Theme.spacingS
|
||||
y: (parent.height - height) / 2
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: "↑↓ " + I18n.tr("nav")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "↵ " + I18n.tr("open")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Tab " + I18n.tr("actions")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
visible: actionPanel.hasActions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: footerBar.top
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
spacing: Theme.spacingXS
|
||||
clip: false
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 56
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
id: pluginBadge
|
||||
visible: controller.activePluginName.length > 0
|
||||
width: visible ? pluginBadgeContent.implicitWidth + Theme.spacingM : 0
|
||||
height: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: 16
|
||||
color: Theme.primary
|
||||
|
||||
Row {
|
||||
id: pluginBadgeContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "extension"
|
||||
size: 14
|
||||
color: Theme.primaryText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: controller.activePluginName
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: searchField
|
||||
width: parent.width - (pluginBadge.visible ? pluginBadge.width + Theme.spacingS : 0)
|
||||
height: 56
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: controller.activePluginId ? "extension" : controller.searchQuery.startsWith("/") ? "folder" : "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
leftIconFocusedColor: Theme.primary
|
||||
showClearButton: true
|
||||
textColor: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: root.parentModal ? root.parentModal.spotlightOpen : true
|
||||
placeholderText: ""
|
||||
ignoreUpDownKeys: true
|
||||
ignoreTabKeys: true
|
||||
keyForwardTargets: [root]
|
||||
|
||||
onTextChanged: {
|
||||
controller.setSearchQuery(text);
|
||||
if (actionPanel.expanded) {
|
||||
actionPanel.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (root.parentModal) {
|
||||
root.parentModal.hide();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter)) {
|
||||
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
|
||||
actionPanel.executeSelectedAction();
|
||||
} else {
|
||||
controller.executeSelected();
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - 56 - actionPanel.height - Theme.spacingM - Theme.spacingXS
|
||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
||||
|
||||
ResultsList {
|
||||
id: resultsList
|
||||
anchors.fill: parent
|
||||
controller: root.controller
|
||||
|
||||
onItemRightClicked: (index, item, sceneX, sceneY) => {
|
||||
if (item && contextMenu.hasContextMenuActions(item)) {
|
||||
var localPos = root.mapFromItem(null, sceneX, sceneY);
|
||||
root.showContextMenu(item, localPos.x, localPos.y, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionPanel {
|
||||
id: actionPanel
|
||||
width: parent.width
|
||||
selectedItem: controller.selectedItem
|
||||
controller: controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: controller
|
||||
function onSelectedItemChanged() {
|
||||
if (actionPanel.expanded && !actionPanel.hasActions) {
|
||||
actionPanel.hide();
|
||||
}
|
||||
}
|
||||
function onSearchQueryRequested(query) {
|
||||
searchField.text = query;
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: editView
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
visible: editMode
|
||||
focus: editMode
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
closeEditMode();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (event.key === Qt.Key_S && event.modifiers & Qt.ControlModifier) {
|
||||
saveAppOverride();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
height: 44
|
||||
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
|
||||
height: 44
|
||||
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
|
||||
height: 44
|
||||
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
|
||||
height: 44
|
||||
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
|
||||
height: 44
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
484
quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml
Normal file
484
quickshell/Modals/DankLauncherV2/LauncherContextMenu.qml
Normal file
@@ -0,0 +1,484 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property var controller: null
|
||||
property var searchField: null
|
||||
property var parentHandler: null
|
||||
|
||||
signal hideRequested
|
||||
signal editAppRequested(var app)
|
||||
|
||||
function hasContextMenuActions(spotlightItem) {
|
||||
if (!spotlightItem)
|
||||
return false;
|
||||
if (spotlightItem.type === "app" && !spotlightItem.isCore)
|
||||
return true;
|
||||
if (spotlightItem.type === "plugin" && spotlightItem.pluginId) {
|
||||
var instance = PluginService.pluginInstances[spotlightItem.pluginId];
|
||||
if (!instance)
|
||||
return false;
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return false;
|
||||
var actions = instance.getContextMenuActions(spotlightItem.data);
|
||||
return Array.isArray(actions) && actions.length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property var desktopEntry: item?.data ?? null
|
||||
readonly property string appId: desktopEntry?.id || desktopEntry?.execString || ""
|
||||
readonly property bool isPinned: SessionData.isPinnedApp(appId)
|
||||
readonly property bool isRegularApp: item?.type === "app" && !item.isCore && desktopEntry
|
||||
readonly property bool isPluginItem: item?.type === "plugin"
|
||||
|
||||
function getPluginContextMenuActions() {
|
||||
if (!isPluginItem || !item?.pluginId)
|
||||
return [];
|
||||
|
||||
var instance = PluginService.pluginInstances[item.pluginId];
|
||||
if (!instance)
|
||||
return [];
|
||||
if (typeof instance.getContextMenuActions !== "function")
|
||||
return [];
|
||||
|
||||
var actions = instance.getContextMenuActions(item.data);
|
||||
if (!Array.isArray(actions))
|
||||
return [];
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
function executePluginAction(actionFunc) {
|
||||
if (typeof actionFunc === "function") {
|
||||
actionFunc();
|
||||
}
|
||||
controller?.performSearch();
|
||||
hide();
|
||||
}
|
||||
|
||||
readonly property var menuItems: {
|
||||
var items = [];
|
||||
|
||||
if (isPluginItem) {
|
||||
var pluginActions = getPluginContextMenuActions();
|
||||
for (var i = 0; i < pluginActions.length; i++) {
|
||||
var act = pluginActions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "play_arrow",
|
||||
text: act.text || act.name || "",
|
||||
pluginAction: act.action
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
if (!desktopEntry)
|
||||
return items;
|
||||
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: isPinned ? "keep_off" : "push_pin",
|
||||
text: isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock"),
|
||||
action: togglePin
|
||||
});
|
||||
|
||||
if (isRegularApp) {
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "visibility_off",
|
||||
text: I18n.tr("Hide App"),
|
||||
action: hideCurrentApp
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "edit",
|
||||
text: I18n.tr("Edit App"),
|
||||
action: editCurrentApp
|
||||
});
|
||||
}
|
||||
|
||||
if (item?.actions && item.actions.length > 0) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
for (var i = 0; i < item.actions.length; i++) {
|
||||
var act = item.actions[i];
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: act.icon || "play_arrow",
|
||||
text: act.name || "",
|
||||
actionData: act
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "launch",
|
||||
text: I18n.tr("Launch"),
|
||||
action: launchApp
|
||||
});
|
||||
|
||||
if (SessionService.nvidiaCommand) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
});
|
||||
items.push({
|
||||
type: "item",
|
||||
icon: "memory",
|
||||
text: I18n.tr("Launch on dGPU"),
|
||||
action: launchWithNvidia
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function show(x, y, spotlightItem, fromKeyboard) {
|
||||
if (!spotlightItem?.data)
|
||||
return;
|
||||
item = spotlightItem;
|
||||
selectedMenuIndex = fromKeyboard ? 0 : -1;
|
||||
keyboardNavigation = fromKeyboard;
|
||||
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = false;
|
||||
|
||||
Qt.callLater(() => {
|
||||
var parentW = parent?.width ?? 500;
|
||||
var parentH = parent?.height ?? 600;
|
||||
var menuW = width > 0 ? width : 200;
|
||||
var menuH = height > 0 ? height : 200;
|
||||
var margin = 8;
|
||||
|
||||
var posX = x + 4;
|
||||
var posY = y + 4;
|
||||
|
||||
if (posX + menuW > parentW - margin) {
|
||||
posX = Math.max(margin, parentW - menuW - margin);
|
||||
}
|
||||
if (posY + menuH > parentH - margin) {
|
||||
posY = Math.max(margin, parentH - menuH - margin);
|
||||
}
|
||||
|
||||
root.x = posX;
|
||||
root.y = posY;
|
||||
open();
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = true;
|
||||
close();
|
||||
}
|
||||
|
||||
function togglePin() {
|
||||
if (!appId)
|
||||
return;
|
||||
if (isPinned)
|
||||
SessionData.removePinnedApp(appId);
|
||||
else
|
||||
SessionData.addPinnedApp(appId);
|
||||
hide();
|
||||
}
|
||||
|
||||
function hideCurrentApp() {
|
||||
if (!appId)
|
||||
return;
|
||||
SessionData.hideApp(appId);
|
||||
controller?.performSearch();
|
||||
hide();
|
||||
}
|
||||
|
||||
function editCurrentApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
editAppRequested(desktopEntry);
|
||||
hide();
|
||||
}
|
||||
|
||||
function launchApp() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
SessionService.launchDesktopEntry(desktopEntry);
|
||||
AppUsageHistoryData.addAppUsage(desktopEntry);
|
||||
controller?.itemExecuted();
|
||||
hide();
|
||||
}
|
||||
|
||||
function launchWithNvidia() {
|
||||
if (!desktopEntry)
|
||||
return;
|
||||
SessionService.launchDesktopEntry(desktopEntry, true);
|
||||
AppUsageHistoryData.addAppUsage(desktopEntry);
|
||||
controller?.itemExecuted();
|
||||
hide();
|
||||
}
|
||||
|
||||
function executeDesktopAction(actionData) {
|
||||
if (!desktopEntry || !actionData)
|
||||
return;
|
||||
SessionService.launchDesktopAction(desktopEntry, actionData.actionData || actionData);
|
||||
AppUsageHistoryData.addAppUsage(desktopEntry);
|
||||
controller?.itemExecuted();
|
||||
hide();
|
||||
}
|
||||
|
||||
property int selectedMenuIndex: 0
|
||||
property bool keyboardNavigation: false
|
||||
|
||||
readonly property int visibleItemCount: {
|
||||
var count = 0;
|
||||
for (var i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type === "item")
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (visibleItemCount > 0)
|
||||
selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount;
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (visibleItemCount > 0)
|
||||
selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount;
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
var itemIndex = 0;
|
||||
for (var i = 0; i < menuItems.length; i++) {
|
||||
if (menuItems[i].type !== "item")
|
||||
continue;
|
||||
if (itemIndex === selectedMenuIndex) {
|
||||
var menuItem = menuItems[i];
|
||||
if (menuItem.action)
|
||||
menuItem.action();
|
||||
else if (menuItem.pluginAction)
|
||||
executePluginAction(menuItem.pluginAction);
|
||||
else if (menuItem.actionData)
|
||||
executeDesktopAction(menuItem.actionData);
|
||||
return;
|
||||
}
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
width: menuContainer.implicitWidth
|
||||
height: menuContainer.implicitHeight
|
||||
padding: 0
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
modal: true
|
||||
dim: false
|
||||
background: Item {}
|
||||
|
||||
onOpened: {
|
||||
Qt.callLater(() => keyboardHandler.forceActiveFocus());
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (parentHandler)
|
||||
parentHandler.enabled = true;
|
||||
if (searchField?.visible) {
|
||||
Qt.callLater(() => searchField.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
id: keyboardHandler
|
||||
focus: true
|
||||
implicitWidth: menuContainer.implicitWidth
|
||||
implicitHeight: menuContainer.implicitHeight
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Down:
|
||||
root.selectNext();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Up:
|
||||
root.selectPrevious();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
root.activateSelected();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Escape:
|
||||
case Qt.Key_Left:
|
||||
root.hide();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
anchors.fill: parent
|
||||
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 4
|
||||
anchors.leftMargin: 2
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -4
|
||||
radius: parent.radius
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
z: -1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
model: root.menuItems
|
||||
|
||||
Item {
|
||||
id: menuItemDelegate
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: menuColumn.width
|
||||
height: modelData.type === "separator" ? 5 : 32
|
||||
|
||||
readonly property int itemIndex: {
|
||||
var count = 0;
|
||||
for (var i = 0; i < index; i++) {
|
||||
if (root.menuItems[i].type === "item")
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: menuItemDelegate.modelData.type === "separator"
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: parent.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: menuItemDelegate.modelData.type === "item"
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (root.keyboardNavigation && root.selectedMenuIndex === menuItemDelegate.itemIndex) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
||||
}
|
||||
return itemMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: Theme.iconSize - 2
|
||||
height: Theme.iconSize - 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
visible: (menuItemDelegate.modelData?.icon ?? "").length > 0
|
||||
name: menuItemDelegate.modelData?.icon ?? ""
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: menuItemDelegate.modelData.text || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - (Theme.iconSize - 2) - Theme.spacingS
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
root.keyboardNavigation = false;
|
||||
root.selectedMenuIndex = menuItemDelegate.itemIndex;
|
||||
}
|
||||
onClicked: {
|
||||
var menuItem = menuItemDelegate.modelData;
|
||||
if (menuItem.action)
|
||||
menuItem.action();
|
||||
else if (menuItem.pluginAction)
|
||||
root.executePluginAction(menuItem.pluginAction);
|
||||
else if (menuItem.actionData)
|
||||
root.executeDesktopAction(menuItem.actionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
189
quickshell/Modals/DankLauncherV2/ResultItem.qml
Normal file
189
quickshell/Modals/DankLauncherV2/ResultItem.qml
Normal file
@@ -0,0 +1,189 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property bool isSelected: false
|
||||
property bool isHovered: itemArea.containsMouse
|
||||
property var controller: null
|
||||
property int flatIndex: -1
|
||||
|
||||
signal clicked
|
||||
signal rightClicked(real mouseX, real mouseY)
|
||||
|
||||
width: parent?.width ?? 200
|
||||
height: 52
|
||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Item {
|
||||
width: 36
|
||||
height: 36
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Image {
|
||||
id: appIcon
|
||||
anchors.fill: parent
|
||||
visible: root.item?.iconType === "image"
|
||||
asynchronous: true
|
||||
source: root.item?.iconType === "image" ? "image://icon/" + (root.item?.icon || "application-x-executable") : ""
|
||||
sourceSize.width: 36
|
||||
sourceSize.height: 36
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: false
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: root.item?.iconType === "material" || root.item?.iconType === "nerd"
|
||||
name: root.item?.icon ?? "apps"
|
||||
size: 24
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: root.item?.iconType === "composite"
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
source: {
|
||||
if (!root.item || root.item.iconType !== "composite")
|
||||
return "";
|
||||
var iconFull = root.item.iconFull || "";
|
||||
if (iconFull.startsWith("svg+corner:")) {
|
||||
var parts = iconFull.substring(11).split("|");
|
||||
return parts[0] || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
sourceSize.width: 36
|
||||
sourceSize.height: 36
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
color: Theme.surfaceContainer
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!root.item || root.item.iconType !== "composite")
|
||||
return "";
|
||||
var iconFull = root.item.iconFull || "";
|
||||
if (iconFull.startsWith("svg+corner:")) {
|
||||
var parts = iconFull.substring(11).split("|");
|
||||
return parts[1] || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
size: 12
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.item?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.item?.subtitle ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
visible: text.length > 0
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightContent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
visible: root.item?.type && root.item.type !== "app"
|
||||
width: typeBadge.implicitWidth + Theme.spacingS * 2
|
||||
height: 20
|
||||
radius: 10
|
||||
color: Theme.surfaceVariantAlpha
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
id: typeBadge
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!root.item)
|
||||
return "";
|
||||
switch (root.item.type) {
|
||||
case "calculator":
|
||||
return I18n.tr("Calc");
|
||||
case "plugin":
|
||||
return I18n.tr("Plugin");
|
||||
case "file":
|
||||
return I18n.tr("File");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall - 2
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var scenePos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.rightClicked(scenePos.x, scenePos.y);
|
||||
} else {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (root.controller) {
|
||||
root.controller.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
489
quickshell/Modals/DankLauncherV2/ResultsList.qml
Normal file
489
quickshell/Modals/DankLauncherV2/ResultsList.qml
Normal file
@@ -0,0 +1,489 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var controller: null
|
||||
property int gridColumns: controller?.gridColumns ?? 4
|
||||
|
||||
signal itemRightClicked(int index, var item, real mouseX, real mouseY)
|
||||
|
||||
function resetScroll() {
|
||||
mainFlickable.contentY = 0;
|
||||
}
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || !controller?.flatModel || index >= controller.flatModel.length)
|
||||
return;
|
||||
var entry = controller.flatModel[index];
|
||||
if (!entry || entry.isHeader)
|
||||
return;
|
||||
scrollItemIntoView(index, entry.sectionId);
|
||||
}
|
||||
|
||||
function scrollItemIntoView(flatIndex, sectionId) {
|
||||
var sections = controller?.sections ?? [];
|
||||
var sectionIndex = -1;
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].id === sectionId) {
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sectionIndex < 0)
|
||||
return;
|
||||
var itemInSection = 0;
|
||||
var foundSection = false;
|
||||
for (var i = 0; i < controller.flatModel.length && i < flatIndex; i++) {
|
||||
var e = controller.flatModel[i];
|
||||
if (e.isHeader && e.section?.id === sectionId)
|
||||
foundSection = true;
|
||||
else if (foundSection && !e.isHeader && e.sectionId === sectionId)
|
||||
itemInSection++;
|
||||
}
|
||||
|
||||
var mode = controller.getSectionViewMode(sectionId);
|
||||
var sectionY = 0;
|
||||
for (var i = 0; i < sectionIndex; i++) {
|
||||
sectionY += getSectionHeight(sections[i]);
|
||||
}
|
||||
|
||||
var itemY, itemHeight;
|
||||
if (mode === "list") {
|
||||
itemY = itemInSection * 52;
|
||||
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 cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
var row = Math.floor(itemInSection / cols);
|
||||
itemY = row * cellHeight;
|
||||
itemHeight = cellHeight;
|
||||
}
|
||||
|
||||
var targetY = sectionY + 32 + itemY;
|
||||
var targetBottom = targetY + itemHeight;
|
||||
var stickyHeight = mainFlickable.contentY > 0 ? 32 : 0;
|
||||
|
||||
var shadowPadding = 24;
|
||||
if (targetY < mainFlickable.contentY + stickyHeight) {
|
||||
mainFlickable.contentY = Math.max(0, targetY - 32);
|
||||
} else if (targetBottom > mainFlickable.contentY + mainFlickable.height - shadowPadding) {
|
||||
mainFlickable.contentY = Math.min(mainFlickable.contentHeight - mainFlickable.height, targetBottom - mainFlickable.height + shadowPadding);
|
||||
}
|
||||
}
|
||||
|
||||
function getSectionHeight(section) {
|
||||
var mode = controller?.getSectionViewMode(section.id) ?? "list";
|
||||
if (section.collapsed)
|
||||
return 32;
|
||||
|
||||
if (mode === "list") {
|
||||
return 32 + (section.items?.length ?? 0) * 52;
|
||||
} 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 cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
return 32 + rows * cellHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedItemPosition() {
|
||||
var fallback = mapToItem(null, width / 2, height / 2);
|
||||
if (!controller?.flatModel || controller.selectedFlatIndex < 0)
|
||||
return fallback;
|
||||
|
||||
var entry = controller.flatModel[controller.selectedFlatIndex];
|
||||
if (!entry || entry.isHeader)
|
||||
return fallback;
|
||||
|
||||
var sections = controller.sections;
|
||||
var sectionIndex = -1;
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].id === entry.sectionId) {
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sectionIndex < 0)
|
||||
return fallback;
|
||||
|
||||
var sectionY = 0;
|
||||
for (var i = 0; i < sectionIndex; i++) {
|
||||
sectionY += getSectionHeight(sections[i]);
|
||||
}
|
||||
|
||||
var mode = controller.getSectionViewMode(entry.sectionId);
|
||||
var itemInSection = entry.indexInSection || 0;
|
||||
|
||||
var itemY, itemX, itemH;
|
||||
if (mode === "list") {
|
||||
itemY = sectionY + 32 + itemInSection * 52;
|
||||
itemX = width / 2;
|
||||
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 cellHeight = mode === "tile" ? cellWidth * 0.75 : cellWidth + 24;
|
||||
var row = Math.floor(itemInSection / cols);
|
||||
var col = itemInSection % cols;
|
||||
itemY = sectionY + 32 + row * cellHeight;
|
||||
itemX = col * cellWidth + cellWidth / 2;
|
||||
itemH = cellHeight;
|
||||
}
|
||||
|
||||
var visualY = itemY - mainFlickable.contentY + itemH / 2;
|
||||
var clampedY = Math.max(40, Math.min(height - 40, visualY));
|
||||
return mapToItem(null, itemX, clampedY);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.controller
|
||||
function onSelectedFlatIndexChanged() {
|
||||
if (root.controller?.keyboardNavigationActive) {
|
||||
Qt.callLater(() => root.ensureVisible(root.controller.selectedFlatIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
id: mainFlickable
|
||||
anchors.fill: parent
|
||||
contentWidth: width
|
||||
contentHeight: sectionsColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: sectionsColumn
|
||||
width: parent.width
|
||||
|
||||
Repeater {
|
||||
model: root.controller?.sections ?? []
|
||||
|
||||
Column {
|
||||
id: sectionDelegate
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property int versionTrigger: root.controller?.viewModeVersion ?? 0
|
||||
readonly property string sectionId: modelData?.id ?? ""
|
||||
readonly property string currentViewMode: {
|
||||
void (versionTrigger);
|
||||
return root.controller?.getSectionViewMode(sectionId) ?? "list";
|
||||
}
|
||||
readonly property bool isGridMode: currentViewMode === "grid" || currentViewMode === "tile"
|
||||
readonly property bool isCollapsed: modelData?.collapsed ?? false
|
||||
|
||||
width: sectionsColumn.width
|
||||
|
||||
SectionHeader {
|
||||
width: parent.width
|
||||
height: 32
|
||||
section: sectionDelegate.modelData
|
||||
controller: root.controller
|
||||
viewMode: sectionDelegate.currentViewMode
|
||||
canChangeViewMode: root.controller?.canChangeSectionViewMode(sectionDelegate.sectionId) ?? false
|
||||
canCollapse: root.controller?.canCollapseSection(sectionDelegate.sectionId) ?? false
|
||||
}
|
||||
|
||||
Column {
|
||||
id: listContent
|
||||
width: parent.width
|
||||
visible: !sectionDelegate.isGridMode && !sectionDelegate.isCollapsed
|
||||
|
||||
Repeater {
|
||||
model: sectionDelegate.isGridMode || sectionDelegate.isCollapsed ? [] : (sectionDelegate.modelData?.items ?? [])
|
||||
|
||||
ResultItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: listContent.width
|
||||
height: 52
|
||||
item: modelData
|
||||
isSelected: getFlatIndex() === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: getFlatIndex()
|
||||
|
||||
function getFlatIndex() {
|
||||
if (!sectionDelegate?.sectionId)
|
||||
return -1;
|
||||
var flatIdx = 0;
|
||||
var sections = root.controller?.sections ?? [];
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
flatIdx++;
|
||||
if (sections[i].id === sectionDelegate.sectionId)
|
||||
return flatIdx + index;
|
||||
if (!sections[i].collapsed)
|
||||
flatIdx += sections[i].items?.length ?? 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (root.controller) {
|
||||
root.controller.executeItem(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(getFlatIndex(), modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: gridContent
|
||||
width: parent.width
|
||||
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 cellHeight: sectionDelegate.currentViewMode === "tile" ? cellWidth * 0.75 : cellWidth + 24
|
||||
|
||||
Repeater {
|
||||
model: sectionDelegate.isGridMode && !sectionDelegate.isCollapsed ? (sectionDelegate.modelData?.items ?? []) : []
|
||||
|
||||
Item {
|
||||
id: gridDelegateItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: gridContent.cellWidth
|
||||
height: gridContent.cellHeight
|
||||
|
||||
function getFlatIndex() {
|
||||
if (!sectionDelegate?.sectionId)
|
||||
return -1;
|
||||
var flatIdx = 0;
|
||||
var sections = root.controller?.sections ?? [];
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
flatIdx++;
|
||||
if (sections[i].id === sectionDelegate.sectionId)
|
||||
return flatIdx + index;
|
||||
if (!sections[i].collapsed)
|
||||
flatIdx += sections[i].items?.length ?? 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int cachedFlatIndex: getFlatIndex()
|
||||
|
||||
GridItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: sectionDelegate.currentViewMode === "grid"
|
||||
item: gridDelegateItem.modelData
|
||||
isSelected: gridDelegateItem.cachedFlatIndex === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridDelegateItem.cachedFlatIndex
|
||||
|
||||
onClicked: {
|
||||
if (root.controller) {
|
||||
root.controller.executeItem(gridDelegateItem.modelData);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridDelegateItem.cachedFlatIndex, gridDelegateItem.modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
TileItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: sectionDelegate.currentViewMode === "tile"
|
||||
item: gridDelegateItem.modelData
|
||||
isSelected: gridDelegateItem.cachedFlatIndex === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridDelegateItem.cachedFlatIndex
|
||||
|
||||
onClicked: {
|
||||
if (root.controller) {
|
||||
root.controller.executeItem(gridDelegateItem.modelData);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridDelegateItem.cachedFlatIndex, gridDelegateItem.modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bottomShadow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 24
|
||||
z: 100
|
||||
visible: {
|
||||
if (mainFlickable.contentHeight <= mainFlickable.height)
|
||||
return false;
|
||||
var atBottom = mainFlickable.contentY >= mainFlickable.contentHeight - mainFlickable.height - 5;
|
||||
if (atBottom)
|
||||
return false;
|
||||
|
||||
var flatModel = root.controller?.flatModel;
|
||||
if (!flatModel || flatModel.length === 0)
|
||||
return false;
|
||||
var lastItemIdx = -1;
|
||||
for (var i = flatModel.length - 1; i >= 0; i--) {
|
||||
if (!flatModel[i].isHeader) {
|
||||
lastItemIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastItemIdx >= 0 && root.controller?.selectedFlatIndex === lastItemIdx)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: "transparent"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: stickyHeader
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: 32
|
||||
z: 101
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
visible: stickyHeaderSection !== null
|
||||
|
||||
readonly property int versionTrigger: root.controller?.viewModeVersion ?? 0
|
||||
|
||||
readonly property var stickyHeaderSection: {
|
||||
if (!root.controller?.sections || root.controller.sections.length === 0)
|
||||
return null;
|
||||
var sections = root.controller.sections;
|
||||
if (sections.length === 0)
|
||||
return null;
|
||||
|
||||
var scrollY = mainFlickable.contentY;
|
||||
if (scrollY <= 0)
|
||||
return null;
|
||||
|
||||
var y = 0;
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var section = sections[i];
|
||||
var sectionHeight = root.getSectionHeight(section);
|
||||
if (scrollY < y + sectionHeight)
|
||||
return section;
|
||||
y += sectionHeight;
|
||||
}
|
||||
return sections[sections.length - 1];
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
width: parent.width
|
||||
section: stickyHeader.stickyHeaderSection
|
||||
controller: root.controller
|
||||
viewMode: {
|
||||
void (stickyHeader.versionTrigger);
|
||||
return root.controller?.getSectionViewMode(stickyHeader.stickyHeaderSection?.id) ?? "list";
|
||||
}
|
||||
canChangeViewMode: {
|
||||
void (stickyHeader.versionTrigger);
|
||||
return root.controller?.canChangeSectionViewMode(stickyHeader.stickyHeaderSection?.id) ?? false;
|
||||
}
|
||||
canCollapse: {
|
||||
void (stickyHeader.versionTrigger);
|
||||
return root.controller?.canCollapseSection(stickyHeader.stickyHeaderSection?.id) ?? false;
|
||||
}
|
||||
isSticky: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.controller?.sections || root.controller.sections.length === 0
|
||||
width: emptyColumn.implicitWidth
|
||||
height: emptyColumn.implicitHeight
|
||||
|
||||
Column {
|
||||
id: emptyColumn
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: getEmptyIcon()
|
||||
size: 48
|
||||
color: Theme.outlineButton
|
||||
|
||||
function getEmptyIcon() {
|
||||
var mode = root.controller?.searchMode ?? "all";
|
||||
if (mode === "files")
|
||||
return "folder_open";
|
||||
if (mode === "plugins")
|
||||
return "extension";
|
||||
if (mode === "apps")
|
||||
return "apps";
|
||||
if (root.controller?.searchQuery?.length > 0)
|
||||
return "search_off";
|
||||
return "search";
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: getEmptyText()
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
function getEmptyText() {
|
||||
var mode = root.controller?.searchMode ?? "all";
|
||||
var hasQuery = root.controller?.searchQuery?.length > 0;
|
||||
|
||||
if (mode === "files") {
|
||||
if (!DSearchService.dsearchAvailable)
|
||||
return I18n.tr("File search requires dsearch\nInstall from github.com/morelazers/dsearch");
|
||||
if (!hasQuery)
|
||||
return I18n.tr("Type to search files");
|
||||
if (root.controller.searchQuery.length < 2)
|
||||
return I18n.tr("Type at least 2 characters");
|
||||
return I18n.tr("No files found");
|
||||
}
|
||||
if (mode === "plugins") {
|
||||
if (!hasQuery)
|
||||
return I18n.tr("Browse or search plugins");
|
||||
return I18n.tr("No plugin results");
|
||||
}
|
||||
if (mode === "apps") {
|
||||
if (!hasQuery)
|
||||
return I18n.tr("Type to search apps");
|
||||
return I18n.tr("No apps found");
|
||||
}
|
||||
if (hasQuery)
|
||||
return I18n.tr("No results found");
|
||||
return I18n.tr("Type to search");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
245
quickshell/Modals/DankLauncherV2/Scorer.js
Normal file
245
quickshell/Modals/DankLauncherV2/Scorer.js
Normal file
@@ -0,0 +1,245 @@
|
||||
.pragma library
|
||||
|
||||
const Weights = {
|
||||
exactMatch: 10000,
|
||||
prefixMatch: 5000,
|
||||
wordBoundary: 1000,
|
||||
substring: 500,
|
||||
fuzzy: 100,
|
||||
frecency: 2000,
|
||||
typeBonus: {
|
||||
app: 1000,
|
||||
plugin: 900,
|
||||
file: 800,
|
||||
action: 600
|
||||
}
|
||||
}
|
||||
|
||||
function tokenize(text) {
|
||||
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(function(w) { return w.length > 0 })
|
||||
}
|
||||
|
||||
function hasWordBoundaryMatch(text, query) {
|
||||
var textWords = tokenize(text)
|
||||
var queryWords = tokenize(query)
|
||||
|
||||
if (queryWords.length === 0) return false
|
||||
if (queryWords.length > textWords.length) return false
|
||||
|
||||
for (var i = 0; i <= textWords.length - queryWords.length; i++) {
|
||||
var allMatch = true
|
||||
for (var j = 0; j < queryWords.length; j++) {
|
||||
if (!textWords[i + j].startsWith(queryWords[j])) {
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allMatch) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function levenshteinDistance(s1, s2) {
|
||||
var len1 = s1.length
|
||||
var len2 = s2.length
|
||||
var matrix = []
|
||||
|
||||
for (var i = 0; i <= len1; i++) {
|
||||
matrix[i] = [i]
|
||||
}
|
||||
for (var j = 0; j <= len2; j++) {
|
||||
matrix[0][j] = j
|
||||
}
|
||||
|
||||
for (var i = 1; i <= len1; i++) {
|
||||
for (var j = 1; j <= len2; j++) {
|
||||
var cost = s1[i - 1] === s2[j - 1] ? 0 : 1
|
||||
matrix[i][j] = Math.min(
|
||||
matrix[i - 1][j] + 1,
|
||||
matrix[i][j - 1] + 1,
|
||||
matrix[i - 1][j - 1] + cost
|
||||
)
|
||||
}
|
||||
}
|
||||
return matrix[len1][len2]
|
||||
}
|
||||
|
||||
function fuzzyScore(text, query) {
|
||||
var maxDistance = query.length === 3 ? 1 : query.length <= 6 ? 2 : 3
|
||||
var bestScore = 0
|
||||
|
||||
if (Math.abs(text.length - query.length) <= maxDistance) {
|
||||
var distance = levenshteinDistance(text, query)
|
||||
if (distance <= maxDistance) {
|
||||
var maxLen = Math.max(text.length, query.length)
|
||||
bestScore = 1 - (distance / maxLen)
|
||||
}
|
||||
}
|
||||
|
||||
var words = tokenize(text)
|
||||
for (var i = 0; i < words.length && bestScore < 0.8; i++) {
|
||||
if (Math.abs(words[i].length - query.length) > maxDistance) continue
|
||||
var wordDistance = levenshteinDistance(words[i], query)
|
||||
if (wordDistance <= maxDistance) {
|
||||
var wordMaxLen = Math.max(words[i].length, query.length)
|
||||
var score = 1 - (wordDistance / wordMaxLen)
|
||||
bestScore = Math.max(bestScore, score)
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore
|
||||
}
|
||||
|
||||
function getTimeBucketWeight(daysSinceUsed) {
|
||||
for (var i = 0; i < TimeBuckets.length; i++) {
|
||||
if (daysSinceUsed <= TimeBuckets[i].maxDays) {
|
||||
return TimeBuckets[i].weight
|
||||
}
|
||||
}
|
||||
return 10
|
||||
}
|
||||
|
||||
function calculateTextScore(name, query) {
|
||||
if (name === query) return Weights.exactMatch
|
||||
if (name.startsWith(query)) return Weights.prefixMatch
|
||||
if (name.includes(query)) return Weights.substring
|
||||
if (hasWordBoundaryMatch(name, query)) return Weights.wordBoundary
|
||||
|
||||
if (query.length >= 3) {
|
||||
var fs = fuzzyScore(name, query)
|
||||
if (fs > 0) return fs * Weights.fuzzy
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function score(item, query, frecencyData) {
|
||||
var typeBonus = Weights.typeBonus[item.type] || 0
|
||||
|
||||
if (!query || query.length === 0) {
|
||||
var usageCount = frecencyData ? frecencyData.usageCount : 0
|
||||
return typeBonus + (usageCount * 100)
|
||||
}
|
||||
|
||||
var name = (item.name || "").toLowerCase()
|
||||
var q = query.toLowerCase()
|
||||
|
||||
var textScore = calculateTextScore(name, q)
|
||||
|
||||
if (textScore === 0 && item.subtitle) {
|
||||
var subtitleScore = calculateTextScore(item.subtitle.toLowerCase(), q)
|
||||
textScore = subtitleScore * 0.5
|
||||
}
|
||||
|
||||
if (textScore === 0 && item.keywords) {
|
||||
for (var i = 0; i < item.keywords.length; i++) {
|
||||
var keywordScore = calculateTextScore(item.keywords[i].toLowerCase(), q)
|
||||
if (keywordScore > 0) {
|
||||
textScore = keywordScore * 0.3
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (textScore === 0) return 0
|
||||
|
||||
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 10, Weights.frecency) : 0
|
||||
|
||||
return textScore + usageBonus + typeBonus
|
||||
}
|
||||
|
||||
function scoreItems(items, query, getFrecencyFn) {
|
||||
var scored = []
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i]
|
||||
var frecencyData = getFrecencyFn ? getFrecencyFn(item) : null
|
||||
var itemScore = score(item, query, frecencyData)
|
||||
|
||||
if (itemScore > 0 || !query || query.length === 0) {
|
||||
scored.push({
|
||||
item: item,
|
||||
score: itemScore
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
scored.sort(function(a, b) {
|
||||
return b.score - a.score
|
||||
})
|
||||
|
||||
return scored
|
||||
}
|
||||
|
||||
function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSection) {
|
||||
var sections = {}
|
||||
var result = []
|
||||
var limit = maxPerSection || 50
|
||||
|
||||
for (var i = 0; i < sectionOrder.length; i++) {
|
||||
var sectionId = sectionOrder[i].id
|
||||
sections[sectionId] = {
|
||||
id: sectionId,
|
||||
title: sectionOrder[i].title,
|
||||
icon: sectionOrder[i].icon,
|
||||
priority: sectionOrder[i].priority,
|
||||
items: [],
|
||||
collapsed: false
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < scoredItems.length; i++) {
|
||||
var scoredItem = scoredItems[i]
|
||||
var item = scoredItem.item
|
||||
var sectionId = item.section || "apps"
|
||||
|
||||
if (sections[sectionId] && sections[sectionId].items.length < limit) {
|
||||
sections[sectionId].items.push(item)
|
||||
} else if (sections["apps"] && sections["apps"].items.length < limit) {
|
||||
sections["apps"].items.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < sectionOrder.length; i++) {
|
||||
var section = sections[sectionOrder[i].id]
|
||||
if (section && section.items.length > 0) {
|
||||
if (sortAlphabetically && section.id === "apps") {
|
||||
section.items.sort(function(a, b) {
|
||||
return (a.name || "").localeCompare(b.name || "")
|
||||
})
|
||||
}
|
||||
result.push(section)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function flattenSections(sections) {
|
||||
var flat = []
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var section = sections[i]
|
||||
|
||||
flat.push({
|
||||
isHeader: true,
|
||||
section: section,
|
||||
sectionId: section.id,
|
||||
sectionIndex: i
|
||||
})
|
||||
|
||||
if (!section.collapsed) {
|
||||
for (var j = 0; j < section.items.length; j++) {
|
||||
flat.push({
|
||||
isHeader: false,
|
||||
item: section.items[j],
|
||||
sectionId: section.id,
|
||||
sectionIndex: i,
|
||||
indexInSection: j
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flat
|
||||
}
|
||||
114
quickshell/Modals/DankLauncherV2/Section.qml
Normal file
114
quickshell/Modals/DankLauncherV2/Section.qml
Normal file
@@ -0,0 +1,114 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var section: null
|
||||
property var controller: null
|
||||
property string viewMode: "list"
|
||||
property int gridColumns: 4
|
||||
property int startIndex: 0
|
||||
|
||||
signal itemClicked(int flatIndex)
|
||||
signal itemRightClicked(int flatIndex, var item, real mouseX, real mouseY)
|
||||
|
||||
height: headerItem.height + (section?.collapsed ? 0 : contentLoader.height + Theme.spacingXS)
|
||||
width: parent?.width ?? 200
|
||||
|
||||
SectionHeader {
|
||||
id: headerItem
|
||||
width: parent.width
|
||||
section: root.section
|
||||
controller: root.controller
|
||||
viewMode: root.viewMode
|
||||
canChangeViewMode: root.controller?.canChangeSectionViewMode(root.section?.id) ?? true
|
||||
|
||||
onViewModeToggled: {
|
||||
if (root.controller && root.section) {
|
||||
var newMode = root.viewMode === "list" ? "grid" : "list";
|
||||
root.controller.setSectionViewMode(root.section.id, newMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.top: headerItem.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
active: !root.section?.collapsed
|
||||
visible: active
|
||||
|
||||
sourceComponent: root.viewMode === "grid" ? gridComponent : listComponent
|
||||
|
||||
Component {
|
||||
id: listComponent
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
width: contentLoader.width
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.section?.items ?? []
|
||||
objectProp: "id"
|
||||
}
|
||||
|
||||
ResultItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent?.width ?? 200
|
||||
item: modelData
|
||||
isSelected: (root.startIndex + index) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: root.startIndex + index
|
||||
|
||||
onClicked: root.itemClicked(root.startIndex + index)
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(root.startIndex + index, modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: gridComponent
|
||||
|
||||
Flow {
|
||||
width: contentLoader.width
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.section?.items ?? []
|
||||
objectProp: "id"
|
||||
}
|
||||
|
||||
GridItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: Math.floor((contentLoader.width - (root.gridColumns - 1) * 4) / root.gridColumns)
|
||||
height: width + 24
|
||||
item: modelData
|
||||
isSelected: (root.startIndex + index) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: root.startIndex + index
|
||||
|
||||
onClicked: root.itemClicked(root.startIndex + index)
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(root.startIndex + index, modelData, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
quickshell/Modals/DankLauncherV2/SectionHeader.qml
Normal file
169
quickshell/Modals/DankLauncherV2/SectionHeader.qml
Normal file
@@ -0,0 +1,169 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var section: null
|
||||
property var controller: null
|
||||
property string viewMode: "list"
|
||||
property bool canChangeViewMode: true
|
||||
property bool canCollapse: true
|
||||
property bool isSticky: false
|
||||
|
||||
signal viewModeToggled
|
||||
|
||||
width: parent?.width ?? 200
|
||||
height: 32
|
||||
color: isSticky ? "transparent" : (hoverArea.containsMouse ? Theme.surfaceHover : "transparent")
|
||||
radius: Theme.cornerRadius / 2
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
Row {
|
||||
id: leftContent
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: root.section?.icon ?? "folder"
|
||||
size: 16
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.section?.title ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.section?.items?.length ?? 0
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outlineButton
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightContent
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: viewModeRow
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
visible: root.canChangeViewMode && !root.section?.collapsed
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
mode: "list",
|
||||
icon: "view_list"
|
||||
},
|
||||
{
|
||||
mode: "grid",
|
||||
icon: "grid_view"
|
||||
},
|
||||
{
|
||||
mode: "tile",
|
||||
icon: "view_module"
|
||||
}
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 4
|
||||
color: root.viewMode === modelData.mode ? Theme.primaryHover : modeArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: parent.modelData.icon
|
||||
size: 14
|
||||
color: root.viewMode === parent.modelData.mode ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: modeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.viewMode !== parent.modelData.mode && root.controller && root.section) {
|
||||
root.controller.setSectionViewMode(root.section.id, parent.modelData.mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: collapseButton
|
||||
width: root.canCollapse ? 24 : 0
|
||||
height: 24
|
||||
visible: root.canCollapse
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.section?.collapsed ? "expand_more" : "expand_less"
|
||||
size: 16
|
||||
color: collapseArea.containsMouse ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: collapseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.controller && root.section) {
|
||||
root.controller.toggleSection(root.section.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: rightContent.width + Theme.spacingS
|
||||
cursorShape: root.canCollapse ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: root.canCollapse
|
||||
onClicked: {
|
||||
if (root.canCollapse && root.controller && root.section) {
|
||||
root.controller.toggleSection(root.section.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
color: Theme.outlineMedium
|
||||
visible: root.isSticky
|
||||
}
|
||||
}
|
||||
147
quickshell/Modals/DankLauncherV2/TileItem.qml
Normal file
147
quickshell/Modals/DankLauncherV2/TileItem.qml
Normal file
@@ -0,0 +1,147 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var item: null
|
||||
property bool isSelected: false
|
||||
property bool isHovered: itemArea.containsMouse
|
||||
property var controller: null
|
||||
property int flatIndex: -1
|
||||
|
||||
signal clicked
|
||||
signal rightClicked(real mouseX, real mouseY)
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
|
||||
border.width: isSelected ? 2 : 0
|
||||
border.color: Theme.primary
|
||||
|
||||
readonly property string imageSource: {
|
||||
if (!item?.data)
|
||||
return "";
|
||||
var data = item.data;
|
||||
if (data.imageUrl)
|
||||
return data.imageUrl;
|
||||
if (data.imagePath)
|
||||
return data.imagePath;
|
||||
if (data.path && isImageFile(data.path))
|
||||
return data.path;
|
||||
return "";
|
||||
}
|
||||
|
||||
readonly property bool useImage: imageSource.length > 0
|
||||
readonly property bool useIconProvider: !useImage && item?.iconType === "image"
|
||||
|
||||
function isImageFile(path) {
|
||||
if (!path)
|
||||
return false;
|
||||
var ext = path.split('.').pop().toLowerCase();
|
||||
return ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"].indexOf(ext) >= 0;
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
|
||||
Rectangle {
|
||||
id: imageContainer
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: Theme.surfaceContainerHigh
|
||||
clip: true
|
||||
|
||||
CachingImage {
|
||||
anchors.fill: parent
|
||||
visible: root.useImage
|
||||
imagePath: root.imageSource
|
||||
maxCacheSize: 256
|
||||
}
|
||||
|
||||
Image {
|
||||
id: iconImage
|
||||
anchors.fill: parent
|
||||
visible: root.useIconProvider
|
||||
source: root.useIconProvider ? "image://icon/" + (root.item?.icon || "application-x-executable") : ""
|
||||
sourceSize.width: parent.width * 2
|
||||
sourceSize.height: parent.height * 2
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
cache: false
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.useImage && !root.useIconProvider
|
||||
name: root.item?.icon ?? "image"
|
||||
size: Math.min(parent.width, parent.height) * 0.4
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: labelText.implicitHeight + Theme.spacingS * 2
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.85)
|
||||
visible: root.item?.name?.length > 0
|
||||
|
||||
StyledText {
|
||||
id: labelText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXS
|
||||
text: root.item?.name ?? ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingXS
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: Theme.primary
|
||||
visible: root.isSelected
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "check"
|
||||
size: 14
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var scenePos = mapToItem(null, mouse.x, mouse.y);
|
||||
root.rightClicked(scenePos.x, scenePos.y);
|
||||
return;
|
||||
}
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (root.controller)
|
||||
root.controller.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,14 @@ BasePill {
|
||||
readonly property bool hasUpdates: SystemUpdateService.updateCount > 0
|
||||
readonly property bool isChecking: SystemUpdateService.isChecking
|
||||
|
||||
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
|
||||
width: (SettingsData.updaterHideWidget && !hasUpdates) ? 0 : (18 + horizontalPadding * 2)
|
||||
|
||||
Ref {
|
||||
service: SystemUpdateService
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : updaterIcon.implicitWidth
|
||||
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
|
||||
implicitWidth: root.isVerticalOrientation ? root.widgetThickness : updaterIcon.implicitWidth
|
||||
implicitHeight: root.widgetThickness
|
||||
|
||||
DankIcon {
|
||||
id: statusIcon
|
||||
|
||||
@@ -73,6 +73,9 @@ DankPopout {
|
||||
root.close();
|
||||
};
|
||||
}
|
||||
if (item && "parentPopout" in item) {
|
||||
item.parentPopout = root;
|
||||
}
|
||||
if (item) {
|
||||
root.contentHeight = Qt.binding(() => item.implicitHeight + Theme.spacingS * 2);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ Column {
|
||||
property string detailsText: ""
|
||||
property bool showCloseButton: false
|
||||
property var closePopout: null
|
||||
property var parentPopout: null
|
||||
property alias headerActions: headerActionsLoader.sourceComponent
|
||||
|
||||
readonly property int headerHeight: popoutHeader.visible ? popoutHeader.height : 0
|
||||
|
||||
@@ -318,7 +318,7 @@ Popup {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: modelData.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: modelData.enabled
|
||||
enabled: modelData.enabled ?? false
|
||||
onEntered: {
|
||||
keyboardNavigation = false;
|
||||
selectedIndex = index;
|
||||
|
||||
@@ -353,6 +353,116 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "tune"
|
||||
title: I18n.tr("Appearance", "launcher appearance settings")
|
||||
settingKey: "dankLauncherV2Appearance"
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Size", "launcher size option")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: sizeGroup.implicitHeight
|
||||
clip: true
|
||||
|
||||
DankButtonGroup {
|
||||
id: sizeGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
buttonPadding: parent.width < 400 ? Theme.spacingS : Theme.spacingL
|
||||
minButtonWidth: parent.width < 400 ? 60 : 80
|
||||
textSize: parent.width < 400 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||
model: [I18n.tr("Compact", "compact launcher size"), I18n.tr("Medium", "medium launcher size"), I18n.tr("Large", "large launcher size")]
|
||||
currentIndex: SettingsData.dankLauncherV2Size === "compact" ? 0 : SettingsData.dankLauncherV2Size === "large" ? 2 : 1
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.set("dankLauncherV2Size", index === 0 ? "compact" : index === 2 ? "large" : "medium");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "dankLauncherV2ShowFooter"
|
||||
tags: ["launcher", "footer", "hints", "shortcuts"]
|
||||
text: I18n.tr("Show Footer", "launcher footer visibility")
|
||||
description: I18n.tr("Show mode tabs and keyboard hints at the bottom.", "launcher footer description")
|
||||
checked: SettingsData.dankLauncherV2ShowFooter
|
||||
onToggled: checked => SettingsData.set("dankLauncherV2ShowFooter", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "dankLauncherV2BorderEnabled"
|
||||
tags: ["launcher", "border", "outline"]
|
||||
text: I18n.tr("Border", "launcher border option")
|
||||
checked: SettingsData.dankLauncherV2BorderEnabled
|
||||
onToggled: checked => SettingsData.set("dankLauncherV2BorderEnabled", checked)
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: SettingsData.dankLauncherV2BorderEnabled
|
||||
|
||||
SettingsSliderRow {
|
||||
settingKey: "dankLauncherV2BorderThickness"
|
||||
tags: ["launcher", "border", "thickness"]
|
||||
text: I18n.tr("Thickness", "border thickness")
|
||||
minimum: 1
|
||||
maximum: 6
|
||||
value: SettingsData.dankLauncherV2BorderThickness
|
||||
defaultValue: 2
|
||||
unit: "px"
|
||||
onSliderValueChanged: newValue => SettingsData.set("dankLauncherV2BorderThickness", newValue)
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Color", "border color")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: borderColorGroup.implicitHeight
|
||||
clip: true
|
||||
|
||||
DankButtonGroup {
|
||||
id: borderColorGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
buttonPadding: parent.width < 400 ? Theme.spacingS : Theme.spacingL
|
||||
minButtonWidth: parent.width < 400 ? 50 : 70
|
||||
textSize: parent.width < 400 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||
model: [I18n.tr("Primary", "primary color"), I18n.tr("Secondary", "secondary color"), I18n.tr("Outline", "outline color"), I18n.tr("Text", "text color")]
|
||||
currentIndex: SettingsData.dankLauncherV2BorderColor === "secondary" ? 1 : SettingsData.dankLauncherV2BorderColor === "outline" ? 2 : SettingsData.dankLauncherV2BorderColor === "surfaceText" ? 3 : 0
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.set("dankLauncherV2BorderColor", index === 1 ? "secondary" : index === 2 ? "outline" : index === 3 ? "surfaceText" : "primary");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "open_in_new"
|
||||
|
||||
@@ -60,6 +60,11 @@ Item {
|
||||
currentIndex: 0
|
||||
selectionMode: "single"
|
||||
checkEnabled: false
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +328,7 @@ Item {
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
currentIndex = index;
|
||||
if (powerCategory.currentIndex === 0) {
|
||||
SettingsData.set("acSuspendBehavior", index);
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,7 @@ import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modals.DankLauncherV2
|
||||
import qs.Services
|
||||
|
||||
Scope {
|
||||
@@ -67,229 +67,215 @@ Scope {
|
||||
hideSpotlight();
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: niriOverlayLoader
|
||||
active: overlayActive || isClosing
|
||||
asynchronous: false
|
||||
Variants {
|
||||
id: overlayVariants
|
||||
model: Quickshell.screens
|
||||
|
||||
sourceComponent: Variants {
|
||||
id: overlayVariants
|
||||
model: Quickshell.screens
|
||||
PanelWindow {
|
||||
id: overlayWindow
|
||||
required property var modelData
|
||||
|
||||
PanelWindow {
|
||||
id: overlayWindow
|
||||
required property var modelData
|
||||
readonly property real dpr: CompositorService.getScreenScale(screen)
|
||||
readonly property bool isActiveScreen: screen.name === NiriService.currentOutput
|
||||
readonly property bool shouldShowSpotlight: niriOverviewScope.searchActive && screen.name === niriOverviewScope.searchActiveScreen && !niriOverviewScope.isClosing
|
||||
readonly property bool isSpotlightScreen: screen.name === niriOverviewScope.searchActiveScreen
|
||||
readonly property bool overlayVisible: NiriService.inOverview || niriOverviewScope.isClosing
|
||||
property bool hasActivePopout: !!PopoutManager.currentPopoutsByScreen[screen.name]
|
||||
property bool hasActiveModal: !!ModalManager.currentModalsByScreen[screen.name]
|
||||
|
||||
readonly property real dpr: CompositorService.getScreenScale(screen)
|
||||
readonly property bool isActiveScreen: screen.name === NiriService.currentOutput
|
||||
readonly property bool shouldShowSpotlight: niriOverviewScope.searchActive && screen.name === niriOverviewScope.searchActiveScreen && !niriOverviewScope.isClosing
|
||||
readonly property bool isSpotlightScreen: screen.name === niriOverviewScope.searchActiveScreen
|
||||
property bool hasActivePopout: !!PopoutManager.currentPopoutsByScreen[screen.name]
|
||||
property bool hasActiveModal: !!ModalManager.currentModalsByScreen[screen.name]
|
||||
|
||||
Connections {
|
||||
target: PopoutManager
|
||||
function onPopoutChanged() {
|
||||
overlayWindow.hasActivePopout = !!PopoutManager.currentPopoutsByScreen[overlayWindow.screen.name];
|
||||
}
|
||||
Connections {
|
||||
target: PopoutManager
|
||||
function onPopoutChanged() {
|
||||
overlayWindow.hasActivePopout = !!PopoutManager.currentPopoutsByScreen[overlayWindow.screen.name];
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onModalChanged() {
|
||||
overlayWindow.hasActiveModal = !!ModalManager.currentModalsByScreen[overlayWindow.screen.name];
|
||||
}
|
||||
Connections {
|
||||
target: ModalManager
|
||||
function onModalChanged() {
|
||||
overlayWindow.hasActiveModal = !!ModalManager.currentModalsByScreen[overlayWindow.screen.name];
|
||||
}
|
||||
}
|
||||
|
||||
screen: modelData
|
||||
visible: NiriService.inOverview || niriOverviewScope.isClosing
|
||||
color: "transparent"
|
||||
screen: modelData
|
||||
visible: true
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:niri-overview-spotlight"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!NiriService.inOverview)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (!isActiveScreen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (niriOverviewScope.releaseKeyboard)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (hasActivePopout || hasActiveModal)
|
||||
return WlrKeyboardFocus.None;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
WlrLayershell.namespace: "dms:niri-overview-spotlight"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!NiriService.inOverview)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (!isActiveScreen)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (niriOverviewScope.releaseKeyboard)
|
||||
return WlrKeyboardFocus.None;
|
||||
if (hasActivePopout || hasActiveModal)
|
||||
return WlrKeyboardFocus.None;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: overlayVisible && spotlightContainer.visible ? spotlightContainer : null
|
||||
}
|
||||
|
||||
onShouldShowSpotlightChanged: {
|
||||
if (shouldShowSpotlight) {
|
||||
if (launcherContent?.controller)
|
||||
launcherContent.controller.performSearch();
|
||||
return;
|
||||
}
|
||||
if (!isActiveScreen)
|
||||
return;
|
||||
Qt.callLater(() => keyboardFocusScope.forceActiveFocus());
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: spotlightContainer.visible ? spotlightContainer : null
|
||||
}
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
onShouldShowSpotlightChanged: {
|
||||
if (shouldShowSpotlight) {
|
||||
if (spotlightContent?.appLauncher)
|
||||
spotlightContent.appLauncher.ensureInitialized();
|
||||
FocusScope {
|
||||
id: keyboardFocusScope
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (overlayWindow.shouldShowSpotlight || niriOverviewScope.isClosing)
|
||||
return;
|
||||
}
|
||||
if (!isActiveScreen)
|
||||
return;
|
||||
Qt.callLater(() => keyboardFocusScope.forceActiveFocus());
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: keyboardFocusScope
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (overlayWindow.shouldShowSpotlight || niriOverviewScope.isClosing)
|
||||
return;
|
||||
if ([Qt.Key_Escape, Qt.Key_Return].includes(event.key)) {
|
||||
NiriService.toggleOverview();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Left) {
|
||||
NiriService.moveColumnLeft();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Right) {
|
||||
NiriService.moveColumnRight();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Up) {
|
||||
NiriService.moveWorkspaceUp();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Down) {
|
||||
NiriService.moveWorkspaceDown();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.modifiers & (Qt.ControlModifier | Qt.MetaModifier) || [Qt.Key_Delete, Qt.Key_Backspace].includes(event.key)) {
|
||||
event.accepted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isAutoRepeat || !event.text)
|
||||
return;
|
||||
if (!spotlightContent?.searchField)
|
||||
return;
|
||||
const trimmedText = event.text.trim();
|
||||
spotlightContent.searchField.text = trimmedText;
|
||||
if (spotlightContent.appLauncher) {
|
||||
spotlightContent.appLauncher.searchQuery = trimmedText;
|
||||
}
|
||||
niriOverviewScope.showSpotlight(overlayWindow.screen.name);
|
||||
Qt.callLater(() => spotlightContent.searchField.forceActiveFocus());
|
||||
if ([Qt.Key_Escape, Qt.Key_Return].includes(event.key)) {
|
||||
NiriService.toggleOverview();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Left) {
|
||||
NiriService.moveColumnLeft();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Right) {
|
||||
NiriService.moveColumnRight();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Up) {
|
||||
NiriService.moveWorkspaceUp();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Down) {
|
||||
NiriService.moveWorkspaceDown();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.modifiers & (Qt.ControlModifier | Qt.MetaModifier) || [Qt.Key_Delete, Qt.Key_Backspace].includes(event.key)) {
|
||||
event.accepted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isAutoRepeat || !event.text)
|
||||
return;
|
||||
if (!launcherContent?.searchField)
|
||||
return;
|
||||
const trimmedText = event.text.trim();
|
||||
launcherContent.searchField.text = trimmedText;
|
||||
launcherContent.controller.setSearchQuery(trimmedText);
|
||||
niriOverviewScope.showSpotlight(overlayWindow.screen.name);
|
||||
Qt.callLater(() => launcherContent.searchField.forceActiveFocus());
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: spotlightContainer
|
||||
x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr)
|
||||
y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr)
|
||||
width: Theme.px(500, overlayWindow.dpr)
|
||||
height: Theme.px(600, overlayWindow.dpr)
|
||||
|
||||
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
||||
|
||||
scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96
|
||||
opacity: overlayWindow.shouldShowSpotlight ? 1 : 0
|
||||
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
||||
enabled: overlayWindow.shouldShowSpotlight
|
||||
|
||||
layer.enabled: true
|
||||
layer.smooth: false
|
||||
layer.textureSize: Qt.size(Math.round(width * overlayWindow.dpr), Math.round(height * overlayWindow.dpr))
|
||||
|
||||
Behavior on scale {
|
||||
id: scaleAnimation
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
onRunningChanged: {
|
||||
if (running || !spotlightContainer.animatingOut)
|
||||
return;
|
||||
niriOverviewScope.resetState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: spotlightContainer
|
||||
x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr)
|
||||
y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr)
|
||||
width: Theme.px(500, overlayWindow.dpr)
|
||||
height: Theme.px(600, overlayWindow.dpr)
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96
|
||||
opacity: overlayWindow.shouldShowSpotlight ? 1 : 0
|
||||
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
||||
enabled: overlayWindow.shouldShowSpotlight
|
||||
LauncherContent {
|
||||
id: launcherContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
|
||||
layer.enabled: true
|
||||
layer.smooth: false
|
||||
layer.textureSize: Qt.size(Math.round(width * overlayWindow.dpr), Math.round(height * overlayWindow.dpr))
|
||||
|
||||
Behavior on scale {
|
||||
id: scaleAnimation
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
onRunningChanged: {
|
||||
if (running || !spotlightContainer.animatingOut)
|
||||
return;
|
||||
niriOverviewScope.resetState();
|
||||
property var fakeParentModal: QtObject {
|
||||
property bool spotlightOpen: spotlightContainer.visible
|
||||
property bool isClosing: niriOverviewScope.isClosing
|
||||
function hide() {
|
||||
if (niriOverviewScope.searchActive) {
|
||||
niriOverviewScope.hideAndReleaseKeyboard();
|
||||
return;
|
||||
}
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
Connections {
|
||||
target: launcherContent.searchField
|
||||
function onTextChanged() {
|
||||
if (launcherContent.searchField.text.length > 0 || !niriOverviewScope.searchActive)
|
||||
return;
|
||||
niriOverviewScope.hideSpotlight();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
Component.onCompleted: {
|
||||
parentModal = fakeParentModal;
|
||||
}
|
||||
|
||||
SpotlightContent {
|
||||
id: spotlightContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
usePopupContextMenu: true
|
||||
|
||||
property var fakeParentModal: QtObject {
|
||||
property bool spotlightOpen: spotlightContainer.visible
|
||||
function hide() {
|
||||
if (niriOverviewScope.searchActive) {
|
||||
niriOverviewScope.hideAndReleaseKeyboard();
|
||||
return;
|
||||
}
|
||||
NiriService.toggleOverview();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: spotlightContent.searchField
|
||||
function onTextChanged() {
|
||||
if (spotlightContent.searchField.text.length > 0 || !niriOverviewScope.searchActive)
|
||||
return;
|
||||
niriOverviewScope.hideSpotlight();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
parentModal = fakeParentModal;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: spotlightContent.appLauncher
|
||||
function onAppLaunched() {
|
||||
niriOverviewScope.releaseKeyboard = true;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: spotlightContent.fileSearchController
|
||||
function onFileOpened() {
|
||||
niriOverviewScope.releaseKeyboard = true;
|
||||
}
|
||||
Connections {
|
||||
target: launcherContent.controller
|
||||
function onItemExecuted() {
|
||||
niriOverviewScope.releaseKeyboard = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
quickshell/PLUGINS/LauncherImageExample/LauncherImageExample.qml
Normal file
113
quickshell/PLUGINS/LauncherImageExample/LauncherImageExample.qml
Normal file
@@ -0,0 +1,113 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property var pluginService: null
|
||||
property string trigger: "img"
|
||||
|
||||
signal itemsChanged
|
||||
|
||||
readonly property var images: [
|
||||
{
|
||||
name: "DankDash",
|
||||
imageUrl: "https://danklinux.com/img/dankdash.png",
|
||||
comment: "DankMaterialShell Dashboard"
|
||||
},
|
||||
{
|
||||
name: "Control Center",
|
||||
imageUrl: "https://danklinux.com/img/cc.png",
|
||||
comment: "System Control Center"
|
||||
},
|
||||
{
|
||||
name: "Desktop",
|
||||
imageUrl: "https://danklinux.com/img/desktop.png",
|
||||
comment: "Desktop Environment"
|
||||
},
|
||||
{
|
||||
name: "Search",
|
||||
imageUrl: "https://danklinux.com/img/dsearch.png",
|
||||
comment: "Application Search"
|
||||
},
|
||||
{
|
||||
name: "Theme Registry",
|
||||
imageUrl: "https://danklinux.com/img/blog/v1.2/themeregistry.png",
|
||||
comment: "Theme Registry Browser"
|
||||
},
|
||||
{
|
||||
name: "Monitor Settings",
|
||||
imageUrl: "https://danklinux.com/img/blog/v1.2/monitordark.png",
|
||||
comment: "Display Configuration"
|
||||
}
|
||||
]
|
||||
|
||||
function getItems(query) {
|
||||
const lowerQuery = query ? query.toLowerCase().trim() : "";
|
||||
|
||||
if (lowerQuery.length === 0) {
|
||||
return images.map(img => ({
|
||||
name: img.name,
|
||||
icon: "material:image",
|
||||
comment: img.comment,
|
||||
action: "view:" + img.imageUrl,
|
||||
categories: ["Image Gallery"],
|
||||
imageUrl: img.imageUrl
|
||||
}));
|
||||
}
|
||||
|
||||
return images.filter(img => img.name.toLowerCase().includes(lowerQuery) || img.comment.toLowerCase().includes(lowerQuery)).map(img => ({
|
||||
name: img.name,
|
||||
icon: "material:image",
|
||||
comment: img.comment,
|
||||
action: "view:" + img.imageUrl,
|
||||
categories: ["Image Gallery"],
|
||||
imageUrl: img.imageUrl
|
||||
}));
|
||||
}
|
||||
|
||||
function executeItem(item) {
|
||||
if (!item?.action)
|
||||
return;
|
||||
const actionParts = item.action.split(":");
|
||||
const actionType = actionParts[0];
|
||||
const actionData = actionParts.slice(1).join(":");
|
||||
|
||||
if (actionType === "view") {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("Image Gallery", "Viewing: " + item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getContextMenuActions(item) {
|
||||
if (!item)
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
icon: "open_in_new",
|
||||
text: "Open in Browser",
|
||||
action: () => {
|
||||
const url = item.imageUrl || "";
|
||||
if (url) {
|
||||
Qt.openUrlExternally(url);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: "content_copy",
|
||||
text: "Copy URL",
|
||||
action: () => {
|
||||
const url = item.imageUrl || "";
|
||||
if (url) {
|
||||
Quickshell.execDetached(["dms", "cl", "copy", url]);
|
||||
if (typeof ToastService !== "undefined") {
|
||||
ToastService.showInfo("Copied", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
43
quickshell/PLUGINS/LauncherImageExample/README.md
Normal file
43
quickshell/PLUGINS/LauncherImageExample/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# LauncherImageExample
|
||||
|
||||
Example launcher plugin demonstrating tile mode with URL-based images.
|
||||
|
||||
## Features
|
||||
|
||||
- **Tile Mode**: Uses `viewMode: "tile"` in plugin.json to display results as image tiles
|
||||
- **Enforced View Mode**: Uses `viewModeEnforced: true` to lock the view to tile mode (users cannot change it)
|
||||
- **URL Images**: Demonstrates using `imageUrl` property for remote images
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open the launcher (DankLauncherV2)
|
||||
2. Type `img` to activate the plugin
|
||||
3. Browse DankMaterialShell screenshots in tile view
|
||||
|
||||
## Plugin Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"viewMode": "tile",
|
||||
"viewModeEnforced": true
|
||||
}
|
||||
```
|
||||
|
||||
- `viewMode`: Sets the default view mode ("list", "grid", or "tile")
|
||||
- `viewModeEnforced`: When true, users cannot switch view modes for this plugin
|
||||
|
||||
## Item Data Structure
|
||||
|
||||
To display images in tile mode, set `imageUrl` directly on the item:
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: "Image Title",
|
||||
icon: "material:image",
|
||||
comment: "Image description",
|
||||
categories: ["Category"],
|
||||
imageUrl: "https://example.com/image.png"
|
||||
}
|
||||
```
|
||||
|
||||
The `imageUrl` property supports remote URLs or local files, use `file://` prefix for local files.
|
||||
14
quickshell/PLUGINS/LauncherImageExample/plugin.json
Normal file
14
quickshell/PLUGINS/LauncherImageExample/plugin.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "launcherImageExample",
|
||||
"name": "Image Gallery Example",
|
||||
"description": "Example launcher plugin demonstrating tile mode with images",
|
||||
"version": "1.0.0",
|
||||
"author": "DMS Team",
|
||||
"icon": "photo_library",
|
||||
"type": "launcher",
|
||||
"trigger": "img",
|
||||
"viewMode": "tile",
|
||||
"viewModeEnforced": true,
|
||||
"component": "./LauncherImageExample.qml",
|
||||
"permissions": []
|
||||
}
|
||||
@@ -13,6 +13,12 @@ Singleton {
|
||||
property var _cachedVisibleApps: null
|
||||
property var _hiddenAppsSet: new Set()
|
||||
|
||||
property var _transformCache: ({})
|
||||
property var _cachedDefaultSections: []
|
||||
property var _cachedDefaultFlatModel: []
|
||||
property bool _defaultCacheValid: false
|
||||
property int cacheVersion: 0
|
||||
|
||||
readonly property int maxResults: 10
|
||||
readonly property int frecencySampleSize: 10
|
||||
|
||||
@@ -43,6 +49,50 @@ Singleton {
|
||||
applications = DesktopEntries.applications.values;
|
||||
_cachedCategories = null;
|
||||
_cachedVisibleApps = null;
|
||||
invalidateLauncherCache();
|
||||
}
|
||||
|
||||
function invalidateLauncherCache() {
|
||||
_transformCache = {};
|
||||
_defaultCacheValid = false;
|
||||
_cachedDefaultSections = [];
|
||||
_cachedDefaultFlatModel = [];
|
||||
cacheVersion++;
|
||||
}
|
||||
|
||||
function getOrTransformApp(app, transformFn) {
|
||||
const id = app.id || app.execString || app.exec || "";
|
||||
if (!id)
|
||||
return transformFn(app);
|
||||
const cached = _transformCache[id];
|
||||
if (cached) {
|
||||
const currentIcon = app.icon || "";
|
||||
const cachedSourceIcon = cached._sourceIcon || "";
|
||||
if (currentIcon === cachedSourceIcon)
|
||||
return cached;
|
||||
}
|
||||
const transformed = transformFn(app);
|
||||
transformed._sourceIcon = app.icon || "";
|
||||
_transformCache[id] = transformed;
|
||||
return transformed;
|
||||
}
|
||||
|
||||
function getCachedDefaultSections() {
|
||||
if (!_defaultCacheValid)
|
||||
return null;
|
||||
return _cachedDefaultSections;
|
||||
}
|
||||
|
||||
function setCachedDefaultSections(sections, flatModel) {
|
||||
_cachedDefaultSections = sections.map(function (s) {
|
||||
return Object.assign({}, s);
|
||||
});
|
||||
_cachedDefaultFlatModel = flatModel.slice();
|
||||
_defaultCacheValid = true;
|
||||
}
|
||||
|
||||
function isCacheValid() {
|
||||
return _defaultCacheValid;
|
||||
}
|
||||
|
||||
function _rebuildHiddenSet() {
|
||||
@@ -68,9 +118,18 @@ Singleton {
|
||||
target: SessionData
|
||||
function onHiddenAppsChanged() {
|
||||
root._rebuildHiddenSet();
|
||||
root.invalidateLauncherCache();
|
||||
}
|
||||
function onAppOverridesChanged() {
|
||||
root._cachedVisibleApps = null;
|
||||
root.invalidateLauncherCache();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AppUsageHistoryData
|
||||
function onAppUsageRankingChanged() {
|
||||
root.invalidateLauncherCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,6 @@ Singleton {
|
||||
pluginDaemonComponents = newDaemons;
|
||||
} else if (isLauncher) {
|
||||
const instance = comp.createObject(root, {
|
||||
"pluginId": pluginId,
|
||||
"pluginService": root
|
||||
});
|
||||
if (!instance) {
|
||||
@@ -702,6 +701,17 @@ Singleton {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
function getPluginViewPreference(pluginId) {
|
||||
const plugin = availablePlugins[pluginId];
|
||||
if (!plugin)
|
||||
return null;
|
||||
|
||||
return {
|
||||
mode: plugin.viewMode || null,
|
||||
enforced: plugin.viewModeEnforced === true
|
||||
};
|
||||
}
|
||||
|
||||
function getGlobalVar(pluginId, varName, defaultValue) {
|
||||
if (globalVars[pluginId] && varName in globalVars[pluginId]) {
|
||||
return globalVars[pluginId][varName];
|
||||
|
||||
@@ -21,6 +21,8 @@ Singleton {
|
||||
property var settingsModalLoader: null
|
||||
property var clipboardHistoryModal: null
|
||||
property var spotlightModal: null
|
||||
property var spotlightV2Modal: null
|
||||
property var spotlightV2ModalLoader: null
|
||||
property var powerMenuModal: null
|
||||
property var processListModal: null
|
||||
property var processListModalLoader: null
|
||||
@@ -361,6 +363,62 @@ Singleton {
|
||||
spotlightModal?.close();
|
||||
}
|
||||
|
||||
property bool _spotlightV2WantsOpen: false
|
||||
property bool _spotlightV2WantsToggle: false
|
||||
property string _spotlightV2PendingQuery: ""
|
||||
|
||||
function openSpotlightV2() {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.show();
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2WantsOpen = true;
|
||||
_spotlightV2WantsToggle = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function openSpotlightV2WithQuery(query: string) {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.showWithQuery(query);
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2PendingQuery = query;
|
||||
_spotlightV2WantsOpen = true;
|
||||
_spotlightV2WantsToggle = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function closeSpotlightV2() {
|
||||
spotlightV2Modal?.hide();
|
||||
}
|
||||
|
||||
function toggleSpotlightV2() {
|
||||
if (spotlightV2Modal) {
|
||||
spotlightV2Modal.toggle();
|
||||
} else if (spotlightV2ModalLoader) {
|
||||
_spotlightV2WantsToggle = true;
|
||||
_spotlightV2WantsOpen = false;
|
||||
spotlightV2ModalLoader.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function _onSpotlightV2ModalLoaded() {
|
||||
if (_spotlightV2WantsOpen) {
|
||||
_spotlightV2WantsOpen = false;
|
||||
if (_spotlightV2PendingQuery) {
|
||||
spotlightV2Modal?.showWithQuery(_spotlightV2PendingQuery);
|
||||
_spotlightV2PendingQuery = "";
|
||||
} else {
|
||||
spotlightV2Modal?.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_spotlightV2WantsToggle) {
|
||||
_spotlightV2WantsToggle = false;
|
||||
spotlightV2Modal?.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
function openPowerMenu() {
|
||||
powerMenuModal?.openCentered();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,17 @@ Image {
|
||||
property string imagePath: ""
|
||||
property int maxCacheSize: 512
|
||||
|
||||
readonly property bool isRemoteUrl: imagePath.startsWith("http://") || imagePath.startsWith("https://")
|
||||
readonly property string normalizedPath: {
|
||||
if (!imagePath)
|
||||
return "";
|
||||
if (isRemoteUrl)
|
||||
return imagePath;
|
||||
if (imagePath.startsWith("file://"))
|
||||
return imagePath.substring(7);
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
function djb2Hash(str) {
|
||||
if (!str)
|
||||
return "";
|
||||
@@ -18,9 +29,15 @@ Image {
|
||||
return hash.toString(16).padStart(8, '0');
|
||||
}
|
||||
|
||||
readonly property string imageHash: imagePath ? djb2Hash(imagePath) : ""
|
||||
readonly property string cachePath: imageHash ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
|
||||
readonly property string encodedImagePath: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
||||
readonly property string imageHash: normalizedPath ? djb2Hash(normalizedPath) : ""
|
||||
readonly property string cachePath: imageHash && !isRemoteUrl ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
|
||||
readonly property string encodedImagePath: {
|
||||
if (!normalizedPath)
|
||||
return "";
|
||||
if (isRemoteUrl)
|
||||
return normalizedPath;
|
||||
return "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
}
|
||||
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
@@ -33,10 +50,14 @@ Image {
|
||||
source = "";
|
||||
return;
|
||||
}
|
||||
if (isRemoteUrl) {
|
||||
source = imagePath;
|
||||
return;
|
||||
}
|
||||
Paths.mkdir(Paths.imagecache);
|
||||
const hash = djb2Hash(imagePath);
|
||||
const hash = djb2Hash(normalizedPath);
|
||||
const cPath = hash ? `${Paths.stringify(Paths.imagecache)}/${hash}@${maxCacheSize}x${maxCacheSize}.png` : "";
|
||||
const encoded = "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
const encoded = "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
source = cPath || encoded;
|
||||
}
|
||||
|
||||
@@ -45,7 +66,7 @@ Image {
|
||||
source = encodedImagePath;
|
||||
return;
|
||||
}
|
||||
if (source != encodedImagePath || status !== Image.Ready || !cachePath)
|
||||
if (isRemoteUrl || source != encodedImagePath || status !== Image.Ready || !cachePath)
|
||||
return;
|
||||
Paths.mkdir(Paths.imagecache);
|
||||
const grabPath = cachePath;
|
||||
|
||||
@@ -8,17 +8,9 @@ StyledRect {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
KeyNavigation.tab: keyNavigationTab
|
||||
KeyNavigation.backtab: keyNavigationBacktab
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
textInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
property alias text: textInput.text
|
||||
property string placeholderText: ""
|
||||
property alias font: textInput.font
|
||||
|
||||
@@ -1109,6 +1109,7 @@
|
||||
"tabIndex": 5,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"always",
|
||||
"area",
|
||||
"auto",
|
||||
"autohide",
|
||||
@@ -1122,7 +1123,7 @@
|
||||
"reveal",
|
||||
"taskbar"
|
||||
],
|
||||
"description": "Hide the dock when not in use and reveal it when hovering near the dock area"
|
||||
"description": "Always hide the dock and reveal it when hovering near the dock area"
|
||||
},
|
||||
{
|
||||
"section": "dockBehavior",
|
||||
@@ -1276,6 +1277,29 @@
|
||||
"taskbar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "dockSmartAutoHide",
|
||||
"label": "Intelligent Auto-hide",
|
||||
"tabIndex": 5,
|
||||
"category": "Dock",
|
||||
"keywords": [
|
||||
"auto",
|
||||
"autohide",
|
||||
"dock",
|
||||
"floating",
|
||||
"hide",
|
||||
"intelligent",
|
||||
"launcher bar",
|
||||
"overlap",
|
||||
"panel",
|
||||
"show",
|
||||
"smart",
|
||||
"taskbar",
|
||||
"windows"
|
||||
],
|
||||
"description": "Show dock when floating windows don",
|
||||
"conditionKey": "isNiri"
|
||||
},
|
||||
{
|
||||
"section": "dockIsolateDisplays",
|
||||
"label": "Isolate Displays",
|
||||
@@ -1428,6 +1452,58 @@
|
||||
"icon": "computer",
|
||||
"conditionKey": "cupsAvailable"
|
||||
},
|
||||
{
|
||||
"section": "appOverrides",
|
||||
"label": "App Customizations",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"app",
|
||||
"customizations",
|
||||
"drawer",
|
||||
"launcher",
|
||||
"menu",
|
||||
"start"
|
||||
],
|
||||
"icon": "edit"
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2Appearance",
|
||||
"label": "Appearance",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"appearance",
|
||||
"bottom",
|
||||
"drawer",
|
||||
"footer",
|
||||
"hints",
|
||||
"keyboard",
|
||||
"launcher",
|
||||
"menu",
|
||||
"mode",
|
||||
"shortcuts",
|
||||
"show",
|
||||
"start",
|
||||
"tabs"
|
||||
],
|
||||
"icon": "tune",
|
||||
"description": "Show mode tabs and keyboard hints at the bottom."
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2BorderEnabled",
|
||||
"label": "Border",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"border",
|
||||
"drawer",
|
||||
"launcher",
|
||||
"menu",
|
||||
"outline",
|
||||
"start"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "launcherLogoBrightness",
|
||||
"label": "Brightness",
|
||||
@@ -1520,6 +1596,21 @@
|
||||
],
|
||||
"description": "Adjust the number of columns in grid view mode."
|
||||
},
|
||||
{
|
||||
"section": "hiddenApps",
|
||||
"label": "Hidden Apps",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"apps",
|
||||
"drawer",
|
||||
"hidden",
|
||||
"launcher",
|
||||
"menu",
|
||||
"start"
|
||||
],
|
||||
"icon": "visibility_off"
|
||||
},
|
||||
{
|
||||
"section": "launcherLogoColorInvertOnMode",
|
||||
"label": "Invert on mode change",
|
||||
@@ -1629,6 +1720,68 @@
|
||||
],
|
||||
"icon": "history"
|
||||
},
|
||||
{
|
||||
"section": "searchAppActions",
|
||||
"label": "Search App Actions",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"actions",
|
||||
"app",
|
||||
"desktop",
|
||||
"drawer",
|
||||
"include",
|
||||
"launcher",
|
||||
"menu",
|
||||
"results",
|
||||
"search",
|
||||
"shortcuts",
|
||||
"start"
|
||||
],
|
||||
"description": "Include desktop actions (shortcuts) in search results."
|
||||
},
|
||||
{
|
||||
"section": "searchOptions",
|
||||
"label": "Search Options",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"actions",
|
||||
"desktop",
|
||||
"drawer",
|
||||
"include",
|
||||
"launcher",
|
||||
"menu",
|
||||
"options",
|
||||
"results",
|
||||
"search",
|
||||
"shortcuts",
|
||||
"start"
|
||||
],
|
||||
"icon": "search",
|
||||
"description": "Include desktop actions (shortcuts) in search results."
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2ShowFooter",
|
||||
"label": "Show Footer",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"bottom",
|
||||
"drawer",
|
||||
"footer",
|
||||
"hints",
|
||||
"keyboard",
|
||||
"launcher",
|
||||
"menu",
|
||||
"mode",
|
||||
"shortcuts",
|
||||
"show",
|
||||
"start",
|
||||
"tabs"
|
||||
],
|
||||
"description": "Show mode tabs and keyboard hints at the bottom."
|
||||
},
|
||||
{
|
||||
"section": "launcherLogoSizeOffset",
|
||||
"label": "Size Offset",
|
||||
@@ -1692,6 +1845,20 @@
|
||||
"icon": "sort_by_alpha",
|
||||
"description": "When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency."
|
||||
},
|
||||
{
|
||||
"section": "dankLauncherV2BorderThickness",
|
||||
"label": "Thickness",
|
||||
"tabIndex": 9,
|
||||
"category": "Launcher",
|
||||
"keywords": [
|
||||
"border",
|
||||
"drawer",
|
||||
"launcher",
|
||||
"menu",
|
||||
"start",
|
||||
"thickness"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "matugenTemplateAlacritty",
|
||||
"label": "Alacritty",
|
||||
@@ -3369,6 +3536,40 @@
|
||||
],
|
||||
"icon": "security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenPowerOffMonitorsOnLock",
|
||||
"label": "Power off monitors on lock",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"activates",
|
||||
"display",
|
||||
"displays",
|
||||
"dpms",
|
||||
"hibernate",
|
||||
"immediately",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"monitor",
|
||||
"monitors",
|
||||
"off",
|
||||
"output",
|
||||
"outputs",
|
||||
"password",
|
||||
"power",
|
||||
"reboot",
|
||||
"restart",
|
||||
"screen",
|
||||
"screens",
|
||||
"security",
|
||||
"shutdown",
|
||||
"sleep",
|
||||
"suspend",
|
||||
"turn"
|
||||
],
|
||||
"description": "Turn off all displays immediately when the lock screen activates"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowPasswordField",
|
||||
"label": "Show Password Field",
|
||||
@@ -5047,6 +5248,26 @@
|
||||
],
|
||||
"description": "Maximum size per clipboard entry"
|
||||
},
|
||||
{
|
||||
"section": "maxPinned",
|
||||
"label": "Maximum Pinned Entries",
|
||||
"tabIndex": 23,
|
||||
"category": "System",
|
||||
"keywords": [
|
||||
"clipboard",
|
||||
"entries",
|
||||
"limit",
|
||||
"linux",
|
||||
"max",
|
||||
"maximum",
|
||||
"number",
|
||||
"os",
|
||||
"pinned",
|
||||
"saved",
|
||||
"system"
|
||||
],
|
||||
"description": "Maximum number of entries that can be saved"
|
||||
},
|
||||
{
|
||||
"section": "_tab_24",
|
||||
"label": "Displays",
|
||||
|
||||
Reference in New Issue
Block a user