mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 14:59:27 -05:00
overview: fancier search bar
This commit is contained in:
@@ -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
|
||||
|
||||
+1
-3
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user