1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00
Files
DankMaterialShell/quickshell/Modules/Settings/LauncherTab.qml
2025-12-03 17:25:40 -05:00

510 lines
24 KiB
QML

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
}
}
}
}
}
}