forked from Shinonome/dots-hyprland
overview: search
This commit is contained in:
@@ -197,6 +197,7 @@ Singleton {
|
|||||||
property int barPreferredSideSectionWidth: 400
|
property int barPreferredSideSectionWidth: 400
|
||||||
property int sidebarWidth: 450
|
property int sidebarWidth: 450
|
||||||
property int notificationPopupWidth: 410
|
property int notificationPopupWidth: 410
|
||||||
|
property int searchWidthCollapsed: 260
|
||||||
property int searchWidth: 450
|
property int searchWidth: 450
|
||||||
property int hyprlandGapsOut: 5
|
property int hyprlandGapsOut: 5
|
||||||
property int elevationMargin: 7
|
property int elevationMargin: 7
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ Singleton {
|
|||||||
property int updateInterval: 3000
|
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 QtObject hacks: QtObject {
|
||||||
property int arbitraryRaceConditionDelay: 10 // milliseconds
|
property int arbitraryRaceConditionDelay: 10 // milliseconds
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ import Quickshell.Widgets
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: root
|
id: root
|
||||||
property DesktopEntry desktopEntry
|
property var entry
|
||||||
property string itemName: desktopEntry?.name
|
property bool entryShown: entry?.shown ?? true
|
||||||
property string itemIcon: desktopEntry?.icon
|
property string itemType: entry?.type
|
||||||
property var itemExecute: desktopEntry?.execute
|
property string itemName: entry?.name
|
||||||
property string itemClickActionName: desktopEntry?.clickActionName
|
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 horizontalMargin: 10
|
||||||
property int buttonHorizontalPadding: 10
|
property int buttonHorizontalPadding: 10
|
||||||
property int buttonVerticalPadding: 5
|
property int buttonVerticalPadding: 5
|
||||||
@@ -61,20 +66,46 @@ Button {
|
|||||||
anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding
|
anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding
|
||||||
anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding
|
anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding
|
||||||
|
|
||||||
|
// Icon
|
||||||
IconImage {
|
IconImage {
|
||||||
|
visible: root.materialSymbol == ""
|
||||||
source: Quickshell.iconPath(root.itemIcon);
|
source: Quickshell.iconPath(root.itemIcon);
|
||||||
width: 35
|
width: 35
|
||||||
height: 35
|
height: 35
|
||||||
}
|
}
|
||||||
StyledText {
|
MaterialSymbol {
|
||||||
Layout.fillWidth: true
|
visible: root.materialSymbol != ""
|
||||||
id: nameText
|
text: root.materialSymbol
|
||||||
font.pixelSize: Appearance.font.pixelSize.normal
|
font.pixelSize: 30
|
||||||
color: Appearance.m3colors.m3onSurface
|
color: Appearance.m3colors.m3onSurface
|
||||||
horizontalAlignment: Text.AlignLeft
|
// width: 35
|
||||||
elide: Text.ElideRight
|
// height: 35
|
||||||
text: root.itemName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
StyledText {
|
||||||
Layout.fillWidth: false
|
Layout.fillWidth: false
|
||||||
visible: (root.hovered || root.focus)
|
visible: (root.hovered || root.focus)
|
||||||
|
|||||||
@@ -17,15 +17,106 @@ Item { // Wrapper
|
|||||||
implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2
|
implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2
|
||||||
implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2
|
implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2
|
||||||
|
|
||||||
Keys.onPressed: {
|
property string mathResult: ""
|
||||||
// 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) {
|
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) {
|
if (!searchInput.activeFocus) {
|
||||||
searchInput.forceActiveFocus();
|
searchInput.forceActiveFocus();
|
||||||
// Insert the character at the cursor position
|
// Insert the character at the cursor position
|
||||||
searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) +
|
searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) +
|
||||||
event.text +
|
event.text +
|
||||||
searchInput.text.slice(searchInput.cursorPosition);
|
searchInput.text.slice(searchInput.cursorPosition);
|
||||||
searchInput.cursorPosition += 1;
|
searchInput.cursorPosition += 1;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
@@ -57,7 +148,12 @@ Item { // Wrapper
|
|||||||
RowLayout {
|
RowLayout {
|
||||||
id: searchBar
|
id: searchBar
|
||||||
spacing: 5
|
spacing: 5
|
||||||
KeyNavigation.down: appResults
|
KeyNavigation.down: {
|
||||||
|
if (appResults.count > 1) {
|
||||||
|
appResults.currentIndex = 1;
|
||||||
|
appResults.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
MaterialSymbol {
|
MaterialSymbol {
|
||||||
id: searchIcon
|
id: searchIcon
|
||||||
Layout.leftMargin: 15
|
Layout.leftMargin: 15
|
||||||
@@ -75,8 +171,14 @@ Item { // Wrapper
|
|||||||
selectedTextColor: Appearance.m3colors.m3onSurface
|
selectedTextColor: Appearance.m3colors.m3onSurface
|
||||||
placeholderText: qsTr("Search")
|
placeholderText: qsTr("Search")
|
||||||
placeholderTextColor: Appearance.m3colors.m3outline
|
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
|
onTextChanged: root.searchingText = text
|
||||||
Connections {
|
Connections {
|
||||||
@@ -125,20 +227,65 @@ Item { // Wrapper
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
KeyNavigation.up: searchBar
|
KeyNavigation.up: searchBar
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onSearchingTextChanged() {
|
||||||
|
if (appResults.count > 0)
|
||||||
|
appResults.currentIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
id: model
|
id: model
|
||||||
values: DesktopEntries.applications.values
|
values: {
|
||||||
.filter((entry) => {
|
if(root.searchingText == "") return [];
|
||||||
if (root.searchingText == "") return false
|
|
||||||
return entry.name.toLowerCase().includes(root.searchingText.toLowerCase())
|
// Start math and other non-app stuff
|
||||||
})
|
nonAppResultsTimer.restart();
|
||||||
.map((entry) => {
|
|
||||||
entry.clickActionName = "Launch";
|
// Init result array
|
||||||
return entry;
|
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 {
|
delegate: SearchItem {
|
||||||
desktopEntry: modelData
|
entry: modelData
|
||||||
// itemName: modelData.name
|
// itemName: modelData.name
|
||||||
// itemIcon: modelData.icon
|
// itemIcon: modelData.icon
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user