forked from Shinonome/dots-hyprland
waffles: ctrl alt del menu
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
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: pinned ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar"),
|
||||
iconName: pinned ? "keep_off" : "keep",
|
||||
iconType: LauncherSearchResult.IconType.Material,
|
||||
execute: () => {
|
||||
TaskbarApps.togglePin(appId);
|
||||
}
|
||||
})
|
||||
] : []),
|
||||
...(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: () => {
|
||||
if (Config.options.launcher.pinnedApps.indexOf(appId) !== -1) {
|
||||
Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.filter(id => id !== appId)
|
||||
} else {
|
||||
Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.concat([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 {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user