forked from Shinonome/dots-hyprland
waffles: functioning search
This commit is contained in:
@@ -15,10 +15,16 @@ FooterRectangle {
|
||||
property real horizontalPadding: 32
|
||||
property real verticalPadding: 16
|
||||
property bool searching: text.length > 0
|
||||
property alias searchInput: searchInput
|
||||
property alias text: searchInput.text
|
||||
implicitHeight: outline.implicitHeight + verticalPadding * 2
|
||||
|
||||
Component.onCompleted: searchInput.forceActiveFocus()
|
||||
signal accepted()
|
||||
|
||||
Component.onCompleted: forceFocus()
|
||||
function forceFocus() {
|
||||
searchInput.forceActiveFocus();
|
||||
}
|
||||
|
||||
focus: true
|
||||
color: searching ? Looks.colors.bgPanelBody : Looks.colors.bgPanelFooter
|
||||
@@ -81,6 +87,10 @@ FooterRectangle {
|
||||
visible: searchInput.text.length === 0
|
||||
font.pixelSize: Looks.font.pixelSize.large
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
root.accepted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.models
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property LauncherSearchResult entry
|
||||
property int iconSize: 24
|
||||
implicitWidth: Math.max(iconSize, textIconLoader.implicitWidth)
|
||||
implicitHeight: iconSize
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
active: root.entry.iconType === LauncherSearchResult.IconType.System && root.entry.iconName !== ""
|
||||
sourceComponent: WAppIcon {
|
||||
implicitSize: root.iconSize
|
||||
iconName: root.entry.iconName
|
||||
tryCustomIcon: false
|
||||
animated: false
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: textIconLoader
|
||||
anchors.centerIn: parent
|
||||
active: root.entry.iconType === LauncherSearchResult.IconType.Text
|
||||
sourceComponent: WText {
|
||||
text: root.entry.iconName
|
||||
font.pixelSize: root.iconSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
active: root.entry.iconType === LauncherSearchResult.IconType.Material || root.entry.iconType === LauncherSearchResult.IconType.None || root.entry.iconName === ""
|
||||
sourceComponent: FluentIcon {
|
||||
icon: root.entry.iconName ? WIcons.fluentFromMaterial(root.entry.iconName) : WIcons.guessIconForName(root.entry.name)
|
||||
implicitSize: root.iconSize
|
||||
animated: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@ import qs.modules.waffle.looks
|
||||
BodyRectangle {
|
||||
id: root
|
||||
|
||||
property alias context: searchResults.context
|
||||
property string searchText: LauncherSearch.query
|
||||
property alias currentIndex: searchResults.currentIndex
|
||||
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
@@ -24,11 +26,13 @@ BodyRectangle {
|
||||
spacing: 12
|
||||
|
||||
TagStrip {
|
||||
context: root.context
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
}
|
||||
|
||||
SearchResults {
|
||||
id: searchResults
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.waffle.looks
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common
|
||||
import qs.services
|
||||
import qs
|
||||
import qs.modules.common.models
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property int maxResultsPerCategory: 4
|
||||
property StartMenuContext context
|
||||
property int currentIndex: context.currentIndex
|
||||
onCurrentIndexChanged: {
|
||||
forceCurrentIndex(currentIndex);
|
||||
}
|
||||
function focusFirstItem() {
|
||||
resultList.currentIndex = 0;
|
||||
forceCurrentIndex(0);
|
||||
}
|
||||
function forceCurrentIndex(index) {
|
||||
context.currentIndex = index;
|
||||
// Somehow this hack is needed
|
||||
if (index === 0) {
|
||||
resultList.incrementCurrentIndex();
|
||||
resultList.decrementCurrentIndex();
|
||||
} else {
|
||||
resultList.decrementCurrentIndex();
|
||||
resultList.incrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: context
|
||||
function onAccepted() {
|
||||
resultList.currentItem?.execute();
|
||||
}
|
||||
}
|
||||
|
||||
ResultList {
|
||||
@@ -25,23 +50,74 @@ RowLayout {
|
||||
Layout.preferredWidth: 386
|
||||
Layout.leftMargin: 1
|
||||
Layout.rightMargin: 1
|
||||
entry: resultList.model[resultList.currentIndex] ?? searchResultComp.createObject()
|
||||
}
|
||||
|
||||
component ResultList: ListView {
|
||||
component ResultList: WListView {
|
||||
id: resultListView
|
||||
section {
|
||||
criteria: ViewSection.FullString
|
||||
property: "type"
|
||||
property: "category" // This is "type" with tweaks to make it match more closely
|
||||
labelPositioning: ViewSection.InlineLabels
|
||||
delegate: Item {
|
||||
id: sectionButton
|
||||
required property string section
|
||||
implicitHeight: sectionChoiceButton.implicitHeight + resultListView.spacing
|
||||
width: ListView.view?.width
|
||||
WChoiceButton {
|
||||
id: sectionChoiceButton
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
implicitHeight: 38
|
||||
contentItem: WText {
|
||||
text: sectionButton.section
|
||||
font.pixelSize: Looks.font.pixelSize.large
|
||||
font.weight: Looks.font.weight.strong
|
||||
}
|
||||
onClicked: {
|
||||
root.context.selectCategory(sectionButton.section);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clip: true
|
||||
spacing: 4
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
// TODO: categorize and have max per category
|
||||
LauncherSearch.results.slice(0, 10)
|
||||
}
|
||||
onValuesChanged: {
|
||||
root.focusFirstItem();
|
||||
currentIndex: root.currentIndex
|
||||
|
||||
// We can't use a ScriptModel here because it would mess up sections
|
||||
model: {
|
||||
const allResults = LauncherSearch.results;
|
||||
// Find categories
|
||||
var categories = new Set();
|
||||
for (let i = 0; i < allResults.length; i++) {
|
||||
categories.add(allResults[i].type);
|
||||
}
|
||||
|
||||
// Collect max 4 per category
|
||||
var categorizedResults = [];
|
||||
categories.forEach(category => {
|
||||
let count = 0;
|
||||
for (let i = 0; i < allResults.length; i++) {
|
||||
if (allResults[i].type === category) {
|
||||
const entry = allResults[i];
|
||||
const tweakedEntry = searchResultComp.createObject(null, Object.assign({}, entry));
|
||||
tweakedEntry.category = categorizedResults.length === 0 ? Translation.tr("Best match") : entry.type
|
||||
categorizedResults.push(tweakedEntry); // Section header
|
||||
count++;
|
||||
if (count >= root.maxResultsPerCategory) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// print(JSON.stringify(categorizedResults, null, 2));
|
||||
return categorizedResults;
|
||||
}
|
||||
onModelChanged: {
|
||||
root.focusFirstItem();
|
||||
}
|
||||
delegate: WSearchResultButton {
|
||||
required property int index
|
||||
@@ -53,8 +129,112 @@ RowLayout {
|
||||
}
|
||||
|
||||
component ResultPreview: Rectangle {
|
||||
id: resultPreview
|
||||
|
||||
property LauncherSearchResult entry // LauncherSearchResult
|
||||
|
||||
Layout.fillHeight: true
|
||||
color: Looks.colors.bg1
|
||||
radius: Looks.radius.large
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 22
|
||||
spacing: 13
|
||||
|
||||
ColumnLayout {
|
||||
id: mainInfoColumn
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
SearchEntryIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 12
|
||||
entry: resultPreview.entry
|
||||
iconSize: 64
|
||||
}
|
||||
WText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
text: resultPreview.entry?.name || ""
|
||||
font.pixelSize: Looks.font.pixelSize.xlarger
|
||||
}
|
||||
WText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: resultPreview.entry?.type || ""
|
||||
color: Looks.colors.accentUnfocused
|
||||
font.pixelSize: Looks.font.pixelSize.normal
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: resultSeparator
|
||||
implicitHeight: 2
|
||||
Layout.topMargin: 16
|
||||
Layout.fillWidth: true
|
||||
color: Looks.colors.bg2Hover
|
||||
}
|
||||
WListView {
|
||||
id: actionsColumn
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
spacing: 2
|
||||
model: {
|
||||
const isAppEntry = resultPreview.entry.type === Translation.tr("App");
|
||||
const appId = isAppEntry ? resultPreview.entry.id : "";
|
||||
const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false;
|
||||
var result = [
|
||||
searchResultComp.createObject(null, {
|
||||
name: resultPreview.entry.verb,
|
||||
iconName: isAppEntry ? "open_in_new" : "keyboard_return",
|
||||
iconType: LauncherSearchResult.IconType.Material,
|
||||
execute: () => {
|
||||
resultPreview.entry.execute();
|
||||
}
|
||||
}),
|
||||
...(isAppEntry ? [
|
||||
searchResultComp.createObject(null, {
|
||||
name: pinned ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar"),
|
||||
iconName: pinned ? "keep_off" : "keep",
|
||||
iconType: LauncherSearchResult.IconType.Material,
|
||||
execute: () => {
|
||||
TaskbarApps.togglePin(appId);
|
||||
}
|
||||
})
|
||||
] : [])
|
||||
];
|
||||
result = result.concat(resultPreview.entry.actions);
|
||||
return result;
|
||||
}
|
||||
delegate: WButton {
|
||||
id: actionButton
|
||||
required property var modelData
|
||||
width: ListView.view?.width
|
||||
icon.name: modelData.iconName
|
||||
text: modelData.name
|
||||
onClicked: modelData.execute();
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: 11
|
||||
SearchEntryIcon {
|
||||
entry: actionButton.modelData
|
||||
iconSize: 16
|
||||
}
|
||||
WText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
text: actionButton.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: searchResultComp
|
||||
LauncherSearchResult {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,72 @@ WBarAttachedPanelContent {
|
||||
property bool searching: false
|
||||
property string searchText: LauncherSearch.query
|
||||
|
||||
StartMenuContext {
|
||||
id: context
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
// Prevent Esc and Backspace from registering
|
||||
if (event.key === Qt.Key_Escape)
|
||||
return;
|
||||
|
||||
// Handle Backspace: focus and delete character if not focused
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
searchBar.forceFocus();
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
// Delete word before cursor
|
||||
let text = searchBar.text;
|
||||
let pos = searchBar.searchInput.cursorPosition;
|
||||
if (pos > 0) {
|
||||
// Find the start of the previous word
|
||||
let left = text.slice(0, pos);
|
||||
let match = left.match(/(\s*\S+)\s*$/);
|
||||
let deleteLen = match ? match[0].length : 1;
|
||||
searchBar.text = text.slice(0, pos - deleteLen) + text.slice(pos);
|
||||
searchBar.searchInput.cursorPosition = pos - deleteLen;
|
||||
}
|
||||
} else {
|
||||
// Delete character before cursor if any
|
||||
if (searchBar.searchInput.cursorPosition > 0) {
|
||||
searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition - 1) + searchBar.text.slice(searchBar.searchInput.cursorPosition);
|
||||
searchBar.searchInput.cursorPosition -= 1;
|
||||
}
|
||||
}
|
||||
// Always move cursor to end after programmatic edit
|
||||
searchBar.searchInput.cursorPosition = searchBar.text.length;
|
||||
event.accepted = true;
|
||||
// If already focused, let TextField handle it
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle visible printable characters (ignore control chars, arrows, etc.)
|
||||
if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && event.key !== Qt.Key_Delete && event.text.charCodeAt(0) >= 0x20) // ignore control chars like Backspace, Tab, etc.
|
||||
{
|
||||
if (!searchBar.searchInput.activeFocus) {
|
||||
searchBar.forceFocus();
|
||||
// Insert the character at the cursor position
|
||||
searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition) + event.text + searchBar.text.slice(searchBar.searchInput.cursorPosition);
|
||||
searchBar.searchInput.cursorPosition += 1;
|
||||
event.accepted = true;
|
||||
context.setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Arrow keys for item navigation
|
||||
if (event.key === Qt.Key_Down) {
|
||||
let maxIndex = Math.max(0, LauncherSearch.results.length - 1);
|
||||
context.setCurrentIndex(Math.min(context.currentIndex + 1, maxIndex));
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
context.setCurrentIndex(Math.max(context.currentIndex - 1, 0));
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: WPane {
|
||||
contentItem: WPanelPageColumn {
|
||||
SearchBar {
|
||||
focus: true
|
||||
id: searchBar
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 832 // TODO: Make sizes naturally inferred
|
||||
horizontalPadding: root.searching ? 24 : 32
|
||||
@@ -27,10 +89,14 @@ WBarAttachedPanelContent {
|
||||
Synchronizer on searching {
|
||||
property alias target: root.searching
|
||||
}
|
||||
focus: true
|
||||
text: root.searchText
|
||||
onTextChanged: {
|
||||
LauncherSearch.query = text;
|
||||
}
|
||||
onAccepted: {
|
||||
context.accepted();
|
||||
}
|
||||
}
|
||||
Item {
|
||||
implicitHeight: root.searching ? 736 : 736 // TODO: Make sizes naturally inferred
|
||||
@@ -46,7 +112,9 @@ WBarAttachedPanelContent {
|
||||
|
||||
Component {
|
||||
id: searchPageComp
|
||||
SearchPageContent {}
|
||||
SearchPageContent {
|
||||
context: context
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.services
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
signal accepted
|
||||
|
||||
property int currentIndex: 0
|
||||
function setCurrentIndex(index) {
|
||||
if (index == currentIndex)
|
||||
return;
|
||||
currentIndex = index;
|
||||
}
|
||||
|
||||
function selectCategory(category) {
|
||||
for (let i = 0; i < root.categories.length; i++) {
|
||||
const thisCategoryName = root.categories[i].name;
|
||||
if (thisCategoryName.startsWith(category) || category.startsWith(thisCategoryName)) {
|
||||
LauncherSearch.ensurePrefix(root.categories[i].prefix);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
property list<var> categories: [
|
||||
{
|
||||
name: Translation.tr("All"),
|
||||
prefix: ""
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Apps"),
|
||||
prefix: Config.options.search.prefix.app
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Actions"),
|
||||
prefix: Config.options.search.prefix.action
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Clipboard"),
|
||||
prefix: Config.options.search.prefix.clipboard
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Emojis"),
|
||||
prefix: Config.options.search.prefix.emojis
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Math"),
|
||||
prefix: Config.options.search.prefix.math
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Commands"),
|
||||
prefix: Config.options.search.prefix.shellCommand
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Web"),
|
||||
prefix: Config.options.search.prefix.webSearch
|
||||
},
|
||||
]
|
||||
|
||||
}
|
||||
@@ -10,6 +10,9 @@ import qs.modules.common.functions
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
property StartMenuContext context
|
||||
|
||||
WPanelIconButton {
|
||||
implicitWidth: 36
|
||||
implicitHeight: 36
|
||||
@@ -23,40 +26,7 @@ RowLayout {
|
||||
Layout.fillHeight: true
|
||||
orientation: Qt.Horizontal
|
||||
spacing: 4
|
||||
model: [
|
||||
{
|
||||
name: Translation.tr("All"),
|
||||
prefix: ""
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Apps"),
|
||||
prefix: Config.options.search.prefix.app
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Actions"),
|
||||
prefix: Config.options.search.prefix.action
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Clipboard"),
|
||||
prefix: Config.options.search.prefix.clipboard
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Emojis"),
|
||||
prefix: Config.options.search.prefix.emojis
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Math"),
|
||||
prefix: Config.options.search.prefix.math
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Commands"),
|
||||
prefix: Config.options.search.prefix.shellCommand
|
||||
},
|
||||
{
|
||||
name: Translation.tr("Web"),
|
||||
prefix: Config.options.search.prefix.webSearch
|
||||
},
|
||||
]
|
||||
model: root.context.categories
|
||||
delegate: WBorderedButton {
|
||||
id: tagButton
|
||||
required property var modelData
|
||||
|
||||
@@ -11,7 +11,7 @@ import qs.modules.waffle.looks
|
||||
|
||||
WChoiceButton {
|
||||
id: root
|
||||
|
||||
|
||||
required property LauncherSearchResult entry
|
||||
property bool firstEntry: false
|
||||
|
||||
@@ -21,45 +21,28 @@ WChoiceButton {
|
||||
implicitHeight: contentLayout.implicitHeight + topPadding + bottomPadding
|
||||
|
||||
onClicked: {
|
||||
GlobalStates.searchOpen = false
|
||||
root.entry.execute()
|
||||
execute();
|
||||
}
|
||||
|
||||
|
||||
function execute() {
|
||||
GlobalStates.searchOpen = false;
|
||||
root.entry.execute();
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
id: contentLayout
|
||||
spacing: 8
|
||||
|
||||
EntryIcon {}
|
||||
|
||||
SearchEntryIcon {
|
||||
entry: root.entry
|
||||
iconSize: 24
|
||||
}
|
||||
EntryNameColumn {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
component EntryIcon: Item {
|
||||
implicitWidth: 24
|
||||
implicitHeight: 24
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
active: root.entry.iconType === LauncherSearchResult.IconType.System
|
||||
sourceComponent: WAppIcon {
|
||||
implicitSize: 24
|
||||
tryCustomIcon: false
|
||||
iconName: root.entry.iconName
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
active: root.entry.iconType === LauncherSearchResult.IconType.Text
|
||||
sourceComponent: WText {
|
||||
text: root.entry.iconName
|
||||
font.pixelSize: 24
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component EntryNameColumn: ColumnLayout {
|
||||
spacing: 4
|
||||
|
||||
@@ -78,4 +61,11 @@ WChoiceButton {
|
||||
color: Looks.colors.accentUnfocused
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
// hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ Scope {
|
||||
target: GlobalStates
|
||||
|
||||
function onSearchOpenChanged() {
|
||||
if (GlobalStates.searchOpen)
|
||||
if (GlobalStates.searchOpen) {
|
||||
LauncherSearch.query = "";
|
||||
panelLoader.active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +64,7 @@ Scope {
|
||||
onClosed: {
|
||||
GlobalStates.searchOpen = false;
|
||||
panelLoader.active = false;
|
||||
LauncherSearch.query = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user