desktop icons

This commit is contained in:
Zacharias-Brohn
2026-03-12 14:45:20 +01:00
parent 851b78f0ff
commit 9e9708ed12
11 changed files with 561 additions and 156 deletions
+15
View File
@@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Controls
import qs.Config
TextInput {
renderType: Text.NativeRendering
selectedTextColor: DynamicColors.palette.m3onSecondaryContainer
selectionColor: DynamicColors.tPalette.colSecondaryContainer
font {
family: Appearance?.font.family.sans ?? "sans-serif"
hintingPreference: Font.PreferFullHinting
pixelSize: Appearance?.font.size.normal ?? 15
}
}
+1
View File
@@ -168,6 +168,7 @@ Singleton {
return { return {
logo: general.logo, logo: general.logo,
wallpaperPath: general.wallpaperPath, wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons,
color: { color: {
wallust: general.color.wallust, wallust: general.color.wallust,
mode: general.color.mode, mode: general.color.mode,
+1
View File
@@ -6,6 +6,7 @@ JsonObject {
} }
property Color color: Color { property Color color: Color {
} }
property bool desktopIcons: false
property Idle idle: Idle { property Idle idle: Idle {
} }
property string logo: "" property string logo: ""
+122
View File
@@ -0,0 +1,122 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import "../scripts/levendist.js" as Levendist
import "../scripts/fuzzysort.js" as Fuzzy
import qs.Config
Singleton {
id: root
readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values).sort((a, b) => a.name.localeCompare(b.name))
readonly property var preppedNames: list.map(a => ({
name: Fuzzy.prepare(`${a.name} `),
entry: a
}))
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property real scoreThreshold: 0.2
property bool sloppySearch: Config.options?.search.sloppy ?? false
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot",
"zen": "zen-browser"
})
signal reload
function computeScore(...args) {
return Levendist.computeScore(...args);
}
function computeTextMatchScore(...args) {
return Levendist.computeTextMatchScore(...args);
}
function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> doesn't work
if (root.sloppySearch) {
const results = list.map(obj => ({
entry: obj,
score: computeScore(obj.name.toLowerCase(), search.toLowerCase())
})).filter(item => item.score > root.scoreThreshold).sort((a, b) => b.score - a.score);
return results.map(item => item.entry);
}
return Fuzzy.go(search, preppedNames, {
all: true,
key: "name"
}).map(r => {
return r.obj.entry;
});
}
function guessIcon(str) {
if (!str || str.length == 0)
return "image-missing";
// Normal substitutions
if (substitutions[str])
return substitutions[str];
// Regex substitutions
for (let i = 0; i < regexSubstitutions.length; i++) {
const substitution = regexSubstitutions[i];
const replacedName = str.replace(substitution.regex, substitution.replace);
if (replacedName != str)
return replacedName;
}
// If it gets detected normally, no need to guess
if (iconExists(str))
return str;
let guessStr = str;
// Guess: Take only app name of reverse domain name notation
guessStr = str.split('.').slice(-1)[0].toLowerCase();
if (iconExists(guessStr))
return guessStr;
// Guess: normalize to kebab case
guessStr = str.toLowerCase().replace(/\s+/g, "-");
if (iconExists(guessStr))
return guessStr;
// Guess: First fuzze desktop entry match
const searchResults = root.fuzzyQuery(str);
if (searchResults.length > 0) {
const firstEntry = searchResults[0];
guessStr = firstEntry.icon;
if (iconExists(guessStr))
return guessStr;
}
// Give up
return str;
}
function iconExists(iconName) {
if (!iconName || iconName.length == 0)
return false;
return (Quickshell.iconPath(iconName, true).length > 0) && !iconName.includes("image-missing");
}
}
+80 -48
View File
@@ -6,6 +6,7 @@ import Quickshell.Hyprland
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Paths import qs.Paths
import qs.Helpers
Item { Item {
id: root id: root
@@ -40,24 +41,31 @@ Item {
ColumnLayout { ColumnLayout {
id: menuLayout id: menuLayout
anchors.fill: parent anchors.centerIn: parent
anchors.margins: popupBackground.padding
spacing: 0 spacing: 0
StateLayer { CustomRect {
Layout.fillWidth: true Layout.preferredWidth: 200
radius: popupBackground.radius - popupBackground.padding
implicitHeight: openTerminalRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: openTerminalRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "terminal"; font.pointSize: 20 } MaterialIcon { text: "terminal"; font.pointSize: 20 }
CustomText { text: "Open terminal"; Layout.fillWidth: true } CustomText { text: "Open terminal"; Layout.fillWidth: true }
} }
onClicked: { StateLayer {
Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", FileUtils.trimFileProtocol(Paths.desktop)]) anchors.fill: parent
root.close()
onClicked: {
Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", FileUtils.trimFileProtocol(Paths.desktop)])
root.close()
}
} }
} }
@@ -69,78 +77,102 @@ Item {
Layout.bottomMargin: 4 Layout.bottomMargin: 4
} }
StateLayer { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: settingsRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: settingsRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "settings"; font.pointSize: 20 } MaterialIcon { text: "settings"; font.pointSize: 20 }
CustomText { text: "Sleex settings"; Layout.fillWidth: true } CustomText { text: "ZShell settings"; Layout.fillWidth: true }
} }
onClicked: { StateLayer {
Quickshell.execDetached(["qs", "-p", "/usr/share/sleex/settings.qml"]) anchors.fill: parent
root.close()
onClicked: {
Quickshell.execDetached(["qs", "-p", "/usr/share/sleex/settings.qml"])
root.close()
}
} }
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 1 implicitHeight: 1
color: Appearance.m3colors.m3outlineVariant color: DynamicColors.palette.m3outlineVariant
Layout.topMargin: 4 Layout.topMargin: 4
Layout.bottomMargin: 4 Layout.bottomMargin: 4
} }
StateLayer { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: logoutRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: logoutRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "logout"; font.pointSize: 20 } MaterialIcon { text: "logout"; font.pointSize: 20 }
CustomText { text: "Logout"; Layout.fillWidth: true } CustomText { text: "Logout"; Layout.fillWidth: true }
} }
onClicked: { StateLayer {
Hyprland.dispatch("global quickshell:sessionOpen")
root.close()
}
}
CustomRect {
Layout.fillWidth: true
implicitHeight: 1
color: Appearance.m3colors.m3outlineVariant
Layout.topMargin: 4
Layout.bottomMargin: 4
}
StateLayer {
Layout.fillWidth: true
contentItem: RowLayout {
spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12
MaterialIcon { text: Config.options.background.showDesktopIcons ? "visibility_off" : "visibility"; font.pointSize: 20 }
CustomText { text: Config.options.background.showDesktopIcons ? "Hide icons" : "Show icons"; Layout.fillWidth: true }
}
onClicked: { onClicked: {
Config.options.background.showDesktopIcons = !Config.options.background.showDesktopIcons Hyprland.dispatch("global quickshell:sessionOpen")
root.close() root.close()
}
} }
} }
// CustomRect {
// Layout.fillWidth: true
// implicitHeight: 1
// color: DynamicColors.palette.m3outlineVariant
// Layout.topMargin: 4
// Layout.bottomMargin: 4
// }
//
// CustomRect {
// Layout.fillWidth: true
// radius: popupBackground.radius - popupBackground.padding
// implicitHeight: desktopIconsRow.implicitHeight + Appearance.padding.small * 2
//
// RowLayout {
// id: desktopIconsRow
// spacing: 8
// anchors.fill: parent
// anchors.leftMargin: Appearance.padding.smaller
//
// MaterialIcon { text: Config.options.background.showDesktopIcons ? "visibility_off" : "visibility"; font.pointSize: 20 }
// CustomText { text: Config.options.background.showDesktopIcons ? "Hide icons" : "Show icons"; Layout.fillWidth: true }
// }
//
// StateLayer {
// anchors.fill: parent
//
// onClicked: {
// Config.options.background.showDesktopIcons = !Config.options.background.showDesktopIcons
// root.close()
// }
// }
// }
} }
} }
function openAt(mouseX, mouseY, parentW, parentH) { function openAt(mouseX, mouseY, parentW, parentH) {
menuX = Math.min(mouseX, parentW - popupBackground.implicitWidth) menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth))
menuY = Math.min(mouseY, parentH - popupBackground.implicitHeight) menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight))
visible = true visible = true
} }
+96 -57
View File
@@ -24,14 +24,9 @@ Item {
property real menuX: 0 property real menuX: 0
property real menuY: 0 property real menuY: 0
MouseArea {
anchors.fill: parent
onClicked: contextMenu.close()
}
CustomClippingRect { CustomClippingRect {
id: popupBackground id: popupBackground
readonly property real padding: 4 readonly property real padding: Appearance.padding.small
x: contextMenu.menuX x: contextMenu.menuX
y: contextMenu.menuY y: contextMenu.menuY
@@ -47,49 +42,68 @@ Item {
ColumnLayout { ColumnLayout {
id: menuLayout id: menuLayout
anchors.fill: parent anchors.centerIn: parent
anchors.margins: popupBackground.padding
spacing: 0 spacing: 0
StateLayer { CustomRect {
Layout.fillWidth: true Layout.preferredWidth: 160
radius: popupBackground.radius - popupBackground.padding
implicitHeight: openRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: openRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "open_in_new"; font.pointSize: 20 } MaterialIcon { text: "open_in_new"; font.pointSize: 20 }
CustomText { text: "Open"; Layout.fillWidth: true } CustomText { text: "Open"; Layout.fillWidth: true }
} }
onClicked: { StateLayer {
for (let i = 0; i < contextMenu.targetPaths.length; i++) { anchors.fill: parent
let p = contextMenu.targetPaths[i];
if (p === contextMenu.targetFilePath) { onClicked: {
if (p.endsWith(".desktop") && contextMenu.targetAppEntry) contextMenu.targetAppEntry.execute() for (let i = 0; i < contextMenu.targetPaths.length; i++) {
else contextMenu.openFileRequested(p, contextMenu.targetIsDir) let p = contextMenu.targetPaths[i];
} else { if (p === contextMenu.targetFilePath) {
Quickshell.execDetached(["xdg-open", p]) if (p.endsWith(".desktop") && contextMenu.targetAppEntry) contextMenu.targetAppEntry.execute()
else contextMenu.openFileRequested(p, contextMenu.targetIsDir)
} else {
Quickshell.execDetached(["xdg-open", p])
}
} }
contextMenu.close()
} }
contextMenu.close()
} }
} }
StateLayer { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: openWithRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: openWithRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: contextMenu.targetIsDir ? "terminal" : "apps"; font.pointSize: 20 } MaterialIcon { text: contextMenu.targetIsDir ? "terminal" : "apps"; font.pointSize: 20 }
CustomText { text: contextMenu.targetIsDir ? "Open in terminal" : "Open with..."; Layout.fillWidth: true } CustomText { text: contextMenu.targetIsDir ? "Open in terminal" : "Open with..."; Layout.fillWidth: true }
} }
onClicked: { StateLayer {
Quickshell.execDetached(["xdg-open", contextMenu.targetFilePath]) anchors.fill: parent
contextMenu.close()
onClicked: {
if (contextMenu.targetIsDir) {
Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", contextMenu.targetFilePath])
} else {
Quickshell.execDetached(["xdg-open", contextMenu.targetFilePath])
}
contextMenu.close()
}
} }
} }
@@ -101,74 +115,99 @@ Item {
Layout.bottomMargin: 4 Layout.bottomMargin: 4
} }
StateLayer { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: copyPathRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: copyPathRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "content_copy"; font.pointSize: 20 } MaterialIcon { text: "content_copy"; font.pointSize: 20 }
CustomText { text: "Copy path"; Layout.fillWidth: true } CustomText { text: "Copy path"; Layout.fillWidth: true }
} }
onClicked: { StateLayer {
Quickshell.execDetached(["wl-copy", contextMenu.targetPaths.join("\n")]) anchors.fill: parent
contextMenu.close()
onClicked: {
Quickshell.execDetached(["wl-copy", contextMenu.targetPaths.join("\n")])
contextMenu.close()
}
} }
} }
StateLayer { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
visible: contextMenu.targetPaths.length === 1 visible: contextMenu.targetPaths.length === 1
radius: popupBackground.radius - popupBackground.padding
implicitHeight: renameRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: renameRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "edit"; font.pointSize: 20 } MaterialIcon { text: "edit"; font.pointSize: 20 }
CustomText { text: "Rename"; Layout.fillWidth: true } CustomText { text: "Rename"; Layout.fillWidth: true }
} }
onClicked: { StateLayer {
contextMenu.renameRequested(contextMenu.targetFilePath) anchors.fill: parent
contextMenu.close()
onClicked: {
contextMenu.renameRequested(contextMenu.targetFilePath)
contextMenu.close()
}
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 1 implicitHeight: 1
color: Appearance.m3colors.m3outlineVariant color: DynamicColors.palette.m3outlineVariant
Layout.topMargin: 4 Layout.topMargin: 4
Layout.bottomMargin: 4 Layout.bottomMargin: 4
} }
StateLayer { CustomRect {
id: deleteButton
Layout.fillWidth: true Layout.fillWidth: true
colBackgroundHover: Appearance.colors.colError radius: popupBackground.radius - popupBackground.padding
implicitHeight: deleteRow.implicitHeight + Appearance.padding.small * 2
contentItem: RowLayout { RowLayout {
id: deleteRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { MaterialIcon {
text: "delete"; text: "delete"
font.pointSize: 20; font.pointSize: 20
color: deleteButton.hovered ? Appearance.colors.colOnError : Appearance.colors.colError color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
} }
CustomText { CustomText {
text: "Move to trash"; text: "Move to trash"
Layout.fillWidth: true; Layout.fillWidth: true
color: deleteButton.hovered ? Appearance.colors.colOnError : Appearance.colors.colError color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
} }
} }
onClicked: { StateLayer {
let cmd = ["gio", "trash"].concat(contextMenu.targetPaths) id: deleteButton
Quickshell.execDetached(cmd) anchors.fill: parent
contextMenu.close() color: DynamicColors.tPalette.m3error
onClicked: {
let cmd = ["gio", "trash"].concat(contextMenu.targetPaths)
Quickshell.execDetached(cmd)
contextMenu.close()
}
} }
} }
} }
@@ -181,8 +220,8 @@ Item {
targetPaths = (selectionArray && selectionArray.length > 0) ? selectionArray : [path] targetPaths = (selectionArray && selectionArray.length > 0) ? selectionArray : [path]
menuX = Math.min(mouseX, parentW - popupBackground.implicitWidth) menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth))
menuY = Math.min(mouseY, parentH - popupBackground.implicitHeight) menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight))
visible = true visible = true
} }
+2 -1
View File
@@ -15,6 +15,7 @@ Item {
property int gridX: model.gridX property int gridX: model.gridX
property int gridY: model.gridY property int gridY: model.gridY
property bool isSnapping: snapAnimX.running || snapAnimY.running property bool isSnapping: snapAnimX.running || snapAnimY.running
property bool lassoActive
property string resolvedIcon: { property string resolvedIcon: {
if (fileName.endsWith(".desktop")) { if (fileName.endsWith(".desktop")) {
if (appEntry && appEntry.icon && appEntry.icon !== "") if (appEntry && appEntry.icon && appEntry.icon !== "")
@@ -214,7 +215,7 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: root.lassoActive ? undefined : Qt.PointingHandCursor
drag.target: dragContainer drag.target: dragContainer
hoverEnabled: true hoverEnabled: true
+57 -13
View File
@@ -2,6 +2,8 @@ import QtQuick
import Quickshell import Quickshell
import qs.Modules import qs.Modules
import qs.Helpers import qs.Helpers
import qs.Config
import qs.Components
import qs.Paths import qs.Paths
import ZShell.Services import ZShell.Services
@@ -15,6 +17,7 @@ Item {
property string editingFilePath: "" property string editingFilePath: ""
property real groupDragX: 0 property real groupDragX: 0
property real groupDragY: 0 property real groupDragY: 0
property bool lassoActive: false
property var selectedIcons: [] property var selectedIcons: []
property real startX: 0 property real startX: 0
property real startY: 0 property real startY: 0
@@ -54,7 +57,6 @@ Item {
root.groupDragY = 0; root.groupDragY = 0;
} }
anchors.fill: parent
focus: true focus: true
Keys.onPressed: event => { Keys.onPressed: event => {
@@ -68,15 +70,55 @@ Item {
Component.onCompleted: loadDirectory(FileUtils.trimFileProtocol(Paths.desktop)) Component.onCompleted: loadDirectory(FileUtils.trimFileProtocol(Paths.desktop))
} }
Rectangle { CustomRect {
id: lasso id: lasso
border.color: Appearance.colors.colPrimary function hideLasso() {
fadeIn.stop();
fadeOut.start();
root.lassoActive = false;
}
function showLasso() {
root.lassoActive = true;
fadeOut.stop();
visible = true;
fadeIn.start();
}
border.color: DynamicColors.palette.m3primary
border.width: 1 border.width: 1
color: DynamicColors.tPalette.m3primary color: DynamicColors.tPalette.m3primary
opacity: 0
radius: Appearance.rounding.small radius: Appearance.rounding.small
visible: false visible: false
z: 99 z: 99
NumberAnimation {
id: fadeIn
duration: 120
from: 0
property: "opacity"
target: lasso
to: 1
}
SequentialAnimation {
id: fadeOut
NumberAnimation {
duration: 120
from: lasso.opacity
property: "opacity"
target: lasso
to: 0
}
ScriptAction {
script: lasso.visible = false
}
}
} }
MouseArea { MouseArea {
@@ -85,10 +127,10 @@ Item {
onPositionChanged: mouse => { onPositionChanged: mouse => {
if (lasso.visible) { if (lasso.visible) {
lasso.x = Math.min(mouse.x, root.startX); lasso.x = Math.floor(Math.min(mouse.x, root.startX));
lasso.y = Math.min(mouse.y, root.startY); lasso.y = Math.floor(Math.min(mouse.y, root.startY));
lasso.width = Math.abs(mouse.x - root.startX); lasso.width = Math.floor(Math.abs(mouse.x - root.startX));
lasso.height = Math.abs(mouse.y - root.startY); lasso.height = Math.floor(Math.abs(mouse.y - root.startY));
let minCol = Math.floor((lasso.x - gridArea.x) / cellWidth); let minCol = Math.floor((lasso.x - gridArea.x) / cellWidth);
let maxCol = Math.floor((lasso.x + lasso.width - gridArea.x) / cellWidth); let maxCol = Math.floor((lasso.x + lasso.width - gridArea.x) / cellWidth);
@@ -115,17 +157,17 @@ Item {
} else { } else {
bgContextMenu.close(); bgContextMenu.close();
root.selectedIcons = []; root.selectedIcons = [];
root.startX = mouse.x; root.startX = Math.floor(mouse.x);
root.startY = mouse.y; root.startY = Math.floor(mouse.y);
lasso.x = mouse.x; lasso.x = Math.floor(mouse.x);
lasso.y = mouse.y; lasso.y = Math.floor(mouse.y);
lasso.width = 0; lasso.width = 0;
lasso.height = 0; lasso.height = 0;
lasso.visible = true; lasso.showLasso();
} }
} }
onReleased: { onReleased: {
lasso.visible = false; lasso.hideLasso();
} }
} }
@@ -142,6 +184,8 @@ Item {
delegate: DesktopIconDelegate { delegate: DesktopIconDelegate {
property int itemIndex: index property int itemIndex: index
lassoActive: root.lassoActive
} }
} }
} }
+8 -1
View File
@@ -32,7 +32,14 @@ Loader {
WallBackground { WallBackground {
} }
DesktopIcons { Loader {
id: loader
active: Config.general.desktopIcons
anchors.fill: parent
sourceComponent: DesktopIcons {
}
} }
} }
} }
+1 -1
View File
@@ -10,7 +10,7 @@ Singleton {
readonly property string cache: `${Quickshell.env("XDG_CACHE_HOME") || `${home}/.cache`}/zshell` readonly property string cache: `${Quickshell.env("XDG_CACHE_HOME") || `${home}/.cache`}/zshell`
readonly property string config: `${Quickshell.env("XDG_CONFIG_HOME") || `${home}/.config`}/zshell` readonly property string config: `${Quickshell.env("XDG_CONFIG_HOME") || `${home}/.config`}/zshell`
readonly property string data: `${Quickshell.env("XDG_DATA_HOME") || `${home}/.local/share`}/zshell` readonly property string data: `${Quickshell.env("XDG_DATA_HOME") || `${home}/.local/share`}/zshell`
readonly property string desktop: `${Quickshell.env("XDG_DATA_HOME") || `${home}/Desktop`}` readonly property string desktop: `${Quickshell.env("HOME")}/Desktop`
readonly property string home: Quickshell.env("HOME") readonly property string home: Quickshell.env("HOME")
readonly property string imagecache: `${cache}/imagecache` readonly property string imagecache: `${cache}/imagecache`
readonly property string libdir: Quickshell.env("ZSHELL_LIB_DIR") || "/usr/lib/zshell" readonly property string libdir: Quickshell.env("ZSHELL_LIB_DIR") || "/usr/lib/zshell"
+143
View File
@@ -0,0 +1,143 @@
// Original code from https://github.com/koeqaife/hyprland-material-you
// Original code license: GPLv3
// Translated to Js from Cython with an LLM and reviewed
function min3(a, b, c) {
return a < b && a < c ? a : b < c ? b : c;
}
function max3(a, b, c) {
return a > b && a > c ? a : b > c ? b : c;
}
function min2(a, b) {
return a < b ? a : b;
}
function max2(a, b) {
return a > b ? a : b;
}
function levenshteinDistance(s1, s2) {
let len1 = s1.length;
let len2 = s2.length;
if (len1 === 0) return len2;
if (len2 === 0) return len1;
if (len2 > len1) {
[s1, s2] = [s2, s1];
[len1, len2] = [len2, len1];
}
let prev = new Array(len2 + 1);
let curr = new Array(len2 + 1);
for (let j = 0; j <= len2; j++) {
prev[j] = j;
}
for (let i = 1; i <= len1; i++) {
curr[0] = i;
for (let j = 1; j <= len2; j++) {
let cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
}
[prev, curr] = [curr, prev];
}
return prev[len2];
}
function partialRatio(shortS, longS) {
let lenS = shortS.length;
let lenL = longS.length;
let best = 0.0;
if (lenS === 0) return 1.0;
for (let i = 0; i <= lenL - lenS; i++) {
let sub = longS.slice(i, i + lenS);
let dist = levenshteinDistance(shortS, sub);
let score = 1.0 - dist / lenS;
if (score > best) best = score;
}
return best;
}
function computeScore(s1, s2) {
if (s1 === s2) return 1.0;
let dist = levenshteinDistance(s1, s2);
let maxLen = max2(s1.length, s2.length);
if (maxLen === 0) return 1.0;
let full = 1.0 - dist / maxLen;
let part =
s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
let score = 0.85 * full + 0.15 * part;
if (s1 && s2 && s1[0] !== s2[0]) {
score -= 0.05;
}
let lenDiff = Math.abs(s1.length - s2.length);
if (lenDiff >= 3) {
score -= (0.05 * lenDiff) / maxLen;
}
let commonPrefixLen = 0;
let minLen = min2(s1.length, s2.length);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
}
}
score += 0.02 * commonPrefixLen;
if (s1.includes(s2) || s2.includes(s1)) {
score += 0.06;
}
return Math.max(0.0, Math.min(1.0, score));
}
function computeTextMatchScore(s1, s2) {
if (s1 === s2) return 1.0;
let dist = levenshteinDistance(s1, s2);
let maxLen = max2(s1.length, s2.length);
if (maxLen === 0) return 1.0;
let full = 1.0 - dist / maxLen;
let part =
s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);
let score = 0.4 * full + 0.6 * part;
let lenDiff = Math.abs(s1.length - s2.length);
if (lenDiff >= 10) {
score -= (0.02 * lenDiff) / maxLen;
}
let commonPrefixLen = 0;
let minLen = min2(s1.length, s2.length);
for (let i = 0; i < minLen; i++) {
if (s1[i] === s2[i]) {
commonPrefixLen++;
} else {
break;
}
}
score += 0.01 * commonPrefixLen;
if (s1.includes(s2) || s2.includes(s1)) {
score += 0.2;
}
return Math.max(0.0, Math.min(1.0, score));
}