1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-31 00:42: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. placeholder: e.g., PikaOS, Void Linux, etc.
validations: validations:
required: false required: false
- type: input - type: textarea
id: dms_version id: dms_doctor
attributes: attributes:
label: dms version label: dms doctor -v
description: Output of dms version command description: Output of `dms doctor -v` command
placeholder: e.g., 1.2.3 placeholder: Paste the output of `dms doctor -v` here
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

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

View File

@@ -96,6 +96,28 @@ Singleton {
saveSettings(); 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 dankBarLeftWidgetsModel: leftWidgetsModel
property alias dankBarCenterWidgetsModel: centerWidgetsModel property alias dankBarCenterWidgetsModel: centerWidgetsModel
property alias dankBarRightWidgetsModel: rightWidgetsModel property alias dankBarRightWidgetsModel: rightWidgetsModel

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,25 @@ Rectangle {
signal clicked signal clicked
signal rightClicked(real mouseX, real mouseY) 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 radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent" color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
@@ -25,88 +44,15 @@ Rectangle {
spacing: Theme.spacingS spacing: Theme.spacingS
width: parent.width - Theme.spacingM width: parent.width - Theme.spacingM
Item { AppIconRenderer {
width: iconSize width: root.computedIconSize
height: iconSize height: root.computedIconSize
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
iconValue: root.iconValue
property int iconSize: Math.min(48, Math.max(32, root.width * 0.45)) iconSize: root.computedIconSize
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
Image { iconColor: root.isSelected ? Theme.primary : Theme.surfaceText
id: appIcon materialIconSizeAdjustment: root.computedIconSize * 0.3
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
}
}
}
} }
StyledText { StyledText {

View File

@@ -16,6 +16,23 @@ Rectangle {
signal clicked signal clicked
signal rightClicked(real mouseX, real mouseY) 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 width: parent?.width ?? 200
height: 52 height: 52
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent" color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
@@ -27,86 +44,14 @@ Rectangle {
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingM spacing: Theme.spacingM
Item { AppIconRenderer {
width: 36 width: 36
height: 36 height: 36
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
iconValue: root.iconValue
Image { iconSize: 36
id: appIcon fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
anchors.fill: parent materialIconSizeAdjustment: 12
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
}
}
}
} }
Column { Column {

View File

@@ -21,22 +21,30 @@ Rectangle {
border.width: isSelected ? 2 : 0 border.width: isSelected ? 2 : 0
border.color: Theme.primary border.color: Theme.primary
readonly property string imageSource: { readonly property string iconValue: {
if (!item?.data) if (!item)
return ""; return "";
var data = item.data; var data = item.data;
if (data.imageUrl) if (data?.imageUrl)
return data.imageUrl; return "image:" + data.imageUrl;
if (data.imagePath) if (data?.imagePath)
return data.imagePath; return "image:" + data.imagePath;
if (data.path && isImageFile(data.path)) if (data?.path && isImageFile(data.path))
return data.path; return "image:" + data.path;
return ""; 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) { function isImageFile(path) {
if (!path) if (!path)
return false; return false;
@@ -55,39 +63,12 @@ Rectangle {
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
clip: true clip: true
CachingImage { AppIconRenderer {
anchors.fill: parent anchors.fill: parent
visible: root.useImage iconValue: root.iconValue
imagePath: root.imageSource iconSize: Math.min(parent.width, parent.height)
maxCacheSize: 256 fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
} materialIconSizeAdjustment: iconSize * 0.3
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
} }
Rectangle { Rectangle {

View File

@@ -200,7 +200,7 @@ Item {
} }
onPreviewRequested: { onPreviewRequested: {
textEditor.togglePreview() textEditor.togglePreview();
} }
onEscapePressed: { onEscapePressed: {
@@ -362,8 +362,8 @@ Item {
DankModal { DankModal {
id: confirmationDialog id: confirmationDialog
width: 400 modalWidth: 400
height: 180 modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 180
shouldBeVisible: false shouldBeVisible: false
allowStacking: true allowStacking: true
@@ -376,6 +376,7 @@ Item {
FocusScope { FocusScope {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
implicitHeight: contentColumn.implicitHeight
Keys.onEscapePressed: event => { Keys.onEscapePressed: event => {
confirmationDialog.close(); confirmationDialog.close();
@@ -384,47 +385,31 @@ Item {
} }
Column { Column {
anchors.centerIn: parent id: contentColumn
width: parent.width - Theme.spacingM * 2 anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
spacing: 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 width: parent.width
wrapMode: Text.Wrap
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;
}
}
} }
Item { Item {
width: parent.width width: parent.width
height: 40 height: 36
Row { Row {
anchors.right: parent.right 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 visible: !expanded
anchors.right: clearButton.visible ? clearButton.left : parent.right anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.bottom: parent.bottom anchors.top: collapsedContent.bottom
anchors.bottomMargin: contentSpacing anchors.topMargin: contentSpacing
spacing: contentSpacing spacing: contentSpacing
Repeater { Repeater {
@@ -614,8 +614,8 @@ Rectangle {
visible: !expanded && actionCount < 3 visible: !expanded && actionCount < 3
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
anchors.bottom: parent.bottom anchors.top: collapsedContent.bottom
anchors.bottomMargin: contentSpacing anchors.topMargin: contentSpacing
width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50) width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: actionButtonHeight height: actionButtonHeight
radius: Theme.spacingXS radius: Theme.spacingXS

View File

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

View File

@@ -581,6 +581,7 @@ Item {
property var allLauncherPlugins: { property var allLauncherPlugins: {
SettingsData.launcherPluginVisibility; SettingsData.launcherPluginVisibility;
SettingsData.launcherPluginOrder;
var plugins = []; var plugins = [];
var builtIn = AppSearchService.getBuiltInLauncherPlugins() || {}; var builtIn = AppSearchService.getBuiltInLauncherPlugins() || {};
for (var pluginId in builtIn) { for (var pluginId in builtIn) {
@@ -607,109 +608,175 @@ Item {
trigger: PluginService.getPluginTrigger(pluginId) || "" 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 { StyledText {
width: parent.width 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 font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Column { Column {
id: pluginVisibilityColumn
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: pluginVisibilityCard.allLauncherPlugins model: pluginVisibilityCard.allLauncherPlugins
delegate: Rectangle { delegate: Item {
id: visibilityDelegate id: visibilityDelegateItem
required property var modelData required property var modelData
required property int index required property int index
width: parent.width property bool held: pluginDragArea.pressed
property real originalY: y
width: pluginVisibilityColumn.width
height: 52 height: 52
radius: Theme.cornerRadius z: held ? 2 : 1
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
Row { Rectangle {
anchors.left: parent.left id: visibilityDelegate
anchors.leftMargin: Theme.spacingM width: parent.width
anchors.verticalCenter: parent.verticalCenter height: 52
spacing: Theme.spacingM radius: Theme.cornerRadius
color: visibilityDelegateItem.held ? Theme.surfaceHover : Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
Item { Row {
width: Theme.iconSize anchors.left: parent.left
height: Theme.iconSize anchors.leftMargin: 28
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon { Item {
anchors.centerIn: parent width: Theme.iconSize
visible: visibilityDelegate.modelData.iconType !== "unicode" height: Theme.iconSize
name: visibilityDelegate.modelData.icon anchors.verticalCenter: parent.verticalCenter
size: Theme.iconSize
color: Theme.primary 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 { Column {
anchors.centerIn: parent anchors.verticalCenter: parent.verticalCenter
visible: visibilityDelegate.modelData.iconType === "unicode" spacing: 2
text: visibilityDelegate.modelData.icon
font.pixelSize: Theme.iconSize Row {
color: Theme.primary 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 anchors.verticalCenter: parent.verticalCenter
spacing: 2 checked: SettingsData.getPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id)
onToggled: function (isChecked) {
Row { SettingsData.setPluginAllowWithoutTrigger(visibilityDelegateItem.modelData.id, isChecked);
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
} }
} }
} }
DankToggle { MouseArea {
id: visibilityToggle id: pluginDragArea
anchors.right: parent.right anchors.left: parent.left
anchors.rightMargin: Theme.spacingM 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 anchors.verticalCenter: parent.verticalCenter
checked: SettingsData.getPluginAllowWithoutTrigger(visibilityDelegate.modelData.id) name: "drag_indicator"
onToggled: function (isChecked) { size: 18
SettingsData.setPluginAllowWithoutTrigger(visibilityDelegate.modelData.id, isChecked); 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 isUnicode: iconValue.startsWith("unicode:")
readonly property bool isSvgCorner: iconValue.startsWith("svg+corner:") readonly property bool isSvgCorner: iconValue.startsWith("svg+corner:")
readonly property bool isSvg: !isSvgCorner && iconValue.startsWith("svg:") 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 materialName: isMaterial ? iconValue.substring(9) : ""
readonly property string unicodeChar: isUnicode ? iconValue.substring(8) : "" readonly property string unicodeChar: isUnicode ? iconValue.substring(8) : ""
readonly property string imagePath: isImage ? iconValue.substring(6) : ""
readonly property string svgSource: { readonly property string svgSource: {
if (isSvgCorner) { if (isSvgCorner) {
const parts = iconValue.substring(11).split("|"); const parts = iconValue.substring(11).split("|");
@@ -38,7 +40,7 @@ Item {
return ""; return "";
} }
readonly property string svgCornerIcon: isSvgCorner ? (iconValue.substring(11).split("|")[1] || "") : "" 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 !== "" visible: iconValue !== undefined && iconValue !== ""
@@ -66,14 +68,23 @@ Item {
visible: root.isSvg || root.isSvgCorner 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 { IconImage {
id: iconImg id: iconImg
anchors.fill: parent anchors.fill: parent
source: root.iconPath source: root.iconPath
smooth: true backer.sourceSize: Qt.size(root.iconSize, root.iconSize)
mipmap: true
asynchronous: 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 { Rectangle {
@@ -84,7 +95,7 @@ Item {
anchors.rightMargin: root.fallbackRightMargin anchors.rightMargin: root.fallbackRightMargin
anchors.topMargin: root.fallbackTopMargin anchors.topMargin: root.fallbackTopMargin
anchors.bottomMargin: root.fallbackBottomMargin 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 color: root.fallbackBackgroundColor
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 0 border.width: 0