Files
dots-hyprland/.config/quickshell/modules/overview/SearchWidget.qml
T

160 lines
5.8 KiB
QML

import "root:/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
Item { // Wrapper
id: root
required property var panelWindow
property string searchingText: ""
property bool showResults: searchingText != ""
property real searchBarHeight: searchBar.height + Appearance.sizes.elevationMargin * 2
implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2
Keys.onPressed: {
// Only handle printable characters (ignore modifiers, arrows, etc.)
if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return) {
if (!searchInput.activeFocus) {
searchInput.forceActiveFocus();
// Insert the character at the cursor position
searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) +
event.text +
searchInput.text.slice(searchInput.cursorPosition);
searchInput.cursorPosition += 1;
event.accepted = true;
}
}
}
Rectangle { // Background
id: searchWidgetContent
anchors.centerIn: parent
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
radius: Appearance.rounding.large
color: Appearance.colors.colLayer0
ColumnLayout {
id: columnLayout
anchors.centerIn: parent
spacing: 0
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: searchWidgetContent.width
height: searchWidgetContent.width
radius: searchWidgetContent.radius
}
}
RowLayout {
id: searchBar
spacing: 5
KeyNavigation.down: appResults
MaterialSymbol {
id: searchIcon
Layout.leftMargin: 15
font.pixelSize: Appearance.font.pixelSize.huge
color: Appearance.m3colors.m3onSurface
text: "search"
}
TextField { // Search box
id: searchInput
focus: root.panelWindow.visible || GlobalStates.overviewOpen
Layout.rightMargin: 15
padding: 15
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
selectedTextColor: Appearance.m3colors.m3onSurface
placeholderText: qsTr("Search")
placeholderTextColor: Appearance.m3colors.m3outline
implicitWidth: Appearance.sizes.searchWidth
onTextChanged: root.searchingText = text
Connections {
target: root
function onVisibleChanged() {
searchInput.selectAll()
root.searchingText = ""
}
}
onAccepted: {
if (appResults.count > 0) {
// Get the first visible delegate and trigger its click
let firstItem = appResults.itemAtIndex(0);
if (firstItem && firstItem.clicked) {
firstItem.clicked();
}
}
}
background: Item {}
cursorDelegate: Rectangle {
width: 1
color: searchInput.activeFocus ? Appearance.m3colors.m3primary : "transparent"
radius: 1
}
}
}
Rectangle { // Separator
visible: root.showResults
Layout.fillWidth: true
height: 1
color: Appearance.m3colors.m3outline
}
ListView { // App results
id: appResults
visible: root.showResults
Layout.fillWidth: true
implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin)
clip: true
topMargin: 10
bottomMargin: 10
spacing: 0
KeyNavigation.up: searchBar
model: ScriptModel {
id: model
values: DesktopEntries.applications.values
.filter((entry) => {
if (root.searchingText == "") return false
return entry.name.toLowerCase().includes(root.searchingText.toLowerCase())
})
.map((entry) => {
entry.clickActionName = "Launch";
return entry;
})
}
delegate: SearchItem {
desktopEntry: modelData
// itemName: modelData.name
// itemIcon: modelData.icon
}
}
}
}
DropShadow {
id: searchWidgetShadow
anchors.fill: searchWidgetContent
source: searchWidgetContent
radius: Appearance.sizes.elevationMargin
samples: radius * 2 + 1
color: Appearance.colors.colShadow
verticalOffset: 2
horizontalOffset: 0
}
}