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 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") 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 DankButtonGroup { id: logoModeGroup anchors.horizontalCenter: parent.horizontalCenter 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 { 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 } Row { anchors.horizontalCenter: parent.horizontalCenter spacing: Theme.spacingM DankButtonGroup { id: colorModeGroup 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 { 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 { 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 { 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 { 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 { 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") 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") SettingsToggleRow { 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 { 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: "open_in_new" title: I18n.tr("Niri Integration") visible: CompositorService.isNiri SettingsToggleRow { text: I18n.tr("Close Overview on Launch") description: I18n.tr("When enabled, launching an app from the launcher will automatically close the Niri overview if it's open.") checked: SettingsData.spotlightCloseNiriOverview onToggled: checked => SettingsData.set("spotlightCloseNiriOverview", checked) } SettingsToggleRow { text: I18n.tr("Enable Overview Overlay") description: I18n.tr("When enabled, shows the launcher overlay when typing in Niri overview mode. Disable this if you prefer to not have the launcher when typing on Niri overview or want to use other launcher in the overview.") checked: SettingsData.niriOverviewOverlayEnabled onToggled: checked => SettingsData.set("niriOverviewOverlayEnabled", checked) } } SettingsCard { width: parent.width iconName: "history" title: I18n.tr("Recently Used Apps") property var rankedAppsModel: { var apps = []; for (var appId in (AppUsageHistoryData.appUsageRanking || {})) { var appData = (AppUsageHistoryData.appUsageRanking || {})[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: parent.parent.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: modelData.icon ? "image://icon/" + modelData.icon : "image://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 || "Unknown App" font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText } StyledText { text: { if (!modelData.lastUsed) return "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 I18n.tr("Last launched %1 minute%2 ago").arg(diffMins).arg(diffMins === 1 ? "" : "s"); if (diffHours < 24) return I18n.tr("Last launched %1 hour%2 ago").arg(diffHours).arg(diffHours === 1 ? "" : "s"); if (diffDays < 7) return I18n.tr("Last launched %1 day%2 ago").arg(diffDays).arg(diffDays === 1 ? "" : "s"); 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: parent.parent.rankedAppsModel.length === 0 ? "No apps have been launched yet." : "" font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceVariantText horizontalAlignment: Text.AlignHCenter visible: parent.parent.rankedAppsModel.length === 0 } } } } } }