pragma ComponentBehavior: Bound import "services" import qs.components import qs.components.controls import qs.services import qs.config import Quickshell import QtQuick Item { id: root required property PersistentProperties visibilities required property var panels required property real maxHeight readonly property int padding: Appearance.padding.large readonly property int rounding: Appearance.rounding.large implicitWidth: listWrapper.width + padding * 2 implicitHeight: searchWrapper.height + listWrapper.height + padding * 2 Item { id: listWrapper implicitWidth: list.width implicitHeight: list.height + root.padding anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: searchWrapper.top anchors.bottomMargin: root.padding ContentList { id: list content: root visibilities: root.visibilities panels: root.panels maxHeight: root.maxHeight - searchWrapper.implicitHeight - root.padding * 3 search: search padding: root.padding rounding: root.rounding } } StyledRect { id: searchWrapper color: Colours.layer(Colours.palette.m3surfaceContainer, 2) radius: Appearance.rounding.full anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: root.padding implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight) MaterialIcon { id: searchIcon anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: root.padding text: "search" color: Colours.palette.m3onSurfaceVariant } StyledTextField { id: search anchors.left: searchIcon.right anchors.right: clearIcon.left anchors.leftMargin: Appearance.spacing.small anchors.rightMargin: Appearance.spacing.small topPadding: Appearance.padding.larger bottomPadding: Appearance.padding.larger placeholderText: "Search programs" onAccepted: { const currentItem = list.currentList?.currentItem; if (currentItem) { Apps.launch(currentItem.modelData); root.visibilities.launcher = false; } } Keys.onUpPressed: list.currentList?.decrementCurrentIndex() Keys.onDownPressed: list.currentList?.incrementCurrentIndex() Keys.onEscapePressed: root.visibilities.launcher = false Keys.onPressed: event => { if (!Config.launcher.vimKeybinds) return; if (event.modifiers & Qt.ControlModifier) { if (event.key === Qt.Key_J) { list.currentList?.incrementCurrentIndex(); event.accepted = true; } else if (event.key === Qt.Key_K) { list.currentList?.decrementCurrentIndex(); event.accepted = true; } } else if (event.key === Qt.Key_Tab) { list.currentList?.incrementCurrentIndex(); event.accepted = true; } else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) { list.currentList?.decrementCurrentIndex(); event.accepted = true; } } Component.onCompleted: forceActiveFocus() Connections { target: root.visibilities function onLauncherChanged(): void { if (!root.visibilities.launcher) search.text = ""; } function onSessionChanged(): void { if (!root.visibilities.session) search.forceActiveFocus(); } } } MaterialIcon { id: clearIcon anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: root.padding width: search.text ? implicitWidth : implicitWidth / 2 opacity: { if (!search.text) return 0; if (mouse.pressed) return 0.7; if (mouse.containsMouse) return 0.8; return 1; } text: "close" color: Colours.palette.m3onSurfaceVariant MouseArea { id: mouse anchors.fill: parent hoverEnabled: true cursorShape: search.text ? Qt.PointingHandCursor : undefined onClicked: search.text = "" } Behavior on width { Anim { duration: Appearance.anim.durations.small } } Behavior on opacity { Anim { duration: Appearance.anim.durations.small } } } } }