overview: app search

This commit is contained in:
end-4
2025-04-24 22:36:47 +02:00
parent 8dd82baf26
commit 72ccce04c6
5 changed files with 238 additions and 33 deletions
@@ -197,6 +197,7 @@ Singleton {
property int barPreferredSideSectionWidth: 400
property int sidebarWidth: 450
property int notificationPopupWidth: 410
property int searchWidth: 450
property int hyprlandGapsOut: 5
property int elevationMargin: 7
property int fabShadowRadius: 5
@@ -24,6 +24,7 @@ Scope {
WlrLayershell.namespace: "quickshell:overview"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
mask: Region {
@@ -40,7 +41,7 @@ Scope {
HyprlandFocusGrab {
id: grab
windows: [ root ]
active: false
active: GlobalStates.overviewOpen
onCleared: () => {
if (!active) GlobalStates.overviewOpen = false
}
@@ -80,42 +81,17 @@ Scope {
width: 1 // Prevent Wayland protocol error
}
TextField {
id: searchInput
SearchWidget {
panelWindow: root
Layout.alignment: Qt.AlignHCenter
padding: 15
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
selectedTextColor: Appearance.m3colors.m3onSurface
placeholderText: qsTr("Search")
placeholderTextColor: Appearance.m3colors.m3outline
focus: root.visible
onTextChanged: root.searchingText = text
Connections {
target: root
function onVisibleChanged() {
searchInput.selectAll()
root.searchingText = ""
}
}
background: Rectangle {
anchors.fill: parent
radius: Appearance.rounding.normal
color: Appearance.colors.colLayer0
}
cursorDelegate: Rectangle {
width: 1
color: searchInput.activeFocus ? Appearance.m3colors.m3primary : "transparent"
radius: 1
onSearchingTextChanged: (text) => {
root.searchingText = searchingText
}
}
OverviewWidget {
panelWindow: root
visible: (root.searchingText == "")
bar: root
}
}
@@ -13,8 +13,8 @@ import "./icons.js" as Icons
Item {
id: root
required property var bar
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen)
required property var panelWindow
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
readonly property var toplevels: ToplevelManager.toplevels
readonly property int workspacesShown: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols
readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown)
@@ -0,0 +1,93 @@
// pragma NativeMethodBehavior: AcceptThisObject
import "root:/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
Button {
id: root
property DesktopEntry desktopEntry
property string itemName: desktopEntry?.name
property string itemIcon: desktopEntry?.icon
property var itemExecute: desktopEntry?.execute
property string itemClickActionName: desktopEntry?.clickActionName
property int horizontalMargin: 10
property int buttonHorizontalPadding: 10
property int buttonVerticalPadding: 5
property bool keyboardDown: false
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: rowLayout.implicitHeight + root.buttonVerticalPadding * 2
implicitWidth: rowLayout.implicitWidth + root.buttonHorizontalPadding * 2
PointingHandInteraction {}
onClicked: {
root.itemExecute()
closeOverview.running = true
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
root.keyboardDown = true
root.clicked()
event.accepted = true;
}
}
Keys.onReleased: (event) => {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
root.keyboardDown = false
event.accepted = true;
}
}
background: Rectangle {
anchors.fill: parent
anchors.leftMargin: root.horizontalMargin
anchors.rightMargin: root.horizontalMargin
radius: Appearance.rounding.small
color: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1))
}
RowLayout {
id: rowLayout
spacing: 10
anchors.fill: parent
anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding
anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding
IconImage {
source: Quickshell.iconPath(root.itemIcon);
width: 35
height: 35
}
StyledText {
Layout.fillWidth: true
id: nameText
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSurface
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
text: root.itemName
}
StyledText {
Layout.fillWidth: false
visible: (root.hovered || root.focus)
id: clickAction
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colSubtext
horizontalAlignment: Text.AlignRight
text: root.itemClickActionName
}
}
Process {
id: closeOverview
command: ["bash", "-c", "qs ipc call overview close &"] // Somehow has to be async to work?
}
}
@@ -0,0 +1,135 @@
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
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
padding: 15
Layout.rightMargin: 15
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
selectedTextColor: Appearance.m3colors.m3onSurface
placeholderText: qsTr("Search")
placeholderTextColor: Appearance.m3colors.m3outline
focus: root.panelWindow.visible || GlobalStates.overviewOpen
implicitWidth: Appearance.sizes.searchWidth
onTextChanged: root.searchingText = text
Connections {
target: root
function onVisibleChanged() {
searchInput.selectAll()
root.searchingText = ""
}
}
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: 600
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
}
}