summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-07-12 23:07:06 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-07-12 23:07:06 +0800
commitf2056f05b4649d49f9322d5fcea011a51bda2dfe (patch)
treef98436bb22fc75febce233e77c047d976d9ddfb7 /components
parent6c47b43925977fd203b82140bc1e6fd46765aecf (diff)
rename and reorganize
Diffstat (limited to 'components')
-rw-r--r--components/AppSearch.qml118
-rw-r--r--components/CircularProgress.qml91
-rw-r--r--components/IWindow.qml12
3 files changed, 221 insertions, 0 deletions
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<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
+ }))
+
+ function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> 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"
+}
+