from the bottom??

This commit is contained in:
Zacharias-Brohn
2025-11-10 13:33:12 +01:00
parent 2a054f7c1a
commit 3ca79b8513
3 changed files with 421 additions and 217 deletions
+82
View File
@@ -0,0 +1,82 @@
import Quickshell
import QtQuick
import QtQuick.Controls
TextField {
id: root
color: "white"
horizontalAlignment: Text.AlignLeft
echoMode: TextInput.Normal
placeholderText: qsTr("Search applications...")
background: null
renderType: TextInput.NativeRendering
font.family: "Rubik"
cursorDelegate: Rectangle {
id: cursor
property bool disableBlink
implicitWidth: 2
color: "white"
radius: 2
Connections {
target: root
function onCursorPositionChanged(): void {
if ( root.activeFocus && root.cursorVisible ) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
}
Timer {
id: enableBlink
interval: 100
onTriggered: cursor.disableBlink = false
}
Timer {
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
repeat: true
triggeredOnStart: true
interval: 500
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
}
Binding {
when: !root.activeFocus || !root.cursorVisible
cursor.opacity: 0
}
Behavior on opacity {
Anim {
duration: 200
}
}
}
Keys.onPressed: {
if ( event.key === Qt.Key_Down ) {
appListView.decrementCurrentIndex();
event.accepted = true;
} else if ( event.key === Qt.Key_Up ) {
appListView.incrementCurrentIndex();
event.accepted = true;
} else if ( event.key === Qt.Key_Return || event.key === Qt.Key_Enter ) {
if ( appListView.currentItem ) {
Search.launch(appListView.currentItem.modelData);
launcherWindow.visible = false;
}
event.accepted = true;
} else if ( event.key === Qt.Key_Escape ) {
closeAnim.start();
event.accepted = true;
}
}
}
+306 -213
View File
@@ -3,6 +3,7 @@ import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import qs import qs
@@ -38,30 +39,133 @@ Scope {
appid: "z-cast" appid: "z-cast"
name: "toggle-launcher" name: "toggle-launcher"
onPressed: { onPressed: {
launcherWindow.visible = !launcherWindow.visible; if ( !launcherWindow.visible ) {
openAnim.start();
} else if ( launcherWindow.visible ) {
closeAnim.start();
}
focusGrab.active = true; focusGrab.active = true;
searchInput.forceActiveFocus(); searchInput.forceActiveFocus();
} }
} }
mask: Region { item: backgroundRect } // mask: Region { item: backgroundRect }
Rectangle {
id: shadowRect
anchors {
top: appListView.count > 0 ? appListRect.top : backgroundRect.top
bottom: backgroundRect.bottom
left: appListRect.left
right: appListRect.right
}
layer.enabled: true
radius: 8
color: "black"
visible: false
}
MultiEffect {
id: effects
source: shadowRect
anchors.fill: shadowRect
shadowBlur: 2.0
shadowEnabled: true
shadowOpacity: 1
shadowColor: "black"
maskSource: shadowRect
maskEnabled: true
maskInverted: true
autoPaddingEnabled: true
}
Rectangle { Rectangle {
id: backgroundRect id: backgroundRect
anchors.top: parent.top anchors.bottom: parent.bottom
anchors.topMargin: 200 anchors.bottomMargin: -1
implicitHeight: mainLayout.childrenRect.height + 20 implicitHeight: mainLayout.childrenRect.height + 20
implicitWidth: 600 implicitWidth: 600
x: Math.round(( parent.width - width ) / 2 ) x: Math.round(( parent.width - width ) / 2 )
color: "#d01a1a1a" color: "#d01a1a1a"
radius: 8 opacity: 1
border.color: "#444444" border.color: "#444444"
border.width: 1 border.width: 1
Behavior on implicitHeight { ParallelAnimation {
id: openAnim
Anim { Anim {
target: appListRect
duration: MaterialEasing.expressiveFastSpatialTime duration: MaterialEasing.expressiveFastSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
property: "implicitHeight"
from: 40
to: appListView.implicitHeight + 20
}
Anim {
target: appListRect
duration: 50
property: "opacity"
from: 0
to: 1
}
Anim {
target: backgroundRect
duration: 50
property: "opacity"
from: 0
to: 1
}
Anim {
target: effects
duration: 50
property: "opacity"
from: 0
to: 1
}
onStarted: {
launcherWindow.visible = true;
}
}
ParallelAnimation {
id: closeAnim
Anim {
target: appListRect
duration: MaterialEasing.expressiveFastSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
property: "implicitHeight"
from: appListView.implicitHeight
to: 40
}
SequentialAnimation {
PauseAnimation { duration: 120 }
ParallelAnimation {
Anim {
target: backgroundRect
duration: 50
property: "opacity"
from: 1
to: 0
}
Anim {
target: appListRect
duration: 50
property: "opacity"
from: 1
to: 0
}
Anim {
target: effects
duration: 50
property: "opacity"
from: 1
to: 0
}
}
}
onStopped: {
launcherWindow.visible = false;
} }
} }
@@ -70,258 +174,247 @@ Scope {
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 anchors.margins: 10
spacing: 5 spacing: 5
clip: true
TextField { CustomTextField {
id: searchInput id: searchInput
implicitHeight: 30 implicitHeight: 30
implicitWidth: parent.width implicitWidth: parent.width
color: "white" }
horizontalAlignment: Text.AlignLeft }
echoMode: TextInput.Normal }
placeholderText: qsTr("Search applications...")
background: null
cursorDelegate: Rectangle { Rectangle {
id: cursor id: appListRect
x: Math.round(( parent.width - width ) / 2 )
implicitWidth: backgroundRect.implicitWidth
implicitHeight: appListView.implicitHeight + 20
anchors.bottom: backgroundRect.top
anchors.bottomMargin: -1
color: backgroundRect.color
topRightRadius: 8
topLeftRadius: 8
border.color: backgroundRect.border.color
property bool disableBlink Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveFastSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
}
}
implicitWidth: 2 Item {
color: "white" anchors.fill: parent
radius: 2 anchors.margins: 10
visible: appListView.count > 0
clip: true
ListView {
id: appListView
anchors.fill: parent
model: ScriptModel {
id: appModel
Connections { onValuesChanged: {
target: searchInput appListView.currentIndex = 0;
function onCursorPositionChanged(): void {
if ( searchInput.activeFocus && searchInput.cursorVisible ) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
} }
}
Timer { verticalLayoutDirection: ListView.BottomToTop
id: enableBlink implicitHeight: Math.min( count, 20 ) * 48
interval: 100 preferredHighlightBegin: 0
onTriggered: cursor.disableBlink = false preferredHighlightEnd: appListRect.height
} highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
focus: true
highlight: Rectangle {
radius: 4
color: "#FFFFFF"
opacity: 0.08
Timer { y: appListView.currentItem?.y
running: searchInput.activeFocus && searchInput.cursorVisible && !cursor.disableBlink implicitWidth: appListView.width
repeat: true implicitHeight: appListView.currentItem?.implicitHeight ?? 0
triggeredOnStart: true
interval: 500
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
}
Binding { Behavior on y {
when: !searchInput.activeFocus || !searchInput.cursorVisible
cursor.opacity: 0
}
Behavior on opacity {
Anim { Anim {
duration: 200 duration: MaterialEasing.expressiveDefaultSpatialTime
easing.bezierCurve: MaterialEasing.expressiveFastSpatial
} }
} }
} }
Keys.onPressed: { property list<var> search: Search.search( searchInput.text )
if ( event.key === Qt.Key_Down ) {
appListView.incrementCurrentIndex(); state: search.length === 0 ? "noresults" : "apps"
event.accepted = true;
} else if ( event.key === Qt.Key_Up ) { states: [
appListView.decrementCurrentIndex(); State {
event.accepted = true; name: "apps"
} else if ( event.key === Qt.Key_Return || event.key === Qt.Key_Enter ) { PropertyChanges {
if ( appListView.currentItem ) { appModel.values: Search.search(searchInput.text)
Search.launch(appListView.currentItem.modelData); appListView.delegate: appItem
launcherWindow.visible = false;
} }
event.accepted = true; },
} else if ( event.key === Qt.Key_Escape ) { State {
launcherWindow.visible = false; name: "noresults"
event.accepted = true; PropertyChanges {
appModel.values: [1]
appListView.delegate: noResultsItem
}
}
]
Component {
id: appItem
AppItem {
} }
} }
}
Rectangle { Component {
id: separator id: noResultsItem
implicitWidth: parent.width Item {
implicitHeight: 1 width: appListView.width
color: "#444444" height: 48
} Text {
id: icon
anchors.verticalCenter: parent.verticalCenter
property real fill: 0
text: "\ue000"
color: "#cccccc"
renderType: Text.NativeRendering
font.pointSize: 28
font.family: "Material Symbols Outlined"
font.variableAxes: ({
FILL: fill.toFixed(1),
GRAD: -25,
opsz: fontInfo.pixelSize,
wght: fontInfo.weight
})
}
Rectangle { Text {
id: appListRect anchors.left: icon.right
implicitWidth: parent.width anchors.leftMargin: 10
implicitHeight: appListView.implicitHeight anchors.verticalCenter: parent.verticalCenter
color: "transparent" text: "No results found"
clip: true color: "#cccccc"
ListView { renderType: Text.NativeRendering
id: appListView
anchors.fill: parent
model: ScriptModel {
id: appModel
onValuesChanged: { font.pointSize: 12
appListView.currentIndex = 0; font.family: "Rubik"
} }
} }
}
implicitHeight: Math.min( count, 15 ) * 48 transitions: Transition {
SequentialAnimation {
preferredHighlightBegin: 0 ParallelAnimation {
preferredHighlightEnd: appListRect.height
highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
focus: true
highlight: Rectangle {
radius: 4
color: "#FFFFFF"
opacity: 0.08
y: appListView.currentItem?.y
implicitWidth: appListView.width
implicitHeight: appListView.currentItem?.implicitHeight ?? 0
Behavior on y {
Anim { Anim {
duration: MaterialEasing.expressiveDefaultSpatialTime target: appListView
easing.bezierCurve: MaterialEasing.expressiveFastSpatial property: "opacity"
from: 1
to: 0
duration: 200
easing.bezierCurve: MaterialEasing.standardAccel
}
Anim {
target: appListView
property: "scale"
from: 1
to: 0.9
duration: 200
easing.bezierCurve: MaterialEasing.standardAccel
} }
} }
} PropertyAction {
targets: [model, appListView]
state: "apps" properties: "values,delegate"
}
states: [ ParallelAnimation {
State { Anim {
name: "apps" target: appListView
PropertyChanges { property: "opacity"
appModel.values: Search.search(searchInput.text) from: 0
appListView.delegate: appItem to: 1
duration: 200
easing.bezierCurve: MaterialEasing.standardDecel
}
Anim {
target: appListView
property: "scale"
from: 0.9
to: 1
duration: 200
easing.bezierCurve: MaterialEasing.standardDecel
} }
} }
] PropertyAction {
targets: [appListView.add, appListView.remove]
Component { property: "enabled"
id: appItem value: true
AppItem {
} }
} }
}
transitions: Transition { add: Transition {
SequentialAnimation { enabled: !appListView.state
ParallelAnimation { Anim {
Anim { properties: "opacity"
target: appListView from: 0
property: "opacity" to: 1
from: 1
to: 0
duration: 200
easing.bezierCurve: MaterialEasing.standardAccel
}
Anim {
target: appListView
property: "scale"
from: 1
to: 0.9
duration: 200
easing.bezierCurve: MaterialEasing.standardAccel
}
}
PropertyAction {
targets: [model, appListView]
properties: "values,delegate"
}
ParallelAnimation {
Anim {
target: appListView
property: "opacity"
from: 0
to: 1
duration: 200
easing.bezierCurve: MaterialEasing.standardDecel
}
Anim {
target: appListView
property: "scale"
from: 0.9
to: 1
duration: 200
easing.bezierCurve: MaterialEasing.standardDecel
}
}
PropertyAction {
targets: [appListView.add, appListView.remove]
property: "enabled"
value: true
}
}
} }
add: Transition { Anim {
Anim { properties: "scale"
properties: "opacity" from: 0.95
from: 0 to: 1
to: 1 }
} }
Anim { remove: Transition {
properties: "scale" enabled: !appListView.state
from: 0.95 Anim {
to: 1 properties: "opacity"
} from: 1
to: 0
} }
remove: Transition { Anim {
Anim { properties: "scale"
properties: "opacity" from: 1
from: 1 to: 0.95
to: 0
}
Anim {
properties: "scale"
from: 1
to: 0.95
}
} }
}
move: Transition { move: Transition {
Anim { Anim {
property: "y" property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
} }
Anim {
addDisplaced: Transition { properties: "opacity,scale"
Anim { to: 1
property: "y"
duration: 200
}
Anim {
properties: "opacity,scale"
to: 1
}
} }
}
displaced: Transition { addDisplaced: Transition {
Anim { Anim {
property: "y" property: "y"
} duration: 200
Anim { }
properties: "opacity,scale" Anim {
to: 1 properties: "opacity,scale"
} to: 1
}
}
displaced: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
} }
} }
} }
+32 -3
View File
@@ -21,10 +21,39 @@ Searcher {
} }
function search(search: string): list<var> { function search(search: string): list<var> {
keys = ["name"]; const prefix = ">";
weights = [1]; if (search.startsWith(`${prefix}i `)) {
keys = ["id", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}c `)) {
keys = ["categories", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}d `)) {
keys = ["comment", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}e `)) {
keys = ["execString", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}w `)) {
keys = ["startupClass", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}g `)) {
keys = ["genericName", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}k `)) {
keys = ["keywords", "name"];
weights = [0.9, 0.1];
} else {
keys = ["name"];
weights = [1];
const results = query(search).map(e => e.entry); if (!search.startsWith(`${prefix}t `))
return query(search).map(e => e.entry);
}
const results = query(search.slice(prefix.length + 2)).map(e => e.entry);
if (search.startsWith(`${prefix}t `))
return results.filter(a => a.runInTerminal);
return results; return results;
} }