diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2025-08-11 19:16:05 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2025-08-14 15:37:02 +0800 |
| commit | b17ae550381232fd8c0cce5024e8ec0852918a33 (patch) | |
| tree | caad80336bcc1ff6c9a8f576cd7dfe3aa26db242 | |
| parent | 6a910bb63463facb7c5b72c2054740154934c7a4 (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.qml | 6 | ||||
| -rw-r--r-- | launcher/Launcher.qml | 46 | ||||
| -rw-r--r-- | launcher/components/Clip.qml | 10 | ||||
| -rw-r--r-- | launcher/components/IComponent.qml | 14 | ||||
| -rw-r--r-- | services/Clip.qml | 2 | ||||
| -rw-r--r-- | 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<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: [] @@ -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 { |
