launcher overhaul

This commit is contained in:
Zacharias-Brohn
2026-02-17 16:32:43 +01:00
parent e818ac5515
commit dbb930f39a
31 changed files with 1603 additions and 1145 deletions
+220
View File
@@ -0,0 +1,220 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Modules.Launcher.Services
import qs.Modules.Launcher.Items
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules as Modules
CustomListView {
id: root
required property CustomTextField search
required property PersistentProperties visibilities
model: ScriptModel {
id: model
onValuesChanged: root.currentIndex = 0
}
verticalLayoutDirection: ListView.BottomToTop
spacing: Appearance.spacing.small
orientation: Qt.Vertical
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxAppsShown, count) - spacing
preferredHighlightBegin: 0
preferredHighlightEnd: height
highlightRangeMode: ListView.ApplyRange
highlightFollowsCurrentItem: false
highlight: CustomRect {
radius: 8
color: DynamicColors.palette.m3onSurface
opacity: 0.08
y: root.currentItem?.y ?? 0
implicitWidth: root.width
implicitHeight: root.currentItem?.implicitHeight ?? 0
Behavior on y {
Modules.Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
state: {
const text = search.text;
const prefix = Config.launcher.actionPrefix;
if (text.startsWith(prefix)) {
for (const action of ["calc", "scheme", "variant"])
if (text.startsWith(`${prefix}${action} `))
return action;
return "actions";
}
return "apps";
}
states: [
State {
name: "apps"
PropertyChanges {
model.values: Apps.search(search.text)
root.delegate: appItem
}
},
State {
name: "actions"
PropertyChanges {
model.values: Actions.query(search.text)
root.delegate: actionItem
}
},
State {
name: "calc"
PropertyChanges {
model.values: [0]
root.delegate: calcItem
}
},
]
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
Modules.Anim {
target: root
property: "opacity"
from: 1
to: 0
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
Modules.Anim {
target: root
property: "scale"
from: 1
to: 0.9
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
PropertyAction {
targets: [model, root]
properties: "values,delegate"
}
ParallelAnimation {
Modules.Anim {
target: root
property: "opacity"
from: 0
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
Modules.Anim {
target: root
property: "scale"
from: 0.9
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
PropertyAction {
targets: [root.add, root.remove]
property: "enabled"
value: true
}
}
}
CustomScrollBar.vertical: CustomScrollBar {
flickable: root
}
add: Transition {
enabled: !root.state
Modules.Anim {
properties: "opacity,scale"
from: 0
to: 1
}
}
remove: Transition {
enabled: !root.state
Modules.Anim {
properties: "opacity,scale"
from: 1
to: 0
}
}
move: Transition {
Modules.Anim {
property: "y"
}
Modules.Anim {
properties: "opacity,scale"
to: 1
}
}
addDisplaced: Transition {
Modules.Anim {
property: "y"
duration: Appearance.anim.durations.small
}
Modules.Anim {
properties: "opacity,scale"
to: 1
}
}
displaced: Transition {
Modules.Anim {
property: "y"
}
Modules.Anim {
properties: "opacity,scale"
to: 1
}
}
Component {
id: appItem
AppItem {
visibilities: root.visibilities
}
}
Component {
id: actionItem
ActionItem {
list: root
}
}
Component {
id: calcItem
CalcItem {
list: root
}
}
}
+61
View File
@@ -0,0 +1,61 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules as Modules
ShapePath {
id: root
required property Wrapper wrapper
readonly property real rounding: Config.barConfig.rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
strokeWidth: -1
fillColor: DynamicColors.palette.m3surface
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
Behavior on fillColor {
Modules.CAnim {}
}
}
+191
View File
@@ -0,0 +1,191 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Modules.Launcher.Services
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules as Modules
Item {
id: root
required property PersistentProperties visibilities
required property var panels
required property real maxHeight
readonly property int padding: Appearance.padding.small
readonly property int rounding: Appearance.rounding.large
implicitWidth: listWrapper.width + padding * 2
implicitHeight: searchWrapper.height + listWrapper.height + padding * 2
Item {
id: listWrapper
implicitWidth: list.width
implicitHeight: list.height + root.padding
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: searchWrapper.top
anchors.bottomMargin: root.padding
ContentList {
id: list
content: root
visibilities: root.visibilities
panels: root.panels
maxHeight: root.maxHeight - searchWrapper.implicitHeight - root.padding * 3
search: search
padding: root.padding
rounding: root.rounding
}
}
CustomRect {
id: searchWrapper
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
radius: 8
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: root.padding
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
MaterialIcon {
id: searchIcon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.padding + 10
text: "search"
color: DynamicColors.palette.m3onSurfaceVariant
}
CustomTextField {
id: search
anchors.left: searchIcon.right
anchors.right: clearIcon.left
anchors.leftMargin: Appearance.spacing.small
anchors.rightMargin: Appearance.spacing.small
topPadding: Appearance.padding.larger
bottomPadding: Appearance.padding.larger
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
onAccepted: {
const currentItem = list.currentList?.currentItem;
if (currentItem) {
if (list.showWallpapers) {
if (DynamicColors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent)
Wallpapers.previewColourLock = true;
Wallpapers.setWallpaper(currentItem.modelData.path);
root.visibilities.launcher = false;
} else if (text.startsWith(Config.launcher.actionPrefix)) {
if (text.startsWith(`${Config.launcher.actionPrefix}calc `))
currentItem.onClicked();
else
currentItem.modelData.onClicked(list.currentList);
} else {
Apps.launch(currentItem.modelData);
root.visibilities.launcher = false;
}
}
}
Keys.onUpPressed: list.currentList?.incrementCurrentIndex()
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
Keys.onEscapePressed: root.visibilities.launcher = false
Keys.onPressed: event => {
if (!Config.launcher.vimKeybinds)
return;
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_J) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_K) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
}
} else if (event.key === Qt.Key_Tab) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
}
}
Component.onCompleted: forceActiveFocus()
Connections {
target: root.visibilities
function onLauncherChanged(): void {
if (!root.visibilities.launcher)
search.text = "";
}
function onSessionChanged(): void {
if (!root.visibilities.session)
search.forceActiveFocus();
}
}
}
MaterialIcon {
id: clearIcon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: root.padding + 10
width: search.text ? implicitWidth : implicitWidth / 2
opacity: {
if (!search.text)
return 0;
if (mouse.pressed)
return 0.7;
if (mouse.containsMouse)
return 0.8;
return 1;
}
text: "close"
color: DynamicColors.palette.m3onSurfaceVariant
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: search.text ? Qt.PointingHandCursor : undefined
onClicked: search.text = ""
}
Behavior on width {
Modules.Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on opacity {
Modules.Anim {
duration: Appearance.anim.durations.small
}
}
}
}
}
+170
View File
@@ -0,0 +1,170 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Paths
import qs.Modules
import qs.Components
import qs.Helpers
import qs.Config
Item {
id: root
required property var content
required property PersistentProperties visibilities
required property var panels
required property real maxHeight
required property CustomTextField search
required property int padding
required property int rounding
readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `)
readonly property Item currentList: showWallpapers ? wallpaperList.item : appList.item
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
clip: true
state: showWallpapers ? "wallpapers" : "apps"
states: [
State {
name: "apps"
PropertyChanges {
root.implicitWidth: Config.launcher.sizes.itemWidth
root.implicitHeight: Math.min(root.maxHeight, appList.implicitHeight > 0 ? appList.implicitHeight : empty.implicitHeight)
appList.active: true
}
AnchorChanges {
anchors.left: root.parent.left
anchors.right: root.parent.right
}
},
State {
name: "wallpapers"
PropertyChanges {
root.implicitWidth: Math.max(Config.launcher.sizes.itemWidth * 1.2, wallpaperList.implicitWidth)
root.implicitHeight: Config.launcher.sizes.wallpaperHeight
wallpaperList.active: true
}
}
]
Behavior on state {
SequentialAnimation {
Anim {
target: root
property: "opacity"
from: 1
to: 0
duration: Appearance.anim.durations.small
}
PropertyAction {}
Anim {
target: root
property: "opacity"
from: 0
to: 1
duration: Appearance.anim.durations.small
}
}
}
Loader {
id: appList
active: false
anchors.fill: parent
sourceComponent: AppList {
search: root.search
visibilities: root.visibilities
}
}
Loader {
id: wallpaperList
active: false
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: WallpaperList {
search: root.search
visibilities: root.visibilities
panels: root.panels
content: root.content
}
}
Row {
id: empty
opacity: root.currentList?.count === 0 ? 1 : 0
scale: root.currentList?.count === 0 ? 1 : 0.5
spacing: Appearance.spacing.normal
padding: Appearance.padding.large
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.extraLarge
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
CustomText {
text: root.state === "wallpapers" ? qsTr("No wallpapers found") : qsTr("No results")
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
CustomText {
text: root.state === "wallpapers" && Wallpapers.list.length === 0 ? qsTr("Try putting some wallpapers in %1").arg(Paths.shortenHome(Paths.wallsdir)) : qsTr("Try searching for something else")
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
}
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
}
Behavior on implicitWidth {
enabled: root.visibilities.launcher
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
Behavior on implicitHeight {
enabled: root.visibilities.launcher
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
+70
View File
@@ -0,0 +1,70 @@
import QtQuick
import qs.Modules.Launcher.Services
import qs.Components
import qs.Helpers
import qs.Config
Item {
id: root
required property var modelData
required property var list
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
radius: Appearance.rounding.normal
function onClicked(): void {
root.modelData?.onClicked(root.list);
}
}
Item {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.larger
anchors.rightMargin: Appearance.padding.larger
anchors.margins: Appearance.padding.smaller
MaterialIcon {
id: icon
text: root.modelData?.icon ?? ""
font.pointSize: Appearance.font.size.extraLarge
anchors.verticalCenter: parent.verticalCenter
}
Item {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.verticalCenter: icon.verticalCenter
implicitWidth: parent.width - icon.width
implicitHeight: name.implicitHeight + desc.implicitHeight
CustomText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.normal
}
CustomText {
id: desc
text: root.modelData?.desc ?? ""
font.pointSize: Appearance.font.size.small
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
width: root.width - icon.width - Appearance.rounding.normal * 2
anchors.top: name.bottom
}
}
}
}
+74
View File
@@ -0,0 +1,74 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import qs.Modules.Launcher.Services
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules
Item {
id: root
required property DesktopEntry modelData
required property PersistentProperties visibilities
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
radius: Appearance.rounding.normal
function onClicked(): void {
Apps.launch(root.modelData);
root.visibilities.launcher = false;
}
}
Item {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.larger
anchors.rightMargin: Appearance.padding.larger
anchors.margins: Appearance.padding.smaller
IconImage {
id: icon
source: Quickshell.iconPath(root.modelData?.icon, "image-missing")
implicitSize: parent.height * 0.8
anchors.verticalCenter: parent.verticalCenter
}
Item {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.verticalCenter: icon.verticalCenter
implicitWidth: parent.width - icon.width
implicitHeight: name.implicitHeight + comment.implicitHeight
CustomText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.normal
}
CustomText {
id: comment
text: (root.modelData?.comment || root.modelData?.genericName || root.modelData?.name) ?? ""
font.pointSize: Appearance.font.size.small
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
width: root.width - icon.width - Appearance.rounding.normal * 2
anchors.top: name.bottom
}
}
}
}
+124
View File
@@ -0,0 +1,124 @@
import ZShell
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules
import qs.Helpers
import qs.Config
Item {
id: root
required property var list
readonly property string math: list.search.text.slice(`${Config.launcher.actionPrefix}calc `.length)
function onClicked(): void {
Quickshell.execDetached(["wl-copy", Qalculator.eval(math, false)]);
root.list.visibilities.launcher = false;
}
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
radius: Appearance.rounding.normal
function onClicked(): void {
root.onClicked();
}
}
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.larger
spacing: Appearance.spacing.normal
MaterialIcon {
text: "function"
font.pointSize: Appearance.font.size.extraLarge
Layout.alignment: Qt.AlignVCenter
}
CustomText {
id: result
color: {
if (text.includes("error: ") || text.includes("warning: "))
return DynamicColors.palette.m3error;
if (!root.math)
return DynamicColors.palette.m3onSurfaceVariant;
return DynamicColors.palette.m3onSurface;
}
text: root.math.length > 0 ? Qalculator.eval(root.math) : qsTr("Type an expression to calculate")
elide: Text.ElideLeft
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
CustomRect {
color: DynamicColors.palette.m3tertiary
radius: Appearance.rounding.normal
clip: true
implicitWidth: (stateLayer.containsMouse ? label.implicitWidth + label.anchors.rightMargin : 0) + icon.implicitWidth + Appearance.padding.normal * 2
implicitHeight: Math.max(label.implicitHeight, icon.implicitHeight) + Appearance.padding.small * 2
Layout.alignment: Qt.AlignVCenter
StateLayer {
id: stateLayer
color: DynamicColors.palette.m3onTertiary
function onClicked(): void {
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.terminal, "fish", "-C", `exec qalc -i '${root.math}'`]);
root.list.visibilities.launcher = false;
}
}
CustomText {
id: label
anchors.verticalCenter: parent.verticalCenter
anchors.right: icon.left
anchors.rightMargin: Appearance.spacing.small
text: qsTr("Open in calculator")
color: DynamicColors.palette.m3onTertiary
font.pointSize: Appearance.font.size.normal
opacity: stateLayer.containsMouse ? 1 : 0
Behavior on opacity {
Anim {}
}
}
MaterialIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
text: "open_in_new"
color: DynamicColors.palette.m3onTertiary
font.pointSize: Appearance.font.size.large
}
Behavior on implicitWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
}
}
+97
View File
@@ -0,0 +1,97 @@
import ZShell.Models
import Quickshell
import QtQuick
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules
Item {
id: root
required property FileSystemEntry modelData
required property PersistentProperties visibilities
scale: 0.5
opacity: 0
z: PathView.z ?? 0
Component.onCompleted: {
scale = Qt.binding(() => PathView.isCurrentItem ? 1 : PathView.onPath ? 0.8 : 0);
opacity = Qt.binding(() => PathView.onPath ? 1 : 0);
}
implicitWidth: image.width + Appearance.padding.larger * 2
implicitHeight: image.height + label.height + Appearance.spacing.small / 2 + Appearance.padding.large + Appearance.padding.normal
StateLayer {
radius: Appearance.rounding.normal
function onClicked(): void {
Wallpapers.setWallpaper(root.modelData.path);
root.visibilities.launcher = false;
}
}
Elevation {
anchors.fill: image
radius: image.radius
opacity: root.PathView.isCurrentItem ? 1 : 0
level: 4
Behavior on opacity {
Anim {}
}
}
CustomClippingRect {
id: image
anchors.horizontalCenter: parent.horizontalCenter
y: Appearance.padding.large
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
implicitWidth: Config.launcher.sizes.wallpaperWidth
implicitHeight: implicitWidth / 16 * 9
MaterialIcon {
anchors.centerIn: parent
text: "image"
color: DynamicColors.tPalette.m3outline
font.pointSize: Appearance.font.size.extraLarge * 2
font.weight: 600
}
CachingImage {
path: root.modelData.path
smooth: !root.PathView.view.moving
cache: true
anchors.fill: parent
}
}
CustomText {
id: label
anchors.top: image.bottom
anchors.topMargin: Appearance.spacing.small / 2
anchors.horizontalCenter: parent.horizontalCenter
width: image.width - Appearance.padding.normal * 2
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
renderType: Text.QtRendering
text: root.modelData.relativePath
font.pointSize: Appearance.font.size.normal
}
Behavior on scale {
Anim {}
}
Behavior on opacity {
Anim {}
}
}
+51
View File
@@ -0,0 +1,51 @@
pragma Singleton
import qs.Modules.Launcher
import qs.Helpers
import qs.Config
import Quickshell
import QtQuick
Searcher {
id: root
function transformSearch(search: string): string {
return search.slice(Config.launcher.actionPrefix.length);
}
list: variants.instances
useFuzzy: Config.launcher.useFuzzy.actions
Variants {
id: variants
model: Config.launcher.actions.filter(a => (a.enabled ?? true))
Action {}
}
component Action: QtObject {
required property var modelData
readonly property string name: modelData.name ?? qsTr("Unnamed")
readonly property string desc: modelData.description ?? qsTr("No description")
readonly property string icon: modelData.icon ?? "help_outline"
readonly property list<string> command: modelData.command ?? []
readonly property bool enabled: modelData.enabled ?? true
readonly property bool dangerous: modelData.dangerous ?? false
function onClicked(list: AppList): void {
if (command.length === 0)
return;
if (command[0] === "autocomplete" && command.length > 1) {
list.search.text = `${Config.launcher.actionPrefix}${command[1]} `;
} else if (command[0] === "setMode" && command.length > 1) {
list.visibilities.launcher = false;
Colours.setMode(command[1]);
} else {
list.visibilities.launcher = false;
Quickshell.execDetached(command);
}
}
}
}
+78
View File
@@ -0,0 +1,78 @@
pragma Singleton
import ZShell
import Quickshell
import qs.Config
import qs.Helpers
import qs.Paths
Searcher {
id: root
function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id);
if (entry.runInTerminal)
Quickshell.execDetached({
command: ["app2unit", "--", ...Config.general.apps.terminal, `${Quickshell.shellDir}/assets/wrap_term_launch.sh`, ...entry.command],
workingDirectory: entry.workingDirectory
});
else
Quickshell.execDetached({
command: ["app2unit", "--", ...entry.command],
workingDirectory: entry.workingDirectory
});
}
function search(search: string): list<var> {
const prefix = Config.launcher.specialPrefix;
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];
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;
}
function selector(item: var): string {
return keys.map(k => item[k]).join(" ");
}
list: appDb.apps
useFuzzy: Config.launcher.useFuzzy.apps
AppDb {
id: appDb
path: `${Paths.state}/apps.sqlite`
entries: DesktopEntries.applications.values
}
}
+97
View File
@@ -0,0 +1,97 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules.Launcher.Items
PathView {
id: root
required property CustomTextField search
required property var visibilities
required property var panels
required property var content
readonly property int itemWidth: Config.launcher.sizes.wallpaperWidth * 0.8 + Appearance.padding.larger * 2
readonly property int numItems: {
const screen = QsWindow.window?.screen;
if (!screen)
return 0;
// Screen width - 4x outer rounding - 2x max side thickness (cause centered)
const barMargins = panels.bar.implicitWidth;
let outerMargins = 0;
if (panels.popouts.hasCurrent && panels.popouts.currentCenter + panels.popouts.nonAnimHeight / 2 > screen.height - content.implicitHeight)
outerMargins = panels.popouts.nonAnimWidth;
if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins)
outerMargins = panels.utilities.implicitWidth;
const maxWidth = screen.width - Config.barConfig.rounding * 4 - (barMargins + outerMargins) * 2;
if (maxWidth <= 0)
return 0;
const maxItemsOnScreen = Math.floor(maxWidth / itemWidth);
const visible = Math.min(maxItemsOnScreen, Config.launcher.maxWallpapers, scriptModel.values.length);
if (visible === 2)
return 1;
if (visible > 1 && visible % 2 === 0)
return visible - 1;
return visible;
}
model: ScriptModel {
id: scriptModel
readonly property string search: root.search.text.split(" ").slice(1).join(" ")
values: Wallpapers.query(search)
onValuesChanged: root.currentIndex = search ? 0 : values.findIndex(w => w.path === Wallpapers.actualCurrent)
}
Component.onCompleted: currentIndex = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent)
Component.onDestruction: Wallpapers.stopPreview()
onCurrentItemChanged: {
if (currentItem)
Wallpapers.preview(currentItem.modelData.path);
}
implicitWidth: Math.min(numItems, count) * itemWidth
pathItemCount: numItems
cacheItemCount: 4
snapMode: PathView.SnapToItem
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
highlightRangeMode: PathView.StrictlyEnforceRange
delegate: WallpaperItem {
visibilities: root.visibilities
}
path: Path {
startY: root.height / 2
PathAttribute {
name: "z"
value: 0
}
PathLine {
x: root.width / 2
relativeY: 0
}
PathAttribute {
name: "z"
value: 1
}
PathLine {
x: root.width
relativeY: 0
}
}
}
+131
View File
@@ -0,0 +1,131 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Components
import qs.Config
import qs.Modules as Modules
Item {
id: root
required property ShellScreen screen
required property PersistentProperties visibilities
required property var panels
readonly property bool shouldBeActive: visibilities.launcher
property int contentHeight
readonly property real maxHeight: {
let max = screen.height - Appearance.spacing.large;
if (visibilities.dashboard)
max -= panels.dashboard.nonAnimHeight;
return max;
}
onMaxHeightChanged: timer.start()
visible: height > 0
implicitHeight: 0
implicitWidth: content.implicitWidth
onShouldBeActiveChanged: {
if (shouldBeActive) {
timer.stop();
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
SequentialAnimation {
id: showAnim
Modules.Anim {
target: root
property: "implicitHeight"
to: root.contentHeight
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
ScriptAction {
script: root.implicitHeight = Qt.binding(() => content.implicitHeight)
}
}
SequentialAnimation {
id: hideAnim
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
Modules.Anim {
target: root
property: "implicitHeight"
to: 0
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
Connections {
target: Config.launcher
function onEnabledChanged(): void {
timer.start();
}
function onMaxShownChanged(): void {
timer.start();
}
}
Connections {
target: DesktopEntries.applications
function onValuesChanged(): void {
if (DesktopEntries.applications.values.length < Config.launcher.maxAppsShown)
timer.start();
}
}
Timer {
id: timer
interval: Appearance.anim.durations.small
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
if (showAnim.running) {
showAnim.stop();
showAnim.start();
}
}
}
}
Loader {
id: content
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
visible: false
active: false
Component.onCompleted: timer.start()
sourceComponent: Content {
visibilities: root.visibilities
panels: root.panels
maxHeight: root.maxHeight
Component.onCompleted: root.contentHeight = implicitHeight
}
}
}