summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/LauncherTab.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/LauncherTab.qml')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/LauncherTab.qml1306
1 files changed, 1306 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/LauncherTab.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/LauncherTab.qml
new file mode 100644
index 0000000..8705d42
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/LauncherTab.qml
@@ -0,0 +1,1306 @@
+import QtQuick
+import qs.Common
+import qs.Modals.FileBrowser
+import qs.Services
+import qs.Widgets
+import qs.Modules.Settings.Widgets
+
+Item {
+ id: root
+
+ FileBrowserModal {
+ id: logoFileBrowser
+ browserTitle: I18n.tr("Select Launcher Logo")
+ browserIcon: "image"
+ browserType: "generic"
+ filterExtensions: ["*.svg", "*.png", "*.jpg", "*.jpeg", "*.webp"]
+ onFileSelected: path => SettingsData.set("launcherLogoCustomPath", path.replace("file://", ""))
+ }
+
+ DankFlickable {
+ anchors.fill: parent
+ clip: true
+ contentHeight: mainColumn.height + Theme.spacingXL
+ contentWidth: width
+
+ Column {
+ id: mainColumn
+ topPadding: 4
+ width: Math.min(550, parent.width - Theme.spacingL * 2)
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingXL
+
+ SettingsCard {
+ width: parent.width
+ iconName: "apps"
+ title: I18n.tr("Launcher Button Logo")
+ settingKey: "launcherLogo"
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("Choose the logo displayed on the launcher button in DankBar")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ wrapMode: Text.WordWrap
+ }
+
+ Item {
+ width: parent.width
+ height: logoModeGroup.implicitHeight
+ clip: true
+
+ DankButtonGroup {
+ id: logoModeGroup
+ anchors.horizontalCenter: parent.horizontalCenter
+ buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
+ minButtonWidth: parent.width < 480 ? 44 : 64
+ textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
+ model: {
+ const modes = [I18n.tr("Apps Icon"), I18n.tr("OS Logo"), I18n.tr("Dank")];
+ if (CompositorService.isNiri) {
+ modes.push("niri");
+ } else if (CompositorService.isHyprland) {
+ modes.push("Hyprland");
+ } else if (CompositorService.isDwl) {
+ modes.push("mango");
+ } else if (CompositorService.isSway) {
+ modes.push("Sway");
+ } else if (CompositorService.isScroll) {
+ modes.push("Scroll");
+ } else if (CompositorService.isMiracle) {
+ modes.push("Miracle");
+ } else {
+ modes.push(I18n.tr("Compositor"));
+ }
+ modes.push(I18n.tr("Custom"));
+ return modes;
+ }
+ currentIndex: {
+ if (SettingsData.launcherLogoMode === "apps")
+ return 0;
+ if (SettingsData.launcherLogoMode === "os")
+ return 1;
+ if (SettingsData.launcherLogoMode === "dank")
+ return 2;
+ if (SettingsData.launcherLogoMode === "compositor")
+ return 3;
+ if (SettingsData.launcherLogoMode === "custom")
+ return 4;
+ return 0;
+ }
+ onSelectionChanged: (index, selected) => {
+ if (!selected)
+ return;
+ switch (index) {
+ case 0:
+ SettingsData.set("launcherLogoMode", "apps");
+ break;
+ case 1:
+ SettingsData.set("launcherLogoMode", "os");
+ break;
+ case 2:
+ SettingsData.set("launcherLogoMode", "dank");
+ break;
+ case 3:
+ SettingsData.set("launcherLogoMode", "compositor");
+ break;
+ case 4:
+ SettingsData.set("launcherLogoMode", "custom");
+ break;
+ }
+ }
+ }
+ }
+
+ Row {
+ width: parent.width
+ visible: SettingsData.launcherLogoMode === "custom"
+ spacing: Theme.spacingM
+
+ StyledRect {
+ width: parent.width - selectButton.width - Theme.spacingM
+ height: 36
+ radius: Theme.cornerRadius
+ color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.9)
+ border.color: Theme.outlineStrong
+ border.width: 1
+
+ StyledText {
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ text: SettingsData.launcherLogoCustomPath || I18n.tr("Select an image file...")
+ font.pixelSize: Theme.fontSizeMedium
+ color: SettingsData.launcherLogoCustomPath ? Theme.surfaceText : Theme.outlineButton
+ width: parent.width - Theme.spacingM * 2
+ elide: Text.ElideMiddle
+ }
+ }
+
+ DankActionButton {
+ id: selectButton
+ iconName: "folder_open"
+ width: 36
+ height: 36
+ onClicked: logoFileBrowser.open()
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingL
+ visible: SettingsData.launcherLogoMode !== "apps"
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingM
+
+ StyledText {
+ text: I18n.tr("Color Override")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ Item {
+ width: parent.width
+ height: colorOverrideRow.implicitHeight
+ clip: true
+
+ Row {
+ id: colorOverrideRow
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingM
+
+ DankButtonGroup {
+ id: colorModeGroup
+ buttonPadding: parent.parent.width < 480 ? Theme.spacingS : Theme.spacingL
+ minButtonWidth: parent.parent.width < 480 ? 44 : 64
+ textSize: parent.parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
+ model: [I18n.tr("Default"), I18n.tr("Primary"), I18n.tr("Surface"), I18n.tr("Custom")]
+ currentIndex: {
+ const override = SettingsData.launcherLogoColorOverride;
+ if (override === "")
+ return 0;
+ if (override === "primary")
+ return 1;
+ if (override === "surface")
+ return 2;
+ return 3;
+ }
+ onSelectionChanged: (index, selected) => {
+ if (!selected)
+ return;
+ switch (index) {
+ case 0:
+ SettingsData.set("launcherLogoColorOverride", "");
+ break;
+ case 1:
+ SettingsData.set("launcherLogoColorOverride", "primary");
+ break;
+ case 2:
+ SettingsData.set("launcherLogoColorOverride", "surface");
+ break;
+ case 3:
+ const currentOverride = SettingsData.launcherLogoColorOverride;
+ const isPreset = currentOverride === "" || currentOverride === "primary" || currentOverride === "surface";
+ if (isPreset) {
+ SettingsData.set("launcherLogoColorOverride", "#ffffff");
+ }
+ break;
+ }
+ }
+ }
+
+ Rectangle {
+ id: colorPickerCircle
+ visible: {
+ const override = SettingsData.launcherLogoColorOverride;
+ return override !== "" && override !== "primary" && override !== "surface";
+ }
+ width: 36
+ height: 36
+ radius: 18
+ color: {
+ const override = SettingsData.launcherLogoColorOverride;
+ if (override !== "" && override !== "primary" && override !== "surface")
+ return override;
+ return "#ffffff";
+ }
+ border.color: Theme.outline
+ border.width: 1
+ anchors.verticalCenter: parent.verticalCenter
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ if (!PopoutService.colorPickerModal)
+ return;
+ PopoutService.colorPickerModal.selectedColor = SettingsData.launcherLogoColorOverride;
+ PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Launcher Logo Color");
+ PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
+ SettingsData.set("launcherLogoColorOverride", selectedColor);
+ };
+ PopoutService.colorPickerModal.show();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ SettingsSliderRow {
+ settingKey: "launcherLogoSizeOffset"
+ tags: ["launcher", "logo", "size", "offset", "scale"]
+ text: I18n.tr("Size Offset")
+ minimum: -12
+ maximum: 12
+ value: SettingsData.launcherLogoSizeOffset
+ defaultValue: 0
+ onSliderValueChanged: newValue => SettingsData.set("launcherLogoSizeOffset", newValue)
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingM
+ visible: {
+ const override = SettingsData.launcherLogoColorOverride;
+ return override !== "" && override !== "primary" && override !== "surface";
+ }
+
+ SettingsSliderRow {
+ settingKey: "launcherLogoBrightness"
+ tags: ["launcher", "logo", "brightness", "color"]
+ text: I18n.tr("Brightness")
+ minimum: 0
+ maximum: 100
+ value: Math.round(SettingsData.launcherLogoBrightness * 100)
+ unit: "%"
+ defaultValue: 100
+ onSliderValueChanged: newValue => SettingsData.set("launcherLogoBrightness", newValue / 100)
+ }
+
+ SettingsSliderRow {
+ settingKey: "launcherLogoContrast"
+ tags: ["launcher", "logo", "contrast", "color"]
+ text: I18n.tr("Contrast")
+ minimum: 0
+ maximum: 200
+ value: Math.round(SettingsData.launcherLogoContrast * 100)
+ unit: "%"
+ defaultValue: 100
+ onSliderValueChanged: newValue => SettingsData.set("launcherLogoContrast", newValue / 100)
+ }
+
+ SettingsToggleRow {
+ settingKey: "launcherLogoColorInvertOnMode"
+ tags: ["launcher", "logo", "invert", "mode", "color"]
+ text: I18n.tr("Invert on mode change")
+ checked: SettingsData.launcherLogoColorInvertOnMode
+ onToggled: checked => SettingsData.set("launcherLogoColorInvertOnMode", checked)
+ }
+ }
+ }
+ }
+
+ SettingsCard {
+ width: parent.width
+ iconName: "terminal"
+ title: I18n.tr("Launch Prefix")
+ settingKey: "launchPrefix"
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("Add a custom prefix to all application launches. This can be used for things like 'uwsm-app', 'systemd-run', or other command wrappers.")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ wrapMode: Text.WordWrap
+ }
+
+ DankTextField {
+ width: parent.width
+ text: SettingsData.launchPrefix
+ placeholderText: I18n.tr("Enter launch prefix (e.g., 'uwsm-app')")
+ onTextEdited: SettingsData.set("launchPrefix", text)
+ }
+ }
+
+ SettingsCard {
+ width: parent.width
+ iconName: "sort_by_alpha"
+ title: I18n.tr("Sorting & Layout")
+ settingKey: "launcherSorting"
+
+ SettingsToggleRow {
+ settingKey: "sortAppsAlphabetically"
+ tags: ["launcher", "sort", "alphabetically", "apps", "order"]
+ text: I18n.tr("Sort Alphabetically")
+ description: I18n.tr("When enabled, apps are sorted alphabetically. When disabled, apps are sorted by usage frequency.")
+ checked: SettingsData.sortAppsAlphabetically
+ onToggled: checked => SettingsData.set("sortAppsAlphabetically", checked)
+ }
+
+ SettingsSliderRow {
+ settingKey: "appLauncherGridColumns"
+ tags: ["launcher", "grid", "columns", "layout"]
+ text: I18n.tr("Grid Columns")
+ description: I18n.tr("Adjust the number of columns in grid view mode.")
+ minimum: 2
+ maximum: 8
+ value: SettingsData.appLauncherGridColumns
+ defaultValue: 5
+ onSliderValueChanged: newValue => SettingsData.set("appLauncherGridColumns", newValue)
+ }
+ }
+
+ 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: ["1", "2", "3", "4"]
+ currentIndex: {
+ switch (SettingsData.dankLauncherV2Size) {
+ case "micro":
+ return 0;
+ case "compact":
+ return 1;
+ case "large":
+ return 3;
+ default:
+ return 2;
+ }
+ }
+ onSelectionChanged: (index, selected) => {
+ if (!selected)
+ return;
+ var sizes = ["micro", "compact", "medium", "large"];
+ SettingsData.set("dankLauncherV2Size", sizes[index]);
+ }
+ }
+ }
+ }
+
+ 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
+ enabled: SettingsData.dankLauncherV2Size !== "micro"
+ onToggled: checked => SettingsData.set("dankLauncherV2ShowFooter", checked)
+ }
+
+ SettingsToggleRow {
+ settingKey: "dankLauncherV2UnloadOnClose"
+ tags: ["launcher", "unload", "close", "memory", "vram"]
+ text: I18n.tr("Unload on Close")
+ description: I18n.tr("Free VRAM/memory when the launcher is closed. May cause a slight delay when reopening.")
+ checked: SettingsData.dankLauncherV2UnloadOnClose
+ onToggled: checked => SettingsData.set("dankLauncherV2UnloadOnClose", 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"
+ title: I18n.tr("Niri Integration").replace("Niri", "niri")
+ visible: CompositorService.isNiri
+
+ SettingsToggleRow {
+ settingKey: "spotlightCloseNiriOverview"
+ tags: ["launcher", "niri", "overview", "close", "launch"]
+ text: I18n.tr("Close Overview on Launch")
+ description: I18n.tr("Auto-close Niri overview when launching apps.")
+ checked: SettingsData.spotlightCloseNiriOverview
+ onToggled: checked => SettingsData.set("spotlightCloseNiriOverview", checked)
+ }
+
+ SettingsToggleRow {
+ settingKey: "niriOverviewOverlayEnabled"
+ tags: ["launcher", "niri", "overview", "overlay", "enable"]
+ text: I18n.tr("Enable Overview Overlay")
+ description: I18n.tr("Show launcher overlay when typing in Niri overview. Disable to use another launcher.")
+ checked: SettingsData.niriOverviewOverlayEnabled
+ onToggled: checked => SettingsData.set("niriOverviewOverlayEnabled", checked)
+ }
+ }
+
+ SettingsCard {
+ id: builtInPluginsCard
+ width: parent.width
+ iconName: "extension"
+ title: "DMS"
+ settingKey: "builtInPlugins"
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search"]
+
+ delegate: Rectangle {
+ id: pluginDelegate
+ required property string modelData
+ required property int index
+ readonly property var plugin: AppSearchService.builtInPlugins[modelData]
+
+ width: parent.width
+ height: 56
+ radius: Theme.cornerRadius
+ color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
+
+ Row {
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingM
+
+ DankIcon {
+ name: pluginDelegate.plugin?.cornerIcon ?? "extension"
+ size: Theme.iconSize
+ color: Theme.primary
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ StyledText {
+ text: pluginDelegate.plugin?.name ?? pluginDelegate.modelData
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ }
+
+ StyledText {
+ text: pluginDelegate.plugin?.comment ?? ""
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ }
+ }
+ }
+
+ Row {
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingM
+
+ DankTextField {
+ id: triggerField
+ width: 60
+ visible: pluginDelegate.plugin?.isLauncher === true
+ anchors.verticalCenter: parent.verticalCenter
+ placeholderText: I18n.tr("Trigger")
+ onTextEdited: SettingsData.setBuiltInPluginSetting(pluginDelegate.modelData, "trigger", text)
+ Component.onCompleted: text = SettingsData.getBuiltInPluginSetting(pluginDelegate.modelData, "trigger", pluginDelegate.plugin?.defaultTrigger ?? "")
+ }
+
+ DankToggle {
+ id: enableToggle
+ anchors.verticalCenter: parent.verticalCenter
+ checked: SettingsData.getBuiltInPluginSetting(pluginDelegate.modelData, "enabled", true)
+ onToggled: SettingsData.setBuiltInPluginSetting(pluginDelegate.modelData, "enabled", checked)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ SettingsCard {
+ id: pluginVisibilityCard
+ width: parent.width
+ iconName: "filter_list"
+ title: I18n.tr("Plugin Visibility")
+ settingKey: "pluginVisibility"
+
+ property var allLauncherPlugins: {
+ SettingsData.launcherPluginVisibility;
+ SettingsData.launcherPluginOrder;
+ SettingsData.dankLauncherV2IncludeFilesInAll;
+ SettingsData.dankLauncherV2IncludeFoldersInAll;
+ var plugins = [];
+ var builtIn = AppSearchService.getBuiltInLauncherPlugins() || {};
+ for (var pluginId in builtIn) {
+ var plugin = builtIn[pluginId];
+ plugins.push({
+ id: pluginId,
+ name: plugin.name || pluginId,
+ icon: plugin.cornerIcon || "extension",
+ iconType: "material",
+ isBuiltIn: true,
+ isVirtual: false,
+ trigger: AppSearchService.getBuiltInPluginTrigger(pluginId) || ""
+ });
+ }
+ var thirdParty = PluginService.getLauncherPlugins() || {};
+ for (var pluginId in thirdParty) {
+ var plugin = thirdParty[pluginId];
+ var rawIcon = plugin.icon || "extension";
+ plugins.push({
+ id: pluginId,
+ name: plugin.name || pluginId,
+ icon: rawIcon.startsWith("material:") ? rawIcon.substring(9) : rawIcon.startsWith("unicode:") ? rawIcon.substring(8) : rawIcon,
+ iconType: rawIcon.startsWith("unicode:") ? "unicode" : "material",
+ isBuiltIn: false,
+ isVirtual: false,
+ trigger: PluginService.getPluginTrigger(pluginId) || ""
+ });
+ }
+ if (SettingsData.dankLauncherV2IncludeFilesInAll) {
+ plugins.push({
+ id: "__files",
+ name: I18n.tr("Files"),
+ icon: "insert_drive_file",
+ iconType: "material",
+ isBuiltIn: false,
+ isVirtual: true,
+ trigger: "/"
+ });
+ }
+ if (SettingsData.dankLauncherV2IncludeFoldersInAll) {
+ plugins.push({
+ id: "__folders",
+ name: I18n.tr("Folders"),
+ icon: "folder",
+ iconType: "material",
+ isBuiltIn: false,
+ isVirtual: true,
+ trigger: "/"
+ });
+ }
+ return SettingsData.getOrderedLauncherPlugins(plugins);
+ }
+
+ function reorderPlugin(fromIndex, toIndex) {
+ if (fromIndex === toIndex)
+ return;
+ var currentOrder = allLauncherPlugins.map(p => p.id);
+ var item = currentOrder.splice(fromIndex, 1)[0];
+ currentOrder.splice(toIndex, 0, item);
+ SettingsData.setLauncherPluginOrder(currentOrder);
+ }
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ wrapMode: Text.WordWrap
+ }
+
+ Column {
+ id: pluginVisibilityColumn
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: pluginVisibilityCard.allLauncherPlugins
+
+ delegate: Item {
+ id: visibilityDelegateItem
+ required property var modelData
+ required property int index
+
+ property bool held: pluginDragArea.pressed
+ property real originalY: y
+
+ width: pluginVisibilityColumn.width
+ height: 52
+ z: held ? 2 : 1
+
+ Rectangle {
+ id: visibilityDelegate
+ width: parent.width
+ height: 52
+ radius: Theme.cornerRadius
+ color: visibilityDelegateItem.held ? Theme.surfaceHover : Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
+
+ Row {
+ anchors.left: parent.left
+ anchors.leftMargin: 28
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingM
+
+ Item {
+ width: Theme.iconSize
+ height: Theme.iconSize
+ anchors.verticalCenter: parent.verticalCenter
+
+ DankIcon {
+ anchors.centerIn: parent
+ visible: visibilityDelegateItem.modelData.iconType !== "unicode"
+ name: visibilityDelegateItem.modelData.icon
+ size: Theme.iconSize
+ color: Theme.primary
+ }
+
+ StyledText {
+ anchors.centerIn: parent
+ visible: visibilityDelegateItem.modelData.iconType === "unicode"
+ text: visibilityDelegateItem.modelData.icon
+ font.pixelSize: Theme.iconSize
+ color: Theme.primary
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ Row {
+ spacing: Theme.spacingS
+
+ StyledText {
+ text: visibilityDelegateItem.modelData.name
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ }
+
+ Rectangle {
+ visible: visibilityDelegateItem.modelData.isBuiltIn
+ width: dmsBadgeLabel.implicitWidth + Theme.spacingS
+ height: 16
+ radius: 8
+ color: Theme.primaryContainer
+ anchors.verticalCenter: parent.verticalCenter
+
+ StyledText {
+ id: dmsBadgeLabel
+ anchors.centerIn: parent
+ text: "DMS"
+ font.pixelSize: Theme.fontSizeSmall - 2
+ color: Theme.primary
+ }
+ }
+ }
+
+ StyledText {
+ text: visibilityDelegateItem.modelData.trigger ? I18n.tr("Trigger: %1").arg(visibilityDelegateItem.modelData.trigger) : I18n.tr("No trigger")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ }
+ }
+ }
+
+ DankToggle {
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ checked: {
+ switch (visibilityDelegateItem.modelData.id) {
+ case "__files":
+ return SettingsData.dankLauncherV2IncludeFilesInAll;
+ case "__folders":
+ return SettingsData.dankLauncherV2IncludeFoldersInAll;
+ default:
+ return SettingsData.getPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id);
+ }
+ }
+ onToggled: function (isChecked) {
+ switch (visibilityDelegateItem.modelData.id) {
+ case "__files":
+ SettingsData.set("dankLauncherV2IncludeFilesInAll", isChecked);
+ break;
+ case "__folders":
+ SettingsData.set("dankLauncherV2IncludeFoldersInAll", isChecked);
+ break;
+ default:
+ SettingsData.setPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id, isChecked);
+ }
+ }
+ }
+ }
+
+ MouseArea {
+ id: pluginDragArea
+ anchors.left: parent.left
+ anchors.top: parent.top
+ width: 28
+ height: parent.height
+ hoverEnabled: true
+ cursorShape: Qt.SizeVerCursor
+ drag.target: visibilityDelegateItem.held ? visibilityDelegateItem : undefined
+ drag.axis: Drag.YAxis
+ preventStealing: true
+
+ onPressed: {
+ visibilityDelegateItem.originalY = visibilityDelegateItem.y;
+ }
+
+ onReleased: {
+ if (!drag.active) {
+ visibilityDelegateItem.y = visibilityDelegateItem.originalY;
+ return;
+ }
+ const spacing = Theme.spacingS;
+ const itemH = visibilityDelegateItem.height + spacing;
+ var newIndex = Math.round(visibilityDelegateItem.y / itemH);
+ newIndex = Math.max(0, Math.min(newIndex, pluginVisibilityCard.allLauncherPlugins.length - 1));
+ pluginVisibilityCard.reorderPlugin(visibilityDelegateItem.index, newIndex);
+ visibilityDelegateItem.y = visibilityDelegateItem.originalY;
+ }
+ }
+
+ DankIcon {
+ x: Theme.spacingXS
+ anchors.verticalCenter: parent.verticalCenter
+ name: "drag_indicator"
+ size: 18
+ color: Theme.outline
+ opacity: pluginDragArea.containsMouse || pluginDragArea.pressed ? 1 : 0.5
+ }
+
+ Behavior on y {
+ enabled: !pluginDragArea.pressed && !pluginDragArea.drag.active
+ NumberAnimation {
+ duration: Theme.shortDuration
+ easing.type: Theme.standardEasing
+ }
+ }
+ }
+ }
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("No launcher plugins installed.")
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ horizontalAlignment: Text.AlignHCenter
+ visible: pluginVisibilityCard.allLauncherPlugins.length === 0
+ }
+ }
+ }
+
+ SettingsCard {
+ width: parent.width
+ iconName: "search"
+ title: I18n.tr("Search Options")
+ settingKey: "searchOptions"
+
+ SettingsToggleRow {
+ settingKey: "searchAppActions"
+ tags: ["launcher", "search", "actions", "shortcuts"]
+ text: I18n.tr("Search App Actions")
+ description: I18n.tr("Include desktop actions (shortcuts) in search results.")
+ checked: SessionData.searchAppActions
+ onToggled: checked => SessionData.setSearchAppActions(checked)
+ }
+
+ SettingsToggleRow {
+ settingKey: "rememberLastQuery"
+ tags: ["launcher", "remember", "last", "search", "query"]
+ text: I18n.tr("Remember Last Query")
+ description: I18n.tr("Autofill last remembered query when opened")
+ checked: SettingsData.rememberLastQuery
+ onToggled: checked => SettingsData.set("rememberLastQuery", checked)
+ }
+
+ SettingsToggleRow {
+ settingKey: "dankLauncherV2IncludeFilesInAll"
+ tags: ["launcher", "files", "dsearch", "all", "results", "indexed"]
+ text: I18n.tr("Include Files in All Tab")
+ description: I18n.tr("Merge indexed file results into the All tab (requires dsearch).")
+ checked: SettingsData.dankLauncherV2IncludeFilesInAll
+ onToggled: checked => SettingsData.set("dankLauncherV2IncludeFilesInAll", checked)
+ }
+
+ SettingsToggleRow {
+ settingKey: "dankLauncherV2IncludeFoldersInAll"
+ tags: ["launcher", "folders", "dirs", "dsearch", "all", "results", "indexed"]
+ text: I18n.tr("Include Folders in All Tab")
+ description: I18n.tr("Merge indexed folder results into the All tab (requires dsearch).")
+ checked: SettingsData.dankLauncherV2IncludeFoldersInAll
+ onToggled: checked => SettingsData.set("dankLauncherV2IncludeFoldersInAll", checked)
+ }
+ }
+
+ SettingsCard {
+ id: hiddenAppsCard
+ width: parent.width
+ iconName: "visibility_off"
+ title: I18n.tr("Hidden Apps")
+ settingKey: "hiddenApps"
+
+ property var hiddenAppsModel: {
+ SessionData.hiddenApps;
+ const apps = [];
+ const allApps = AppSearchService.applications || [];
+ for (const hiddenId of SessionData.hiddenApps) {
+ const app = allApps.find(a => (a.id || a.execString || a.exec) === hiddenId);
+ if (app) {
+ apps.push({
+ id: hiddenId,
+ name: app.name || hiddenId,
+ icon: app.icon || "",
+ comment: app.comment || ""
+ });
+ } else {
+ apps.push({
+ id: hiddenId,
+ name: hiddenId,
+ icon: "",
+ comment: ""
+ });
+ }
+ }
+ return apps.sort((a, b) => a.name.localeCompare(b.name));
+ }
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ wrapMode: Text.WordWrap
+ }
+
+ Column {
+ id: hiddenAppsList
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: hiddenAppsCard.hiddenAppsModel
+
+ delegate: Rectangle {
+ width: hiddenAppsList.width
+ height: 48
+ radius: Theme.cornerRadius
+ color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
+ border.width: 0
+
+ Row {
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingM
+
+ Image {
+ width: 24
+ height: 24
+ source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
+ sourceSize.width: 24
+ sourceSize.height: 24
+ fillMode: Image.PreserveAspectFit
+ anchors.verticalCenter: parent.verticalCenter
+ onStatusChanged: {
+ if (status === Image.Error)
+ source = "image://icon/application-x-executable";
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ StyledText {
+ text: modelData.name
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ }
+
+ StyledText {
+ text: modelData.comment || modelData.id
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ visible: text.length > 0
+ }
+ }
+ }
+
+ DankActionButton {
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ iconName: "visibility"
+ iconSize: 18
+ iconColor: Theme.primary
+ onClicked: SessionData.showApp(modelData.id)
+ }
+ }
+ }
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("No hidden apps.")
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ horizontalAlignment: Text.AlignHCenter
+ visible: hiddenAppsCard.hiddenAppsModel.length === 0
+ }
+ }
+ }
+
+ SettingsCard {
+ id: appOverridesCard
+ width: parent.width
+ iconName: "edit"
+ title: I18n.tr("App Customizations")
+ settingKey: "appOverrides"
+
+ property var overridesModel: {
+ SessionData.appOverrides;
+ const items = [];
+ const allApps = AppSearchService.applications || [];
+ for (const appId in SessionData.appOverrides) {
+ const override = SessionData.appOverrides[appId];
+ const app = allApps.find(a => (a.id || a.execString || a.exec) === appId);
+ items.push({
+ id: appId,
+ name: override.name || app?.name || appId,
+ originalName: app?.name || appId,
+ icon: override.icon || app?.icon || "",
+ hasOverride: true
+ });
+ }
+ return items.sort((a, b) => a.name.localeCompare(b.name));
+ }
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ wrapMode: Text.WordWrap
+ }
+
+ Column {
+ id: overridesList
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: appOverridesCard.overridesModel
+
+ delegate: Rectangle {
+ width: overridesList.width
+ height: 48
+ radius: Theme.cornerRadius
+ color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
+ border.width: 0
+
+ Row {
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingM
+
+ Image {
+ width: 24
+ height: 24
+ source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
+ sourceSize.width: 24
+ sourceSize.height: 24
+ fillMode: Image.PreserveAspectFit
+ anchors.verticalCenter: parent.verticalCenter
+ onStatusChanged: {
+ if (status === Image.Error)
+ source = "image://icon/application-x-executable";
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ StyledText {
+ text: modelData.name
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ }
+
+ StyledText {
+ text: modelData.originalName !== modelData.name ? modelData.originalName : modelData.id
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ }
+ }
+ }
+
+ DankActionButton {
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ iconName: "delete"
+ iconSize: 18
+ iconColor: Theme.error
+ onClicked: SessionData.clearAppOverride(modelData.id)
+ }
+ }
+ }
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("No app customizations.")
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ horizontalAlignment: Text.AlignHCenter
+ visible: appOverridesCard.overridesModel.length === 0
+ }
+ }
+ }
+
+ SettingsCard {
+ id: recentAppsCard
+ width: parent.width
+ iconName: "history"
+ title: I18n.tr("Recently Used Apps")
+ settingKey: "recentApps"
+ collapsible: true
+ expanded: false
+
+ property var rankedAppsModel: {
+ var ranking = AppUsageHistoryData.appUsageRanking;
+ if (!ranking)
+ return [];
+ var apps = [];
+ for (var appId in ranking) {
+ var appData = ranking[appId];
+ apps.push({
+ "id": appId,
+ "name": appData.name,
+ "exec": appData.exec,
+ "icon": appData.icon,
+ "comment": appData.comment,
+ "usageCount": appData.usageCount,
+ "lastUsed": appData.lastUsed
+ });
+ }
+ apps.sort(function (a, b) {
+ if (a.usageCount !== b.usageCount)
+ return b.usageCount - a.usageCount;
+ return a.name.localeCompare(b.name);
+ });
+ return apps.slice(0, 20);
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingM
+
+ StyledText {
+ width: parent.width - clearAllButton.width - Theme.spacingM
+ text: I18n.tr("Apps are ordered by usage frequency, then last used, then alphabetically.")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ wrapMode: Text.WordWrap
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ DankActionButton {
+ id: clearAllButton
+ iconName: "delete_sweep"
+ iconSize: Theme.iconSize - 2
+ iconColor: Theme.error
+ anchors.verticalCenter: parent.verticalCenter
+ onClicked: {
+ AppUsageHistoryData.appUsageRanking = {};
+ AppUsageHistoryData.saveSettings();
+ }
+ }
+ }
+
+ Column {
+ id: rankedAppsList
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Repeater {
+ model: recentAppsCard.rankedAppsModel
+
+ delegate: Rectangle {
+ width: rankedAppsList.width
+ height: 48
+ radius: Theme.cornerRadius
+ color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
+ border.width: 0
+
+ Row {
+ anchors.left: parent.left
+ anchors.leftMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Theme.spacingM
+
+ StyledText {
+ text: (index + 1).toString()
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ color: Theme.primary
+ width: 20
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Image {
+ width: 24
+ height: 24
+ source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
+ sourceSize.width: 24
+ sourceSize.height: 24
+ fillMode: Image.PreserveAspectFit
+ anchors.verticalCenter: parent.verticalCenter
+ onStatusChanged: {
+ if (status === Image.Error)
+ source = "image://icon/application-x-executable";
+ }
+ }
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: 2
+
+ StyledText {
+ text: modelData.name || I18n.tr("Unknown App")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ }
+
+ StyledText {
+ text: {
+ if (!modelData.lastUsed)
+ return I18n.tr("Never used");
+ var date = new Date(modelData.lastUsed);
+ var now = new Date();
+ var diffMs = now - date;
+ var diffMins = Math.floor(diffMs / (1000 * 60));
+ var diffHours = Math.floor(diffMs / (1000 * 60 * 60));
+ var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
+ if (diffMins < 1)
+ return I18n.tr("Last launched just now");
+ if (diffMins < 60)
+ return diffMins === 1 ? I18n.tr("Last launched %1 minute ago").arg(diffMins) : I18n.tr("Last launched %1 minutes ago").arg(diffMins);
+ if (diffHours < 24)
+ return diffHours === 1 ? I18n.tr("Last launched %1 hour ago").arg(diffHours) : I18n.tr("Last launched %1 hours ago").arg(diffHours);
+ if (diffDays < 7)
+ return diffDays === 1 ? I18n.tr("Last launched %1 day ago").arg(diffDays) : I18n.tr("Last launched %1 days ago").arg(diffDays);
+ return I18n.tr("Last launched %1").arg(date.toLocaleDateString());
+ }
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ }
+ }
+ }
+
+ DankActionButton {
+ anchors.right: parent.right
+ anchors.rightMargin: Theme.spacingM
+ anchors.verticalCenter: parent.verticalCenter
+ circular: true
+ iconName: "close"
+ iconSize: 16
+ iconColor: Theme.error
+ onClicked: {
+ var currentRanking = Object.assign({}, AppUsageHistoryData.appUsageRanking || {});
+ delete currentRanking[modelData.id];
+ AppUsageHistoryData.appUsageRanking = currentRanking;
+ AppUsageHistoryData.saveSettings();
+ }
+ }
+ }
+ }
+
+ StyledText {
+ width: parent.width
+ text: I18n.tr("No apps have been launched yet.")
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceVariantText
+ horizontalAlignment: Text.AlignHCenter
+ visible: recentAppsCard.rankedAppsModel.length === 0
+ }
+ }
+ }
+ }
+ }
+}