forked from Shinonome/dots-hyprland
257 lines
9.3 KiB
QML
257 lines
9.3 KiB
QML
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.models
|
|
import qs.modules.waffle.startMenu
|
|
import Quickshell
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
import QtQuick
|
|
|
|
RowLayout {
|
|
id: root
|
|
|
|
property int maxResultsPerCategory: 4
|
|
property StartMenuContext context
|
|
property int currentIndex: context.currentIndex
|
|
onCurrentIndexChanged: {
|
|
forceCurrentIndex(currentIndex);
|
|
}
|
|
function focusFirstItem() {
|
|
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 {
|
|
id: resultList
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
}
|
|
ResultPreview {
|
|
Layout.preferredWidth: 386
|
|
Layout.leftMargin: 1
|
|
Layout.rightMargin: 1
|
|
entry: resultList.model[resultList.currentIndex] ?? searchResultComp.createObject()
|
|
}
|
|
|
|
component ResultList: WListView {
|
|
id: resultListView
|
|
section {
|
|
criteria: ViewSection.FullString
|
|
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
|
|
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: SearchResultButton {
|
|
required property int index
|
|
required property var modelData
|
|
entry: modelData
|
|
firstEntry: index === 0
|
|
width: ListView.view?.width
|
|
checked: resultListView.currentIndex === index
|
|
onRequestFocus: {
|
|
root.forceCurrentIndex(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
const startPinned = isAppEntry ? (Config.options.launcher.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: startPinned ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start"),
|
|
iconName: startPinned ? "keep_off" : "keep",
|
|
iconType: LauncherSearchResult.IconType.Material,
|
|
execute: () => {
|
|
LauncherApps.togglePin(appId);
|
|
}
|
|
})
|
|
] : []),
|
|
...(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 {}
|
|
}
|
|
}
|