mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 23:09:26 -05:00
overview: search
This commit is contained in:
@@ -197,6 +197,7 @@ Singleton {
|
||||
property int barPreferredSideSectionWidth: 400
|
||||
property int sidebarWidth: 450
|
||||
property int notificationPopupWidth: 410
|
||||
property int searchWidthCollapsed: 260
|
||||
property int searchWidth: 450
|
||||
property int hyprlandGapsOut: 5
|
||||
property int elevationMargin: 7
|
||||
|
||||
@@ -41,6 +41,12 @@ Singleton {
|
||||
property int updateInterval: 3000
|
||||
}
|
||||
|
||||
property QtObject search: QtObject {
|
||||
property int nonAppResultDelay: 30 // This prevents lagging when typing
|
||||
property string engineBaseUrl: "https://www.google.com/search?q="
|
||||
property list<string> excludedSites: [ "quora.com" ]
|
||||
}
|
||||
|
||||
property QtObject hacks: QtObject {
|
||||
property int arbitraryRaceConditionDelay: 10 // milliseconds
|
||||
}
|
||||
|
||||
@@ -11,12 +11,17 @@ 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 var entry
|
||||
property bool entryShown: entry?.shown ?? true
|
||||
property string itemType: entry?.type
|
||||
property string itemName: entry?.name
|
||||
property string itemIcon: entry?.icon ?? ""
|
||||
property var itemExecute: entry?.execute
|
||||
property string fontType: entry?.fontType ?? "main"
|
||||
property string itemClickActionName: entry?.clickActionName
|
||||
property string materialSymbol: entry?.materialSymbol ?? ""
|
||||
|
||||
visible: root.entryShown
|
||||
property int horizontalMargin: 10
|
||||
property int buttonHorizontalPadding: 10
|
||||
property int buttonVerticalPadding: 5
|
||||
@@ -61,20 +66,46 @@ Button {
|
||||
anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding
|
||||
anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding
|
||||
|
||||
// Icon
|
||||
IconImage {
|
||||
visible: root.materialSymbol == ""
|
||||
source: Quickshell.iconPath(root.itemIcon);
|
||||
width: 35
|
||||
height: 35
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
id: nameText
|
||||
font.pixelSize: Appearance.font.pixelSize.normal
|
||||
MaterialSymbol {
|
||||
visible: root.materialSymbol != ""
|
||||
text: root.materialSymbol
|
||||
font.pixelSize: 30
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
text: root.itemName
|
||||
// width: 35
|
||||
// height: 35
|
||||
}
|
||||
|
||||
// Main text
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 0
|
||||
StyledText {
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
color: Appearance.colors.colSubtext
|
||||
visible: root.itemType && root.itemType != "App"
|
||||
text: root.itemType
|
||||
}
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
id: nameText
|
||||
font.pixelSize: Appearance.font.pixelSize.normal
|
||||
font.family: Appearance.font.family[root.fontType]
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
text: root.itemName
|
||||
}
|
||||
}
|
||||
|
||||
// Action text
|
||||
StyledText {
|
||||
Layout.fillWidth: false
|
||||
visible: (root.hovered || root.focus)
|
||||
|
||||
@@ -17,15 +17,106 @@ Item { // Wrapper
|
||||
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) {
|
||||
property string mathResult: ""
|
||||
|
||||
Timer {
|
||||
id: nonAppResultsTimer
|
||||
interval: ConfigOptions.search.nonAppResultDelay
|
||||
onTriggered: {
|
||||
mathProcess.calculateExpression(root.searchingText);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: mathProcess
|
||||
property list<string> baseCommand: ["qalc", "-t"]
|
||||
function calculateExpression(expression) {
|
||||
// mathProcess.running = false
|
||||
mathProcess.command = baseCommand.concat(expression)
|
||||
mathProcess.running = true
|
||||
}
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.mathResult = data
|
||||
if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: copyText
|
||||
property list<string> baseCommand: ["wl-copy"]
|
||||
function copyTextToClipboard(text) {
|
||||
copyText.running = false
|
||||
copyText.command = baseCommand.concat(text)
|
||||
copyText.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: webSearch
|
||||
property list<string> baseCommand: ["xdg-open"]
|
||||
function search(query) {
|
||||
webSearch.running = false
|
||||
let url = ConfigOptions.search.engineBaseUrl + query
|
||||
for (let site of ConfigOptions.search.excludedSites) {
|
||||
url += ` -site:${site}`;
|
||||
}
|
||||
webSearch.command = baseCommand.concat(url)
|
||||
webSearch.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
// Prevent Esc and Backspace from registering
|
||||
if (event.key === Qt.Key_Escape) return;
|
||||
|
||||
// Handle Backspace: focus and delete character if not focused
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
if (!searchInput.activeFocus) {
|
||||
searchInput.forceActiveFocus();
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
// Delete word before cursor
|
||||
let text = searchInput.text;
|
||||
let pos = 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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
// Always move cursor to end after programmatic edit
|
||||
searchInput.cursorPosition = searchInput.text.length;
|
||||
event.accepted = true;
|
||||
}
|
||||
// If already focused, let TextField handle it
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.text.charCodeAt(0) >= 0x20 // ignore control chars like Backspace, Tab, etc.
|
||||
) {
|
||||
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);
|
||||
event.text +
|
||||
searchInput.text.slice(searchInput.cursorPosition);
|
||||
searchInput.cursorPosition += 1;
|
||||
event.accepted = true;
|
||||
}
|
||||
@@ -57,7 +148,12 @@ Item { // Wrapper
|
||||
RowLayout {
|
||||
id: searchBar
|
||||
spacing: 5
|
||||
KeyNavigation.down: appResults
|
||||
KeyNavigation.down: {
|
||||
if (appResults.count > 1) {
|
||||
appResults.currentIndex = 1;
|
||||
appResults.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
MaterialSymbol {
|
||||
id: searchIcon
|
||||
Layout.leftMargin: 15
|
||||
@@ -75,8 +171,14 @@ Item { // Wrapper
|
||||
selectedTextColor: Appearance.m3colors.m3onSurface
|
||||
placeholderText: qsTr("Search")
|
||||
placeholderTextColor: Appearance.m3colors.m3outline
|
||||
implicitWidth: Appearance.sizes.searchWidth
|
||||
implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.elementDecelFast.duration
|
||||
easing.type: Appearance.animation.elementDecelFast.type
|
||||
}
|
||||
}
|
||||
|
||||
onTextChanged: root.searchingText = text
|
||||
Connections {
|
||||
@@ -125,20 +227,65 @@ Item { // Wrapper
|
||||
spacing: 0
|
||||
KeyNavigation.up: searchBar
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onSearchingTextChanged() {
|
||||
if (appResults.count > 0)
|
||||
appResults.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
})
|
||||
values: {
|
||||
if(root.searchingText == "") return [];
|
||||
|
||||
// Start math and other non-app stuff
|
||||
nonAppResultsTimer.restart();
|
||||
|
||||
// Init result array
|
||||
let result = [];
|
||||
|
||||
// Add filtered application entries
|
||||
result = result.concat(
|
||||
DesktopEntries.applications.values
|
||||
.filter((entry) => {
|
||||
if (root.searchingText == "") return false
|
||||
return entry.name.toLowerCase().includes(root.searchingText.toLowerCase())
|
||||
})
|
||||
.map((entry) => {
|
||||
entry.clickActionName = "Launch";
|
||||
entry.type = "App"
|
||||
return entry;
|
||||
})
|
||||
);
|
||||
|
||||
// Add non-app results
|
||||
result.push({
|
||||
name: root.mathResult,
|
||||
clickActionName: "Copy",
|
||||
type: qsTr("Math result"),
|
||||
fontType: "monospace",
|
||||
materialSymbol: 'calculate',
|
||||
execute: () => {
|
||||
copyText.copyTextToClipboard(root.mathResult);
|
||||
}
|
||||
});
|
||||
result.push({
|
||||
name: root.searchingText,
|
||||
clickActionName: "Search",
|
||||
type: "Search the web",
|
||||
materialSymbol: 'travel_explore',
|
||||
execute: () => {
|
||||
webSearch.search(root.searchingText);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
delegate: SearchItem {
|
||||
desktopEntry: modelData
|
||||
entry: modelData
|
||||
// itemName: modelData.name
|
||||
// itemIcon: modelData.icon
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user