diff --git a/dots/.config/quickshell/ii/modules/common/Appearance.qml b/dots/.config/quickshell/ii/modules/common/Appearance.qml index 38589b161..8087f80ae 100644 --- a/dots/.config/quickshell/ii/modules/common/Appearance.qml +++ b/dots/.config/quickshell/ii/modules/common/Appearance.qml @@ -358,7 +358,7 @@ Singleton { property real notificationPopupWidth: 410 property real osdWidth: 180 property real searchWidthCollapsed: 260 - property real searchWidth: 450 + property real searchWidth: 400 property real sidebarWidth: 460 property real sidebarWidthExtended: 750 property real baseVerticalBarWidth: 46 diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml index b8c3469d6..b6abd1e9d 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml @@ -12,14 +12,12 @@ MaterialShape { color: Appearance.colors.colSecondaryContainer colSymbol: Appearance.colors.colOnSecondaryContainer - shape: MaterialShape.Shape.Clover4Leaf - implicitSize: Math.max(symbol.implicitWidth, symbol.implicitHeight) + padding * 2 MaterialSymbol { id: symbol anchors.centerIn: parent + color: root.colSymbol } - } diff --git a/dots/.config/quickshell/ii/modules/overview/Overview.qml b/dots/.config/quickshell/ii/modules/overview/Overview.qml index 844aea40a..6385b2485 100644 --- a/dots/.config/quickshell/ii/modules/overview/Overview.qml +++ b/dots/.config/quickshell/ii/modules/overview/Overview.qml @@ -2,6 +2,7 @@ import qs import qs.services import qs.modules.common import qs.modules.common.widgets +import Qt.labs.synchronizer import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -33,15 +34,12 @@ Scope { mask: Region { item: GlobalStates.overviewOpen ? columnLayout : null } - // HyprlandWindow.visibleMask: Region { // Buggy with scaled monitors - // item: GlobalStates.overviewOpen ? columnLayout : null - // } anchors { top: true bottom: true - left: !(Config?.options.overview.enable ?? true) - right: !(Config?.options.overview.enable ?? true) + left: true + right: true } HyprlandFocusGrab { @@ -89,13 +87,14 @@ Scope { searchWidget.focusFirstItem(); } - ColumnLayout { + Column { id: columnLayout visible: GlobalStates.overviewOpen anchors { horizontalCenter: parent.horizontalCenter top: parent.top } + spacing: -8 Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { @@ -111,14 +110,15 @@ Scope { SearchWidget { id: searchWidget - Layout.alignment: Qt.AlignHCenter - onSearchingTextChanged: text => { - root.searchingText = searchingText; + anchors.horizontalCenter: parent.horizontalCenter + Synchronizer on searchingText { + property alias source: root.searchingText } } Loader { id: overviewLoader + anchors.horizontalCenter: parent.horizontalCenter active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true) sourceComponent: OverviewWidget { panelWindow: root diff --git a/dots/.config/quickshell/ii/modules/overview/SearchBar.qml b/dots/.config/quickshell/ii/modules/overview/SearchBar.qml new file mode 100644 index 000000000..c054c2395 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overview/SearchBar.qml @@ -0,0 +1,111 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +RowLayout { + id: root + spacing: 6 + property bool animateWidth: false + property alias searchInput: searchInput + property string searchingText + + function focus() { + searchInput.forceActiveFocus(); + } + + enum SearchPrefixType { Action, App, Clipboard, Emojis, Math, ShellCommand, WebSearch, DefaultSearch } + + property var searchPrefixType: { + if (root.searchingText.startsWith(Config.options.search.prefix.action)) return SearchBar.SearchPrefixType.Action; + if (root.searchingText.startsWith(Config.options.search.prefix.app)) return SearchBar.SearchPrefixType.App; + if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) return SearchBar.SearchPrefixType.Clipboard; + if (root.searchingText.startsWith(Config.options.search.prefix.emojis)) return SearchBar.SearchPrefixType.Emojis; + if (root.searchingText.startsWith(Config.options.search.prefix.math)) return SearchBar.SearchPrefixType.Math; + if (root.searchingText.startsWith(Config.options.search.prefix.shellCommand)) return SearchBar.SearchPrefixType.ShellCommand; + if (root.searchingText.startsWith(Config.options.search.prefix.webSearch)) return SearchBar.SearchPrefixType.WebSearch; + return SearchBar.SearchPrefixType.DefaultSearch; + } + + MaterialShapeWrappedMaterialSymbol { + id: searchIcon + Layout.alignment: Qt.AlignVCenter + iconSize: Appearance.font.pixelSize.huge + shape: switch(root.searchPrefixType) { + case SearchBar.SearchPrefixType.Action: return MaterialShape.Shape.Pill; + case SearchBar.SearchPrefixType.App: return MaterialShape.Shape.Clover4Leaf; + case SearchBar.SearchPrefixType.Clipboard: return MaterialShape.Shape.Gem; + case SearchBar.SearchPrefixType.Emojis: return MaterialShape.Shape.Sunny; + case SearchBar.SearchPrefixType.Math: return MaterialShape.Shape.PuffyDiamond; + case SearchBar.SearchPrefixType.ShellCommand: return MaterialShape.Shape.PixelCircle; + case SearchBar.SearchPrefixType.WebSearch: return MaterialShape.Shape.SoftBurst; + default: return MaterialShape.Shape.Cookie7Sided; + } + text: switch (root.searchPrefixType) { + case SearchBar.SearchPrefixType.Action: return "settings_suggest"; + case SearchBar.SearchPrefixType.App: return "apps"; + case SearchBar.SearchPrefixType.Clipboard: return "content_paste_search"; + case SearchBar.SearchPrefixType.Emojis: return "add_reaction"; + case SearchBar.SearchPrefixType.Math: return "calculate"; + case SearchBar.SearchPrefixType.ShellCommand: return "terminal"; + case SearchBar.SearchPrefixType.WebSearch: return "travel_explore"; + case SearchBar.SearchPrefixType.DefaultSearch: return "search"; + default: return "search"; + } + } + ToolbarTextField { // Search box + id: searchInput + Layout.alignment: Qt.AlignVCenter + focus: GlobalStates.overviewOpen + padding: 15 + font.pixelSize: Appearance.font.pixelSize.small + placeholderText: Translation.tr("Search, calculate or run") + implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth + + Behavior on implicitWidth { + id: searchWidthBehavior + enabled: root.animateWidth + NumberAnimation { + duration: 300 + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + + onTextChanged: root.searchingText = text + + 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: null + + cursorDelegate: Rectangle { + width: 1 + color: searchInput.activeFocus ? Appearance.colors.colPrimary : "transparent" + radius: 1 + } + } + + IconToolbarButton { + onClicked: { + GlobalStates.overviewOpen = false; + Hyprland.dispatch("global quickshell:regionSearch") + } + text: "image_search" + } +} diff --git a/dots/.config/quickshell/ii/modules/overview/SearchWidget.qml b/dots/.config/quickshell/ii/modules/overview/SearchWidget.qml index b4b29848d..3bc4c2e50 100644 --- a/dots/.config/quickshell/ii/modules/overview/SearchWidget.qml +++ b/dots/.config/quickshell/ii/modules/overview/SearchWidget.qml @@ -3,6 +3,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions +import Qt.labs.synchronizer import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -15,7 +16,6 @@ Item { // Wrapper readonly property string xdgConfigHome: Directories.config 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 @@ -93,18 +93,22 @@ Item { // Wrapper appResults.currentIndex = 0; } + function focusSearchInput() { + searchBar.focus(); + } + function disableExpandAnimation() { - searchWidthBehavior.enabled = false; + searchBar.animateWidth = false; } function cancelSearch() { - searchInput.selectAll(); + searchBar.searchInput.selectAll(); root.searchingText = ""; - searchWidthBehavior.enabled = true; + searchBar.animateWidth = true; } function setSearchingText(text) { - searchInput.text = text; + searchBar.searchInput.text = text; root.searchingText = text; } @@ -149,29 +153,29 @@ Item { // Wrapper // Handle Backspace: focus and delete character if not focused if (event.key === Qt.Key_Backspace) { - if (!searchInput.activeFocus) { - searchInput.forceActiveFocus(); + if (!searchBar.searchInput.activeFocus) { + root.focusSearchInput(); if (event.modifiers & Qt.ControlModifier) { // Delete word before cursor - let text = searchInput.text; - let pos = searchInput.cursorPosition; + let text = searchBar.searchInput.text; + let pos = searchBar.searchInput.cursorPosition; if (pos > 0) { // Find the start of the previous word let left = text.slice(0, pos); let match = left.match(/(\s*\S+)\s*$/); let deleteLen = match ? match[0].length : 1; - searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos); - searchInput.cursorPosition = pos - deleteLen; + searchBar.searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos); + searchBar.searchInput.cursorPosition = pos - deleteLen; } } else { // Delete character before cursor if any - if (searchInput.cursorPosition > 0) { - searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition - 1) + searchInput.text.slice(searchInput.cursorPosition); - searchInput.cursorPosition -= 1; + if (searchBar.searchInput.cursorPosition > 0) { + searchBar.searchInput.text = searchBar.searchInput.text.slice(0, searchBar.searchInput.cursorPosition - 1) + searchBar.searchInput.text.slice(searchBar.searchInput.cursorPosition); + searchBar.searchInput.cursorPosition -= 1; } } // Always move cursor to end after programmatic edit - searchInput.cursorPosition = searchInput.text.length; + searchBar.searchInput.cursorPosition = searchBar.searchInput.text.length; event.accepted = true; } // If already focused, let TextField handle it @@ -181,11 +185,11 @@ Item { // Wrapper // Only handle visible printable characters (ignore control chars, arrows, etc.) if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && event.key !== Qt.Key_Delete && event.text.charCodeAt(0) >= 0x20) // ignore control chars like Backspace, Tab, etc. { - if (!searchInput.activeFocus) { - searchInput.forceActiveFocus(); + if (!searchBar.searchInput.activeFocus) { + root.focusSearchInput(); // 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; + searchBar.searchInput.text = searchBar.searchInput.text.slice(0, searchBar.searchInput.cursorPosition) + event.text + searchBar.searchInput.text.slice(searchBar.searchInput.cursorPosition); + searchBar.searchInput.cursorPosition += 1; event.accepted = true; root.focusFirstItem(); } @@ -200,10 +204,8 @@ Item { // Wrapper anchors.centerIn: parent implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight - radius: Appearance.rounding.large - color: Appearance.colors.colLayer0 - border.width: 1 - border.color: Appearance.colors.colLayer0Border + radius: searchBar.height / 2 + searchBar.verticalPadding + color: Appearance.colors.colSurfaceContainer ColumnLayout { id: columnLayout @@ -220,64 +222,16 @@ Item { // Wrapper } } - RowLayout { + SearchBar { id: searchBar - spacing: 5 - MaterialSymbol { - id: searchIcon - Layout.leftMargin: 15 - iconSize: Appearance.font.pixelSize.huge - color: Appearance.m3colors.m3onSurface - text: root.searchingText.startsWith(Config.options.search.prefix.clipboard) ? 'content_paste_search' : 'search' - } - TextField { // Search box - id: searchInput - - focus: GlobalStates.overviewOpen - Layout.rightMargin: 15 - padding: 15 - renderType: Text.NativeRendering - font { - family: Appearance?.font.family.main ?? "sans-serif" - pixelSize: Appearance?.font.pixelSize.small ?? 15 - hintingPreference: Font.PreferFullHinting - } - color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.colors.colSecondaryContainer - placeholderText: Translation.tr("Search, calculate or run") - placeholderTextColor: Appearance.m3colors.m3outline - implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth - - Behavior on implicitWidth { - id: searchWidthBehavior - enabled: false - NumberAnimation { - duration: 300 - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - } - - onTextChanged: root.searchingText = text - - 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: null - - cursorDelegate: Rectangle { - width: 1 - color: searchInput.activeFocus ? Appearance.colors.colPrimary : "transparent" - radius: 1 - } + property real verticalPadding: 4 + Layout.fillWidth: true + Layout.leftMargin: 10 + Layout.rightMargin: 4 + Layout.topMargin: verticalPadding + Layout.bottomMargin: verticalPadding + Synchronizer on searchingText { + property alias source: root.searchingText } }