1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00

Compare commits

...

9 Commits

Author SHA1 Message Date
bbedward
6735989455 launcher v2: reset visibility on screen change 2026-01-21 19:29:03 -05:00
bbedward
db37ac24c7 launcher v2: support CachingImage in icon renderer 2026-01-21 17:54:36 -05:00
bbedward
0231270f9e launcher v2: use AppIconRenderer from legacy launcha 2026-01-21 17:51:24 -05:00
bbedward
b5194aa9e1 notifications: update dimensions and text expansion logic 2026-01-21 16:51:39 -05:00
bbedward
ea0ffaacb0 launcher v2: fix some plugin icon handling 2026-01-21 16:09:52 -05:00
bbedward
3b1f084a13 notepad: fix unsave changed dialog height 2026-01-21 16:01:59 -05:00
bbedward
39a9e3a89f add dms doctor to issue template 2026-01-21 14:25:41 -05:00
bbedward
7a7af775c2 launcher v2: some optims on meta performance
- limit plugin results to 10
- longer debounce
- search plugins when chars > 1
2026-01-21 14:20:12 -05:00
bbedward
6ac2a305f7 launcher v2: sort order preference for plugin results 2026-01-21 14:08:40 -05:00
14 changed files with 483 additions and 381 deletions

View File

@@ -42,12 +42,12 @@ body:
placeholder: e.g., PikaOS, Void Linux, etc.
validations:
required: false
- type: input
id: dms_version
- type: textarea
id: dms_doctor
attributes:
label: dms version
description: Output of dms version command
placeholder: e.g., 1.2.3
label: dms doctor -v
description: Output of `dms doctor -v` command
placeholder: Paste the output of `dms doctor -v` here
validations:
required: true
- type: textarea

View File

@@ -27,12 +27,12 @@ body:
placeholder: Your Linux distribution
validations:
required: false
- type: input
id: dms_version
- type: textarea
id: dms_doctor
attributes:
label: dms version
description: Output of dms version command
placeholder: e.g., 1.2.3
label: dms doctor -v
description: Output of `dms doctor -v` command
placeholder: Paste the output of `dms doctor -v` here
validations:
required: false
- type: textarea

View File

@@ -96,6 +96,28 @@ Singleton {
saveSettings();
}
property var launcherPluginOrder: []
onLauncherPluginOrderChanged: saveSettings()
function setLauncherPluginOrder(order) {
launcherPluginOrder = order;
}
function getOrderedLauncherPlugins(allPlugins) {
if (!launcherPluginOrder || launcherPluginOrder.length === 0)
return allPlugins;
const orderMap = {};
for (let i = 0; i < launcherPluginOrder.length; i++)
orderMap[launcherPluginOrder[i]] = i;
return allPlugins.slice().sort((a, b) => {
const aOrder = orderMap[a.id] ?? 9999;
const bOrder = orderMap[b.id] ?? 9999;
if (aOrder !== bOrder)
return aOrder - bOrder;
return a.name.localeCompare(b.name);
});
}
property alias dankBarLeftWidgetsModel: leftWidgetsModel
property alias dankBarCenterWidgetsModel: centerWidgetsModel
property alias dankBarRightWidgetsModel: rightWidgetsModel

View File

@@ -416,7 +416,8 @@ var SPEC = {
desktopWidgetGroups: { def: [] },
builtInPluginSettings: { def: {} },
launcherPluginVisibility: { def: {} }
launcherPluginVisibility: { def: {} },
launcherPluginOrder: { def: [] }
};
function getValidKeys() {

View File

@@ -176,9 +176,11 @@ Item {
pluginViewPreferences = prefs;
}
property int _searchVersion: 0
Timer {
id: searchDebounce
interval: 80
interval: searchMode === "all" && searchQuery.length > 0 ? 120 : 60
onTriggered: root.performSearch()
}
@@ -193,6 +195,7 @@ Item {
}
function setSearchQuery(query) {
_searchVersion++;
searchQuery = query;
searchDebounce.restart();
@@ -261,6 +264,7 @@ Item {
}
function performSearch() {
var currentVersion = _searchVersion;
isSearching = true;
var cachedSections = AppSearchService.getCachedDefaultSections();
@@ -430,36 +434,35 @@ Item {
allItems = allItems.concat(apps);
if (searchMode === "all") {
if (searchQuery) {
var allPluginIds = getVisibleLauncherPluginIds();
for (var i = 0; i < allPluginIds.length; i++) {
var pluginId = allPluginIds[i];
var pItems = getPluginItems(pluginId, searchQuery);
allItems = allItems.concat(pItems);
}
var allBuiltInIds = getVisibleBuiltInLauncherIds();
for (var i = 0; i < allBuiltInIds.length; i++) {
var pluginId = allBuiltInIds[i];
var blItems = AppSearchService.getBuiltInLauncherItems(pluginId, searchQuery);
for (var j = 0; j < blItems.length; j++) {
allItems.push(transformBuiltInLauncherItem(blItems[j], pluginId));
var includePlugins = !searchQuery || searchQuery.length >= 2;
if (searchQuery && includePlugins) {
var allPluginsOrdered = getAllVisiblePluginsOrdered();
var maxPerPlugin = 10;
for (var i = 0; i < allPluginsOrdered.length; i++) {
var plugin = allPluginsOrdered[i];
if (plugin.isBuiltIn) {
var blItems = AppSearchService.getBuiltInLauncherItems(plugin.id, searchQuery);
var blLimit = Math.min(blItems.length, maxPerPlugin);
for (var j = 0; j < blLimit; j++)
allItems.push(transformBuiltInLauncherItem(blItems[j], plugin.id));
} else {
var pItems = getPluginItems(plugin.id, searchQuery);
if (pItems.length > maxPerPlugin)
pItems = pItems.slice(0, maxPerPlugin);
allItems = allItems.concat(pItems);
}
}
} else {
var emptyTriggerPlugins = getEmptyTriggerPlugins();
for (var i = 0; i < emptyTriggerPlugins.length; i++) {
var pluginId = emptyTriggerPlugins[i];
var pItems = getPluginItems(pluginId, searchQuery);
allItems = allItems.concat(pItems);
}
var builtInLauncherPlugins = getBuiltInEmptyTriggerLaunchers();
for (var i = 0; i < builtInLauncherPlugins.length; i++) {
var pluginId = builtInLauncherPlugins[i];
var blItems = AppSearchService.getBuiltInLauncherItems(pluginId, searchQuery);
for (var j = 0; j < blItems.length; j++) {
allItems.push(transformBuiltInLauncherItem(blItems[j], pluginId));
} else if (!searchQuery) {
var emptyTriggerOrdered = getEmptyTriggerPluginsOrdered();
for (var i = 0; i < emptyTriggerOrdered.length; i++) {
var plugin = emptyTriggerOrdered[i];
if (plugin.isBuiltIn) {
var blItems = AppSearchService.getBuiltInLauncherItems(plugin.id, searchQuery);
for (var j = 0; j < blItems.length; j++)
allItems.push(transformBuiltInLauncherItem(blItems[j], plugin.id));
} else {
var pItems = getPluginItems(plugin.id, searchQuery);
allItems = allItems.concat(pItems);
}
}
@@ -469,17 +472,29 @@ Item {
}
var dynamicDefs = buildDynamicSectionDefs(allItems);
if (currentVersion !== _searchVersion) {
isSearching = false;
return;
}
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
sections = Scorer.groupBySection(scoredItems, dynamicDefs, sortAlpha, searchQuery ? 50 : 500);
var newSections = Scorer.groupBySection(scoredItems, dynamicDefs, sortAlpha, searchQuery ? 50 : 500);
for (var i = 0; i < sections.length; i++) {
var sid = sections[i].id;
if (currentVersion !== _searchVersion) {
isSearching = false;
return;
}
for (var i = 0; i < newSections.length; i++) {
var sid = newSections[i].id;
if (collapsedSections[sid] !== undefined) {
sections[i].collapsed = collapsedSections[sid];
newSections[i].collapsed = collapsedSections[sid];
}
}
sections = newSections;
flatModel = Scorer.flattenSections(sections);
if (!AppSearchService.isCacheValid() && !searchQuery && searchMode === "all" && !pluginFilter) {
@@ -663,8 +678,16 @@ Item {
function transformBuiltInLauncherItem(item, pluginId) {
var rawIcon = item.icon || "extension";
var iconType = detectIconType(rawIcon);
var icon = stripIconPrefix(rawIcon);
var iconType = item.iconType;
if (!iconType) {
if (rawIcon.startsWith("material:"))
iconType = "material";
else if (rawIcon.startsWith("unicode:"))
iconType = "unicode";
else
iconType = "image";
}
return {
id: item.action || "",
@@ -677,6 +700,7 @@ Item {
data: item,
pluginId: pluginId,
isBuiltInLauncher: true,
keywords: item.keywords || [],
actions: [],
primaryAction: {
name: I18n.tr("Open"),
@@ -859,9 +883,10 @@ Item {
function getEmptyTriggerPlugins() {
var plugins = PluginService.getPluginsWithEmptyTrigger();
return plugins.filter(function (pluginId) {
var visible = plugins.filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function getAllLauncherPluginIds() {
@@ -871,9 +896,10 @@ Item {
function getVisibleLauncherPluginIds() {
var launchers = PluginService.getLauncherPlugins();
return Object.keys(launchers).filter(function (pluginId) {
var visible = Object.keys(launchers).filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function getAllBuiltInLauncherIds() {
@@ -883,9 +909,88 @@ Item {
function getVisibleBuiltInLauncherIds() {
var launchers = AppSearchService.getBuiltInLauncherPlugins();
return Object.keys(launchers).filter(function (pluginId) {
var visible = Object.keys(launchers).filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function sortPluginIdsByOrder(pluginIds) {
var order = SettingsData.launcherPluginOrder || [];
if (order.length === 0)
return pluginIds;
var orderMap = {};
for (var i = 0; i < order.length; i++)
orderMap[order[i]] = i;
return pluginIds.slice().sort(function (a, b) {
var aOrder = orderMap[a] !== undefined ? orderMap[a] : 9999;
var bOrder = orderMap[b] !== undefined ? orderMap[b] : 9999;
return aOrder - bOrder;
});
}
function getAllVisiblePluginsOrdered() {
var thirdPartyLaunchers = PluginService.getLauncherPlugins() || {};
var builtInLaunchers = AppSearchService.getBuiltInLauncherPlugins() || {};
var all = [];
for (var id in thirdPartyLaunchers) {
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: false
});
}
for (var id in builtInLaunchers) {
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: true
});
}
var order = SettingsData.launcherPluginOrder || [];
if (order.length === 0)
return all;
var orderMap = {};
for (var i = 0; i < order.length; i++)
orderMap[order[i]] = i;
return all.sort(function (a, b) {
var aOrder = orderMap[a.id] !== undefined ? orderMap[a.id] : 9999;
var bOrder = orderMap[b.id] !== undefined ? orderMap[b.id] : 9999;
return aOrder - bOrder;
});
}
function getEmptyTriggerPluginsOrdered() {
var thirdParty = PluginService.getPluginsWithEmptyTrigger() || [];
var builtIn = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger() || [];
var all = [];
for (var i = 0; i < thirdParty.length; i++) {
var id = thirdParty[i];
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: false
});
}
for (var i = 0; i < builtIn.length; i++) {
var id = builtIn[i];
if (SettingsData.getPluginAllowWithoutTrigger(id))
all.push({
id: id,
isBuiltIn: true
});
}
var order = SettingsData.launcherPluginOrder || [];
if (order.length === 0)
return all;
var orderMap = {};
for (var i = 0; i < order.length; i++)
orderMap[order[i]] = i;
return all.sort(function (a, b) {
var aOrder = orderMap[a.id] !== undefined ? orderMap[a.id] : 9999;
var bOrder = orderMap[b.id] !== undefined ? orderMap[b.id] : 9999;
return aOrder - bOrder;
});
}
function getPluginBrowseItems() {
@@ -948,9 +1053,10 @@ Item {
function getBuiltInEmptyTriggerLaunchers() {
var plugins = AppSearchService.getBuiltInLauncherPluginsWithEmptyTrigger();
return plugins.filter(function (pluginId) {
var visible = plugins.filter(function (pluginId) {
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
});
return sortPluginIdsByOrder(visible);
}
function getPluginItems(pluginId, query) {
@@ -1088,8 +1194,16 @@ Item {
function transformPluginItem(item, pluginId) {
var rawIcon = item.icon || "extension";
var iconType = item.iconType || detectIconType(rawIcon);
var icon = stripIconPrefix(rawIcon);
var iconType = item.iconType;
if (!iconType) {
if (rawIcon.startsWith("material:"))
iconType = "material";
else if (rawIcon.startsWith("unicode:"))
iconType = "unicode";
else
iconType = "image";
}
return {
id: item.id || item.name || "",
@@ -1101,6 +1215,7 @@ Item {
section: "plugin_" + pluginId,
data: item,
pluginId: pluginId,
keywords: item.keywords || [],
actions: item.actions || [],
primaryAction: item.primaryAction || {
name: I18n.tr("Select"),

View File

@@ -17,6 +17,7 @@ Item {
property alias spotlightContent: launcherContent
property bool openedFromOverview: false
property bool isClosing: false
property bool _windowEnabled: true
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property var effectiveScreen: launcherWindow.screen
@@ -197,27 +198,41 @@ Item {
Connections {
target: Quickshell
function onScreensChanged() {
if (!launcherWindow.screen)
if (Quickshell.screens.length === 0)
return;
const currentScreenName = launcherWindow.screen.name;
let screenStillExists = false;
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name === currentScreenName) {
screenStillExists = true;
break;
const screen = launcherWindow.screen;
const screenName = screen?.name;
let needsReset = !screen || !screenName;
if (!needsReset) {
needsReset = true;
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name === screenName) {
needsReset = false;
break;
}
}
}
if (screenStillExists)
if (!needsReset)
return;
const newScreen = CompositorService.getFocusedScreen();
if (newScreen)
launcherWindow.screen = newScreen;
const newScreen = CompositorService.getFocusedScreen() ?? Quickshell.screens[0];
if (!newScreen)
return;
root._windowEnabled = false;
launcherWindow.screen = newScreen;
Qt.callLater(() => {
root._windowEnabled = true;
});
}
}
PanelWindow {
id: launcherWindow
visible: true
visible: root._windowEnabled
color: "transparent"
exclusionMode: ExclusionMode.Ignore

View File

@@ -16,6 +16,25 @@ Rectangle {
signal clicked
signal rightClicked(real mouseX, real mouseY)
readonly property string iconValue: {
if (!item)
return "";
switch (item.iconType) {
case "material":
case "nerd":
return "material:" + (item.icon || "apps");
case "unicode":
return "unicode:" + (item.icon || "");
case "composite":
return item.iconFull || "";
case "image":
default:
return item.icon || "";
}
}
readonly property int computedIconSize: Math.min(48, Math.max(32, width * 0.45))
radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
@@ -25,88 +44,15 @@ Rectangle {
spacing: Theme.spacingS
width: parent.width - Theme.spacingM
Item {
width: iconSize
height: iconSize
AppIconRenderer {
width: root.computedIconSize
height: root.computedIconSize
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
}
StyledText {
anchors.centerIn: parent
visible: root.item?.iconType === "unicode"
text: root.item?.icon ?? ""
font.pixelSize: 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
}
}
}
iconValue: root.iconValue
iconSize: root.computedIconSize
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
iconColor: root.isSelected ? Theme.primary : Theme.surfaceText
materialIconSizeAdjustment: root.computedIconSize * 0.3
}
StyledText {

View File

@@ -16,6 +16,23 @@ Rectangle {
signal clicked
signal rightClicked(real mouseX, real mouseY)
readonly property string iconValue: {
if (!item)
return "";
switch (item.iconType) {
case "material":
case "nerd":
return "material:" + (item.icon || "apps");
case "unicode":
return "unicode:" + (item.icon || "");
case "composite":
return item.iconFull || "";
case "image":
default:
return item.icon || "";
}
}
width: parent?.width ?? 200
height: 52
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
@@ -27,86 +44,14 @@ Rectangle {
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingM
Item {
AppIconRenderer {
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
}
StyledText {
anchors.centerIn: parent
visible: root.item?.iconType === "unicode"
text: root.item?.icon ?? ""
font.pixelSize: 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
}
}
}
iconValue: root.iconValue
iconSize: 36
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
materialIconSizeAdjustment: 12
}
Column {

View File

@@ -21,22 +21,30 @@ Rectangle {
border.width: isSelected ? 2 : 0
border.color: Theme.primary
readonly property string imageSource: {
if (!item?.data)
readonly property string iconValue: {
if (!item)
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 "";
if (data?.imageUrl)
return "image:" + data.imageUrl;
if (data?.imagePath)
return "image:" + data.imagePath;
if (data?.path && isImageFile(data.path))
return "image:" + data.path;
switch (item.iconType) {
case "material":
case "nerd":
return "material:" + (item.icon || "image");
case "unicode":
return "unicode:" + (item.icon || "");
case "composite":
return item.iconFull || "";
case "image":
default:
return item.icon || "";
}
}
readonly property bool useImage: imageSource.length > 0
readonly property bool useIconProvider: !useImage && item?.iconType === "image"
function isImageFile(path) {
if (!path)
return false;
@@ -55,39 +63,12 @@ Rectangle {
color: Theme.surfaceContainerHigh
clip: true
CachingImage {
AppIconRenderer {
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 && root.item?.iconType !== "unicode"
name: root.item?.icon ?? "image"
size: Math.min(parent.width, parent.height) * 0.4
color: Theme.surfaceVariantText
}
StyledText {
anchors.centerIn: parent
visible: root.item?.iconType === "unicode"
text: root.item?.icon ?? ""
font.pixelSize: Math.min(parent.width, parent.height) * 0.4
color: Theme.surfaceVariantText
iconValue: root.iconValue
iconSize: Math.min(parent.width, parent.height)
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
materialIconSizeAdjustment: iconSize * 0.3
}
Rectangle {

View File

@@ -200,7 +200,7 @@ Item {
}
onPreviewRequested: {
textEditor.togglePreview()
textEditor.togglePreview();
}
onEscapePressed: {
@@ -362,8 +362,8 @@ Item {
DankModal {
id: confirmationDialog
width: 400
height: 180
modalWidth: 400
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 180
shouldBeVisible: false
allowStacking: true
@@ -376,6 +376,7 @@ Item {
FocusScope {
anchors.fill: parent
focus: true
implicitHeight: contentColumn.implicitHeight
Keys.onEscapePressed: event => {
confirmationDialog.close();
@@ -384,47 +385,31 @@ Item {
}
Column {
anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2
id: contentColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Row {
StyledText {
text: I18n.tr("Unsaved Changes")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Unsaved Changes")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
wrapMode: Text.Wrap
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: {
confirmationDialog.close();
root.confirmationDialogOpen = false;
}
}
wrapMode: Text.Wrap
}
Item {
width: parent.width
height: 40
height: 36
Row {
anchors.right: parent.right
@@ -515,6 +500,20 @@ Item {
}
}
}
DankActionButton {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: {
confirmationDialog.close();
root.confirmationDialogOpen = false;
}
}
}
}
}

View File

@@ -558,8 +558,8 @@ Rectangle {
visible: !expanded
anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.bottom: parent.bottom
anchors.bottomMargin: contentSpacing
anchors.top: collapsedContent.bottom
anchors.topMargin: contentSpacing
spacing: contentSpacing
Repeater {
@@ -614,8 +614,8 @@ Rectangle {
visible: !expanded && actionCount < 3
anchors.right: parent.right
anchors.rightMargin: Theme.spacingL
anchors.bottom: parent.bottom
anchors.bottomMargin: contentSpacing
anchors.top: collapsedContent.bottom
anchors.topMargin: contentSpacing
width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: actionButtonHeight
radius: Theme.spacingXS

View File

@@ -531,8 +531,8 @@ PanelWindow {
Row {
anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.bottom: parent.bottom
anchors.bottomMargin: contentSpacing
anchors.top: notificationContent.bottom
anchors.topMargin: contentSpacing
spacing: contentSpacing
z: 20
@@ -585,8 +585,8 @@ PanelWindow {
visible: actionCount < 3
anchors.right: parent.right
anchors.rightMargin: Theme.spacingL
anchors.bottom: parent.bottom
anchors.bottomMargin: contentSpacing
anchors.top: notificationContent.bottom
anchors.topMargin: contentSpacing
width: Math.max(clearTextLabel.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: actionButtonHeight
radius: Theme.spacingXS

View File

@@ -581,6 +581,7 @@ Item {
property var allLauncherPlugins: {
SettingsData.launcherPluginVisibility;
SettingsData.launcherPluginOrder;
var plugins = [];
var builtIn = AppSearchService.getBuiltInLauncherPlugins() || {};
for (var pluginId in builtIn) {
@@ -607,109 +608,175 @@ Item {
trigger: PluginService.getPluginTrigger(pluginId) || ""
});
}
return plugins.sort((a, b) => a.name.localeCompare(b.name));
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. Disabled plugins will only show when using their trigger.")
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: Rectangle {
id: visibilityDelegate
delegate: Item {
id: visibilityDelegateItem
required property var modelData
required property int index
width: parent.width
property bool held: pluginDragArea.pressed
property real originalY: y
width: pluginVisibilityColumn.width
height: 52
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
z: held ? 2 : 1
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
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)
Item {
width: Theme.iconSize
height: Theme.iconSize
Row {
anchors.left: parent.left
anchors.leftMargin: 28
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
anchors.centerIn: parent
visible: visibilityDelegate.modelData.iconType !== "unicode"
name: visibilityDelegate.modelData.icon
size: Theme.iconSize
color: Theme.primary
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
}
}
StyledText {
anchors.centerIn: parent
visible: visibilityDelegate.modelData.iconType === "unicode"
text: visibilityDelegate.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.primaryText
}
}
}
StyledText {
text: visibilityDelegateItem.modelData.trigger ? I18n.tr("Trigger: %1").arg(visibilityDelegateItem.modelData.trigger) : I18n.tr("No trigger")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
Column {
DankToggle {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: 2
Row {
spacing: Theme.spacingS
StyledText {
text: visibilityDelegate.modelData.name
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
}
Rectangle {
visible: visibilityDelegate.modelData.isBuiltIn
width: dmsLabel.implicitWidth + Theme.spacingS
height: 16
radius: 8
color: Theme.primaryContainer
anchors.verticalCenter: parent.verticalCenter
StyledText {
id: dmsLabel
anchors.centerIn: parent
text: "DMS"
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.primaryText
}
}
}
StyledText {
text: visibilityDelegate.modelData.trigger ? I18n.tr("Trigger: %1").arg(visibilityDelegate.modelData.trigger) : I18n.tr("No trigger")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
checked: SettingsData.getPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id)
onToggled: function (isChecked) {
SettingsData.setPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id, isChecked);
}
}
}
DankToggle {
id: visibilityToggle
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
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
checked: SettingsData.getPluginAllowWithoutTrigger(visibilityDelegate.modelData.id)
onToggled: function (isChecked) {
SettingsData.setPluginAllowWithoutTrigger(visibilityDelegate.modelData.id, isChecked);
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
}
}
}

View File

@@ -26,8 +26,10 @@ Item {
readonly property bool isUnicode: iconValue.startsWith("unicode:")
readonly property bool isSvgCorner: iconValue.startsWith("svg+corner:")
readonly property bool isSvg: !isSvgCorner && iconValue.startsWith("svg:")
readonly property bool isImage: iconValue.startsWith("image:")
readonly property string materialName: isMaterial ? iconValue.substring(9) : ""
readonly property string unicodeChar: isUnicode ? iconValue.substring(8) : ""
readonly property string imagePath: isImage ? iconValue.substring(6) : ""
readonly property string svgSource: {
if (isSvgCorner) {
const parts = iconValue.substring(11).split("|");
@@ -38,7 +40,7 @@ Item {
return "";
}
readonly property string svgCornerIcon: isSvgCorner ? (iconValue.substring(11).split("|")[1] || "") : ""
readonly property string iconPath: isMaterial || isUnicode || isSvg || isSvgCorner ? "" : Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue)
readonly property string iconPath: isMaterial || isUnicode || isSvg || isSvgCorner || isImage ? "" : Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue)
visible: iconValue !== undefined && iconValue !== ""
@@ -66,14 +68,23 @@ Item {
visible: root.isSvg || root.isSvgCorner
}
CachingImage {
id: cachingImg
anchors.fill: parent
imagePath: root.imagePath
maxCacheSize: root.iconSize * 2
visible: root.isImage && status === Image.Ready
}
IconImage {
id: iconImg
anchors.fill: parent
source: root.iconPath
smooth: true
backer.sourceSize: Qt.size(root.iconSize, root.iconSize)
mipmap: true
asynchronous: true
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && root.iconPath !== "" && status === Image.Ready
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && !root.isImage && root.iconPath !== "" && status === Image.Ready
}
Rectangle {
@@ -84,7 +95,7 @@ Item {
anchors.rightMargin: root.fallbackRightMargin
anchors.topMargin: root.fallbackTopMargin
anchors.bottomMargin: root.fallbackBottomMargin
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && (root.iconPath === "" || iconImg.status !== Image.Ready)
visible: !root.isMaterial && !root.isUnicode && !root.isSvg && !root.isSvgCorner && !root.isImage && (root.iconPath === "" || iconImg.status !== Image.Ready)
color: root.fallbackBackgroundColor
radius: Theme.cornerRadius
border.width: 0