From b17ae550381232fd8c0cce5024e8ec0852918a33 Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Mon, 11 Aug 2025 19:16:05 +0800 Subject: 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 --- components/Searchable.qml | 6 ++++- launcher/Launcher.qml | 46 +++++++++++++++++++++----------------- launcher/components/Clip.qml | 10 ++++----- launcher/components/IComponent.qml | 14 ++++++++---- services/Clip.qml | 2 +- shell.qml | 42 ++++++++++++++++++++++++++++++++++ 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 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 _clipHist: [] property list _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 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 { -- cgit v1.2.3