summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-08-11 19:16:05 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-08-14 15:37:02 +0800
commitb17ae550381232fd8c0cce5024e8ec0852918a33 (patch)
treecaad80336bcc1ff6c9a8f576cd7dfe3aa26db242
parent6a910bb63463facb7c5b72c2054740154934c7a4 (diff)
feat(shell): implement standalone function for dynamic component loading
feat(Searchable): add Include algorithm and update filtering logic fix(Launcher): refactor state handling and improve selection logic fix(Clip): update clipHist property initialization and selection state sync refactor(IComponent): enhance state management and streamline properties refactor(Clip): change algorithm to Include for improved search functionality
-rw-r--r--components/Searchable.qml6
-rw-r--r--launcher/Launcher.qml46
-rw-r--r--launcher/components/Clip.qml10
-rw-r--r--launcher/components/IComponent.qml14
-rw-r--r--services/Clip.qml2
-rw-r--r--shell.qml42
6 files changed, 87 insertions, 33 deletions
diff --git a/components/Searchable.qml b/components/Searchable.qml
index 9501827..50e73fe 100644
--- a/components/Searchable.qml
+++ b/components/Searchable.qml
@@ -12,7 +12,8 @@ Singleton {
enum SearchAlgorithm {
Levendist,
Fzf,
- Fuzzysort
+ Fuzzysort,
+ Include
}
property int algorithm: Searchable.SearchAlgorithm.Levendist
property int scoreThreshold: 50
@@ -224,6 +225,9 @@ Singleton {
return out;
}
+ if (algorithm === Searchable.SearchAlgorithm.Include)
+ return list.filter(item => keys.some(k => transformSearch(fieldString(item, k)).includes(search)));
+
return [];
}
}
diff --git a/launcher/Launcher.qml b/launcher/Launcher.qml
index f78eb9c..8ac9032 100644
--- a/launcher/Launcher.qml
+++ b/launcher/Launcher.qml
@@ -9,6 +9,10 @@ import qs.config
// TODO: currently, we're relying on the compositor to provide the animation which ig is fine, but we should use our own in the future, however it might look weird combined with the compositors's
ILauncher {
id: launcher
+
+ property list<Component> widgetItems: Launcher.widgets
+ property var state: SelectionState
+
name: "quickshell::launcher::launcher"
anchors.top: true
@@ -18,10 +22,10 @@ ILauncher {
IRect {
id: container
- property var selection: SelectionState?.selected || null
+ property var selection: launcher.state?.selected || null
property var mappedSelection: selection?.mapToItem(null, 0, 0)
onMappedSelectionChanged: {
- // SelectionState?.selected may be slow.. smh qt
+ // launcher.state?.selected may be slow.. smh qt
// my guess is mapToItem is called when its not finished doing whatever it needs to do,
// so it doesnt get the right vars, but it doesn't update after since it already triggered it
selectionSync.running = true;
@@ -33,7 +37,7 @@ ILauncher {
// sync w/ IComponent y animation duration
interval: General.animationDuration / 4
onTriggered: {
- parent.mappedSelection = SelectionState?.selected?.mapToItem(null, 0, 0);
+ parent.mappedSelection = launcher.state?.selected?.mapToItem(null, 0, 0);
}
}
@@ -42,8 +46,8 @@ ILauncher {
visible: parent.mappedSelection || false
y: parent.mappedSelection?.y || 0
x: parent.mappedSelection?.x || 0
- implicitWidth: SelectionState?.selected?.width || 0
- implicitHeight: SelectionState?.selected?.height || 0
+ implicitWidth: launcher.state?.selected?.width || 0
+ implicitHeight: launcher.state?.selected?.height || 0
radius: Launcher.borderRadius
color: Qt.rgba(Colors.foreground2.r, Colors.foreground2.g, Colors.foreground2.b, Launcher.bgTransparency)
Behavior on x {
@@ -110,7 +114,7 @@ ILauncher {
Layout.maximumWidth: implicitWidth
color: Colors.foreground1
selectionColor: Colors.background3
- text: SelectionState?.input || ""
+ text: launcher.state?.input || ""
clip: true
focus: true
renderType: Text.CurveRendering
@@ -169,17 +173,17 @@ ILauncher {
}
}
onTextChanged: {
- if (SelectionState.input !== textInput.text)
- SelectionState.input = textInput.text; // TODO: find ways to optimize this, like how i was using an alias
+ if (launcher.state.input !== textInput.text)
+ launcher.state.input = textInput.text; // TODO: find ways to optimize this, like how i was using an alias
}
onCursorPositionChanged: {
- SelectionState.cursorPosition = textInput.cursorPosition;
+ launcher.state.cursorPosition = textInput.cursorPosition;
}
// onActiveFocusChanged: {
// parentLoader.active = activeFocus;
// }
onAccepted: {
- SelectionState?.priorities[SelectionState.selectedPriority]?._exec(); // TODO: kde like waiting animation for app to launch
+ launcher.state?.priorities[launcher.state.selectedPriority]?._exec(); // TODO: kde like waiting animation for app to launch
parentLoader.active = false;
textInput.text = "";
}
@@ -188,7 +192,7 @@ ILauncher {
}
}
IRect {
- opacity: SelectionState?.priorities[0]?.predictiveCompletion ? 1 : 0
+ opacity: launcher.state?.priorities[0]?.predictiveCompletion ? 1 : 0
Layout.fillHeight: true
Layout.preferredWidth: text.width
radius: Launcher.predictiveCompletionRadius
@@ -205,7 +209,7 @@ ILauncher {
family: Style.font.family.sans
wordSpacing: 5
}
- text: SelectionState?.priorities[0]?.predictiveCompletion || ""
+ text: launcher.state?.priorities[0]?.predictiveCompletion || ""
}
Behavior on Layout.preferredWidth {
ISpringAnimation {}
@@ -224,8 +228,8 @@ ILauncher {
Layout.fillWidth: true
}
Loader {
- active: SelectionState?.priorities[0]?.priority || false // .priority should always be true if it's in priorities
- sourceComponent: SelectionState?.priorities[0]?.preview
+ active: launcher.state?.priorities[0]?.priority || false // .priority should always be true if it's in priorities
+ sourceComponent: launcher.state?.priorities[0]?.preview
}
}
}
@@ -240,13 +244,13 @@ ILauncher {
}
Repeater {
- model: Launcher.widgets
+ model: launcher.widgetItems
delegate: Loader {
active: true
sourceComponent: modelData
asynchronous: true
onLoaded: {
- SelectionState.widgets[index] = item;
+ launcher.state.widgets[index] = item;
// FIXME: prev predictiveCompletion still exists here
// workaround atm: just clear it or preserve the text manually
}
@@ -321,7 +325,7 @@ ILauncher {
// 2 = up
// -2 = down
function changePos(direction: int) {
- const item = SelectionState?.priorities[SelectionState.selectedPriority] || null;
+ const item = launcher.state?.priorities[launcher.state.selectedPriority] || null;
if (!item)
return;
@@ -356,12 +360,12 @@ ILauncher {
switch (set) {
case 1:
- if (SelectionState.selectedPriority < SelectionState.priorities.length - 1)
- SelectionState.selectedPriority++;
+ if (launcher.state.selectedPriority < launcher.state.priorities.length - 1)
+ launcher.state.selectedPriority++;
break;
case -1:
- if (SelectionState.selectedPriority > 0)
- SelectionState.selectedPriority--;
+ if (launcher.state.selectedPriority > 0)
+ launcher.state.selectedPriority--;
break;
}
diff --git a/launcher/components/Clip.qml b/launcher/components/Clip.qml
index ffec453..789cd48 100644
--- a/launcher/components/Clip.qml
+++ b/launcher/components/Clip.qml
@@ -10,7 +10,7 @@ import qs.config
IComponent {
id: root
- property var clipHist: active ? Clip.query(inputCleaned) : []
+ property var clipHist: Clip.query(inputCleaned)
property int selectedIndex: -1
name: "Clipboard"
@@ -21,11 +21,9 @@ IComponent {
process: function () {
const isValid = clipHist.length > 0;
- const selected = isValid && clipHist[selectedIndex];
return {
valid: isValid,
- priority: isValid,
- answer: selected.data || ""
+ priority: isValid
};
}
@@ -65,8 +63,8 @@ IComponent {
function syncSelectionState() {
Qt.callLater(() => {
// fucking Qt why tf is repeater not ready when this is called
- SelectionState.selected = repeater.itemAt(selectedIndex);
- SelectionState.exec = root.exec;
+ state.selected = repeater.itemAt(selectedIndex);
+ state.exec = root.exec;
});
}
diff --git a/launcher/components/IComponent.qml b/launcher/components/IComponent.qml
index f0dc5f6..fdeb5b6 100644
--- a/launcher/components/IComponent.qml
+++ b/launcher/components/IComponent.qml
@@ -7,12 +7,14 @@ import qs.config
IRect {
id: root
// virtual properties
+ property bool standalone: false
+ property var state: SelectionState
property string name // Component/File name
- readonly property string _input: SelectionState.input
- readonly property string input: _input.slice(prefix.length)
+ readonly property string _input: state.input
+ readonly property string input: !standalone ? _input.slice(prefix.length) : _input
readonly property string inputCleaned: input.toLowerCase().trim()
property string prefix
- property bool active: input && _input.startsWith(prefix)
+ property bool active: !standalone ? input && _input.startsWith(prefix) : true
property bool valid: processed?.valid || false
property bool priority: valid && processed?.priority || false // please set priority to false if it's invalid
property string answer: processed?.answer || ""
@@ -66,7 +68,11 @@ IRect {
color: Qt.rgba(Colors.background3.r, Colors.background3.g, Colors.background3.b, Launcher.widgetBgTransparency) // TODO when prioritized, highlight
onPriorityChanged: {
- SelectionState.priorities = [...[...SelectionState.priorities, root].reduce((s, x) => (s[(s.has(x) && 'delete') || 'add'](x), s), new Set())]; // sync with SelectionState.priorities
+ // sync with state.priorities
+ if (!standalone)
+ state.priorities = [...[...state.priorities, root].reduce((s, x) => (s[(s.has(x) && 'delete') || 'add'](x), s), new Set())];
+ else
+ state.priorities = [this];
}
Behavior on y {
diff --git a/services/Clip.qml b/services/Clip.qml
index 26eff11..c47507e 100644
--- a/services/Clip.qml
+++ b/services/Clip.qml
@@ -9,7 +9,7 @@ Searchable {
id: root
key: "data"
- algorithm: Searchable.SearchAlgorithm.Levendist
+ algorithm: Searchable.SearchAlgorithm.Include
property list<string> _clipHist: []
property list<string> _clipImg: []
diff --git a/shell.qml b/shell.qml
index 0c5e13e..9ad27fa 100644
--- a/shell.qml
+++ b/shell.qml
@@ -7,6 +7,7 @@ import QtQuick
import QtQuick.Layouts
Scope {
+ id: root
Bar {}
LazyLoader {
id: launcherLoader
@@ -28,6 +29,47 @@ Scope {
function toggle() {
launcherLoader.active = !launcherLoader.active;
}
+
+ function standalone(component: string): void {
+ // NOTE: 'component' must be a QML type name (e.g., 'Calc'), not a 'Component { ... }' block.
+ // Passing a full Component block will cause a QML error.
+ Qt.createQmlObject(`
+ import Quickshell
+ import QtQuick
+ import "launcher"
+ import "launcher/components"
+
+ LazyLoader {
+ property QtObject selectionState: QtObject {
+ property Item selected: null
+ property string input: ""
+ property int cursorPosition: 0
+ property var priorities: []
+ property int selectedPriority: 0
+ property int previousSelectedPriority: 0
+ property var widgets: []
+ }
+ readonly property list<Component> widgets: [
+ Component {
+ Loader {
+ sourceComponent: Qt.createComponent("launcher/components/" + "${component}.qml")
+ onLoaded: {
+ item.standalone = true
+ item.state = selectionState;
+ }
+ }
+ }
+ ]
+ id: root
+ active: true
+ component: Launcher {
+ widgetItems: widgets
+ state: selectionState
+ parentLoader: root
+ }
+ }
+ `, root);
+ }
}
Connections {