From f2056f05b4649d49f9322d5fcea011a51bda2dfe Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Sat, 12 Jul 2025 23:07:06 +0800 Subject: rename and reorganize --- bar/ActiveWindow.qml | 2 +- components/AppSearch.qml | 118 ++++++++++++++++++++++++++++++++++++++++ components/CircularProgress.qml | 91 +++++++++++++++++++++++++++++++ components/IWindow.qml | 12 ++++ utils/AppSearch.qml | 118 ---------------------------------------- utils/CircularProgress.qml | 91 ------------------------------- utils/IWindow.qml | 12 ---- 7 files changed, 222 insertions(+), 222 deletions(-) create mode 100644 components/AppSearch.qml create mode 100644 components/CircularProgress.qml create mode 100644 components/IWindow.qml delete mode 100644 utils/AppSearch.qml delete mode 100644 utils/CircularProgress.qml delete mode 100644 utils/IWindow.qml diff --git a/bar/ActiveWindow.qml b/bar/ActiveWindow.qml index a95a68a..25e87e2 100644 --- a/bar/ActiveWindow.qml +++ b/bar/ActiveWindow.qml @@ -3,7 +3,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Widgets -import qs.utils +import qs.components import qs.config Item { diff --git a/components/AppSearch.qml b/components/AppSearch.qml new file mode 100644 index 0000000..9336fc0 --- /dev/null +++ b/components/AppSearch.qml @@ -0,0 +1,118 @@ +pragma Singleton + +import "../utils/fuzzysort.js" as Fuzzy +import "../utils/levendist.js" as Levendist +import Quickshell +import Quickshell.Io +import qs.config + +/** + * - Eases fuzzy searching for applications by name + * - Guesses icon name for window class name + */ +Singleton { + id: root + property bool sloppySearch: General.sloppySearch ?? false + property real scoreThreshold: 0.2 + 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", + "brave-browser": "brave-desktop" + }) + 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" + } + ] + + readonly property list 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 + })) + + function fuzzyQuery(search: string): var { // Idk why list doesn't work + if (root.sloppySearch) { + const results = list.map(obj => ({ + entry: obj, + score: Levendist.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 iconExists(iconName) { + if (!iconName || iconName.length == 0) + return false; + return (Quickshell.iconPath(iconName, true).length > 0) && !iconName.includes("image-missing"); + } + + 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 fuzzy 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; + } +} diff --git a/components/CircularProgress.qml b/components/CircularProgress.qml new file mode 100644 index 0000000..240e520 --- /dev/null +++ b/components/CircularProgress.qml @@ -0,0 +1,91 @@ +// From https://github.com/rafzby/circular-progressbar with modifications +// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo + +import QtQuick + +/** + * Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs + */ +Item { + id: root + + property int size: 30 + property int lineWidth: 2 + property real value: 0 + // property color primaryColor: Appearance.m3colors.m3onSecondaryContainer + // property color secondaryColor: Appearance.colors.colSecondaryContainer + property real gapAngle: Math.PI / 9 + property bool fill: false + property int fillOverflow: 2 + property int animationDuration: 1000 + property var easingType: Easing.OutCubic + + width: size + height: size + + signal animationFinished + + onValueChanged: { + canvas.degree = value * 360; + } + // onPrimaryColorChanged: { + // canvas.requestPaint(); + // } + // onSecondaryColorChanged: { + // canvas.requestPaint(); + // } + + Canvas { + id: canvas + + property real degree: 0 + + anchors.fill: parent + antialiasing: true + + onDegreeChanged: { + requestPaint(); + } + + onPaint: { + var ctx = getContext("2d"); + var x = root.width / 2; + var y = root.height / 2; + var radius = root.size / 2 - root.lineWidth; + var startAngle = (Math.PI / 180) * 270; + var fullAngle = (Math.PI / 180) * (270 + 360); + var progressAngle = (Math.PI / 180) * (270 + degree); + var epsilon = 0.01; // Small angle in radians + + ctx.reset(); + if (root.fill) { + // ctx.fillStyle = root.secondaryColor; + ctx.beginPath(); + ctx.arc(x, y, radius + fillOverflow, startAngle, fullAngle); + ctx.fill(); + } + ctx.lineCap = 'round'; + ctx.lineWidth = root.lineWidth; + + // Secondary + ctx.beginPath(); + ctx.arc(x, y, radius, progressAngle + gapAngle, fullAngle - gapAngle); + // ctx.strokeStyle = root.secondaryColor; + ctx.stroke(); + + // Primary (value indication) + var endAngle = progressAngle + (value > 0 ? 0 : epsilon); + ctx.beginPath(); + ctx.arc(x, y, radius, startAngle, endAngle); + // ctx.strokeStyle = root.primaryColor; + ctx.stroke(); + } + + Behavior on degree { + NumberAnimation { + duration: root.animationDuration + easing.type: root.easingType + } + } + } +} diff --git a/components/IWindow.qml b/components/IWindow.qml new file mode 100644 index 0000000..85f2205 --- /dev/null +++ b/components/IWindow.qml @@ -0,0 +1,12 @@ +import Quickshell +import Quickshell.Wayland + +PanelWindow { + required property string name + required property ShellScreen modelData + + screen: modelData + WlrLayershell.namespace: `${name}` + color: "transparent" +} + diff --git a/utils/AppSearch.qml b/utils/AppSearch.qml deleted file mode 100644 index 6d1ad15..0000000 --- a/utils/AppSearch.qml +++ /dev/null @@ -1,118 +0,0 @@ -pragma Singleton - -import "fuzzysort.js" as Fuzzy -import "levendist.js" as Levendist -import Quickshell -import Quickshell.Io -import qs.config - -/** - * - Eases fuzzy searching for applications by name - * - Guesses icon name for window class name - */ -Singleton { - id: root - property bool sloppySearch: General.sloppySearch ?? false - property real scoreThreshold: 0.2 - 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", - "brave-browser": "brave-desktop" - }) - 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" - } - ] - - readonly property list 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 - })) - - function fuzzyQuery(search: string): var { // Idk why list doesn't work - if (root.sloppySearch) { - const results = list.map(obj => ({ - entry: obj, - score: Levendist.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 iconExists(iconName) { - if (!iconName || iconName.length == 0) - return false; - return (Quickshell.iconPath(iconName, true).length > 0) && !iconName.includes("image-missing"); - } - - 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 fuzzy 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; - } -} diff --git a/utils/CircularProgress.qml b/utils/CircularProgress.qml deleted file mode 100644 index 240e520..0000000 --- a/utils/CircularProgress.qml +++ /dev/null @@ -1,91 +0,0 @@ -// From https://github.com/rafzby/circular-progressbar with modifications -// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo - -import QtQuick - -/** - * Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs - */ -Item { - id: root - - property int size: 30 - property int lineWidth: 2 - property real value: 0 - // property color primaryColor: Appearance.m3colors.m3onSecondaryContainer - // property color secondaryColor: Appearance.colors.colSecondaryContainer - property real gapAngle: Math.PI / 9 - property bool fill: false - property int fillOverflow: 2 - property int animationDuration: 1000 - property var easingType: Easing.OutCubic - - width: size - height: size - - signal animationFinished - - onValueChanged: { - canvas.degree = value * 360; - } - // onPrimaryColorChanged: { - // canvas.requestPaint(); - // } - // onSecondaryColorChanged: { - // canvas.requestPaint(); - // } - - Canvas { - id: canvas - - property real degree: 0 - - anchors.fill: parent - antialiasing: true - - onDegreeChanged: { - requestPaint(); - } - - onPaint: { - var ctx = getContext("2d"); - var x = root.width / 2; - var y = root.height / 2; - var radius = root.size / 2 - root.lineWidth; - var startAngle = (Math.PI / 180) * 270; - var fullAngle = (Math.PI / 180) * (270 + 360); - var progressAngle = (Math.PI / 180) * (270 + degree); - var epsilon = 0.01; // Small angle in radians - - ctx.reset(); - if (root.fill) { - // ctx.fillStyle = root.secondaryColor; - ctx.beginPath(); - ctx.arc(x, y, radius + fillOverflow, startAngle, fullAngle); - ctx.fill(); - } - ctx.lineCap = 'round'; - ctx.lineWidth = root.lineWidth; - - // Secondary - ctx.beginPath(); - ctx.arc(x, y, radius, progressAngle + gapAngle, fullAngle - gapAngle); - // ctx.strokeStyle = root.secondaryColor; - ctx.stroke(); - - // Primary (value indication) - var endAngle = progressAngle + (value > 0 ? 0 : epsilon); - ctx.beginPath(); - ctx.arc(x, y, radius, startAngle, endAngle); - // ctx.strokeStyle = root.primaryColor; - ctx.stroke(); - } - - Behavior on degree { - NumberAnimation { - duration: root.animationDuration - easing.type: root.easingType - } - } - } -} diff --git a/utils/IWindow.qml b/utils/IWindow.qml deleted file mode 100644 index 85f2205..0000000 --- a/utils/IWindow.qml +++ /dev/null @@ -1,12 +0,0 @@ -import Quickshell -import Quickshell.Wayland - -PanelWindow { - required property string name - required property ShellScreen modelData - - screen: modelData - WlrLayershell.namespace: `${name}` - color: "transparent" -} - -- cgit v1.2.3