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
-72
View File
@@ -1,72 +0,0 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import qs
Item {
id: root
required property DesktopEntry modelData
implicitHeight: 48
anchors.left: parent?.left
anchors.right: parent?.right
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: event => onClicked(event)
function onClicked(): void {
Search.launch(root.modelData);
}
}
Item {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
anchors.margins: 4
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: 8
anchors.verticalCenter: icon.verticalCenter
implicitWidth: parent.width - icon.width
implicitHeight: name.implicitHeight + comment.implicitHeight
Text {
id: name
text: root.modelData?.name || qsTr("Unknown Application")
font.pointSize: 12
color: mouseArea.containsMouse ? "#ffffff" : "#cccccc"
elide: Text.ElideRight
}
Text {
id: comment
text: ( root.modelData?.comment || root.modelData?.genericName || root.modelData?.name ) ?? ""
font.pointSize: 10
color: mouseArea.containsMouse ? "#dddddd" : "#888888"
elide: Text.ElideRight
width: root.width - icon.width - 4 * 2
anchors.top: name.bottom
}
}
}
}
-153
View File
@@ -1,153 +0,0 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Daemons
Repeater {
model: ScriptModel {
values: {
const map = new Map();
for ( const n of NotifServer.notClosed )
map.set( n.appName, null );
for ( const n of NotifServer.list )
map.set( n.appName, null );
return [ ...map.keys() ];
}
onValuesChanged: {
root.flagChanged();
}
}
Column {
id: groupColumn
required property string modelData
property list<var> notifications: NotifServer.list.filter( n => n.appName === modelData )
width: parent.width
spacing: 10
property bool shouldShow: false
property bool isExpanded: false
property bool collapseAnimRunning: false
property color textColor: DynamicColors.palette.m3onSurface
function closeAll(): void {
for ( const n of NotifServer.notClosed.filter( n => n.appName === modelData ))
n.close();
}
Behavior on height {
Anim {}
}
Behavior on y {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
add: Transition {
id: addTrans
SequentialAnimation {
PauseAnimation {
duration: ( addTrans.ViewTransition.index - addTrans.ViewTransition.targetIndexes[ 0 ]) * 30
}
ParallelAnimation {
NumberAnimation {
properties: "y";
from: addTrans.ViewTransition.destination.y - (height / 2);
to: addTrans.ViewTransition.destination.y;
duration: 100;
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "opacity";
from: 0;
to: 1;
duration: 100;
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "scale";
from: 0.7;
to: 1.0;
duration: 100
easing.type: Easing.InOutQuad
}
}
}
}
Timer {
interval: addTrans.ViewTransition.targetIndexes.length * 30 + 100
running: groupColumn.isExpanded
repeat: false
onTriggered: {
groupColumn.shouldShow = true;
console.log("ran timer");
}
}
move: Transition {
id: moveTrans
NumberAnimation {
properties: "y";
duration: 100;
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "opacity, scale";
to: 1.0;
}
}
RowLayout {
width: parent.width
height: 30
Text {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.leftMargin: 5
text: groupColumn.modelData
color: groupColumn.textColor
font.pointSize: 14
font.bold: true
}
Rectangle {
id: collapseRect
property color notifyBgColor: DynamicColors.palette.m3primary
property color notifyColor: DynamicColors.palette.m3onPrimary
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillHeight: true
Layout.preferredWidth: 30
color: !groupColumn.isExpanded ? collapseRect.notifyBgColor : ( collapseArea.containsMouse ? "#15FFFFFF" : "transparent" )
radius: groupColumn.isExpanded ? 4 : height / 2
visible: true
Text {
anchors.centerIn: parent
text: groupColumn.isExpanded ? "\ue944" : groupColumn.notifications.length
font.family: groupColumn.isExpanded ? "Material Symbols Rounded" : "Rubik"
font.pointSize: 18
color: groupColumn.isExpanded ? groupColumn.textColor : collapseRect.notifyColor
}
MouseArea {
id: collapseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
groupColumn.collapseAnimRunning = true;
}
}
}
}
NotifGroupRepeater { id: groupRepeater }
}
}
-538
View File
@@ -1,538 +0,0 @@
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick
import QtQuick.Controls
import qs.Components
import qs.Config
import qs.Helpers
import qs.Effects
import qs.Paths
Scope {
id: root
PanelWindow {
id: launcherWindow
anchors {
top: true
left: true
right: true
bottom: true
}
color: "transparent"
visible: false
WlrLayershell.namespace: "ZShell-Launcher"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
onVisibleChanged: {
if ( !visible ) {
searchInput.text = "";
appListLoader.item.currentIndex = 0;
appListLoader.item.positionViewAtBeginning();
}
}
CustomShortcut {
name: "toggle-launcher"
onPressed: {
if ( !launcherWindow.visible ) {
if ( !openAnim.running ) {
openAnim.start();
}
} else if ( launcherWindow.visible ) {
if ( !closeAnim.running ) {
closeAnim.start();
}
}
searchInput.forceActiveFocus();
}
}
ShadowRect {
id: effects
anchors {
top: appListRect.top
bottom: backgroundRect.bottom
left: appListRect.left
right: appListRect.right
}
radius: 8
}
Rectangle {
id: backgroundRect
property color backgroundColor: DynamicColors.tPalette.m3surface
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
implicitHeight: mainLayout.childrenRect.height + 20
implicitWidth: appListRect.implicitWidth
x: Math.round(( parent.width - width ) / 2 )
color: backgroundColor
opacity: 1
ParallelAnimation {
id: openAnim
Anim {
target: appListRect
duration: MaterialEasing.expressiveDefaultSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
property: "implicitHeight"
from: 40
to: appListContainer.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.expressiveDefaultSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
property: "implicitHeight"
from: appListContainer.implicitHeight
to: 0
}
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;
}
}
Column {
id: mainLayout
anchors.fill: parent
anchors.margins: 10
spacing: 5
clip: true
CustomTextField {
id: searchInput
implicitHeight: 30
implicitWidth: parent.width
}
}
}
Rectangle {
id: appListRect
x: Math.round(( parent.width - width ) / 2 )
implicitWidth: appListContainer.implicitWidth + 20
implicitHeight: appListContainer.implicitHeight + 20
anchors.bottom: backgroundRect.top
color: backgroundRect.color
topRightRadius: 8
topLeftRadius: 8
clip: true
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveFastSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
}
}
Behavior on implicitWidth {
Anim {
duration: MaterialEasing.expressiveFastSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
}
}
Item {
anchors.centerIn: parent
id: appListContainer
visible: true
clip: true
property var showWallpapers: searchInput.text.startsWith(">")
state: showWallpapers ? "wallpaperpicker" : "apps"
states: [
State {
name: "apps"
PropertyChanges {
appListLoader.active: true
appListContainer.implicitHeight: appListLoader.implicitHeight
appListContainer.implicitWidth: 600
}
},
State {
name: "wallpaperpicker"
PropertyChanges {
wallpaperPickerLoader.active: true
appListContainer.implicitHeight: wallpaperPickerLoader.implicitHeight
appListContainer.implicitWidth: wallpaperPickerLoader.implicitWidth
}
}
]
Loader {
id: wallpaperPickerLoader
active: false
anchors.fill: parent
sourceComponent: PathView {
id: wallpaperPickerView
anchors.fill: parent
model: ScriptModel {
id: wallpaperModel
readonly property string search: searchInput.text.split(" ").slice(1).join(" ")
values: SearchWallpapers.query( search )
onValuesChanged: wallpaperPickerView.currentIndex = SearchWallpapers.list.findIndex( w => w.path === WallpaperPath.currentWallpaperPath )
}
readonly property int itemWidth: 288 + 10
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 margins = 10;
const maxWidth = screen.width - margins * 2;
if ( maxWidth <= 0 )
return 0;
const maxItemsOnScreen = Math.floor( maxWidth / itemWidth );
const visible = Math.min( maxItemsOnScreen, Config.launcher.maxWallpapers, wallpaperModel.values.length );
if ( visible === 2 )
return 1;
if ( visible > 1 && visible % 2 === 0 )
return visible - 1;
return visible;
}
Component.onCompleted: currentIndex = SearchWallpapers.list.findIndex( w => w.path === WallpaperPath.currentWallpaperPath )
Component.onDestruction: SearchWallpapers.stopPreview()
onCurrentItemChanged: {
if ( currentItem )
SearchWallpapers.preview( currentItem.modelData.path );
Quickshell.execDetached(["python3", Quickshell.shellPath("scripts/SchemeColorGen.py"), `--path=${currentItem.modelData.path}`, `--thumbnail=${Paths.cache}/imagecache/thumbnail.jpg`, `--output=${Paths.state}/scheme.json`, `--scheme=${Config.colors.schemeType}`]);
}
cacheItemCount: 5
snapMode: PathView.SnapToItem
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
highlightRangeMode: PathView.StrictlyEnforceRange
pathItemCount: numItems
implicitHeight: 212
implicitWidth: Math.min( numItems, count ) * itemWidth
path: Path {
startY: wallpaperPickerView.height / 2
PathAttribute {
name: "z"
value: 0
}
PathLine {
x: wallpaperPickerView.width / 2
relativeY: 0
}
PathAttribute {
name: "z"
value: 1
}
PathLine {
x: wallpaperPickerView.width
relativeY: 0
}
}
focus: true
delegate: WallpaperItem { }
}
}
Loader {
id: appListLoader
active: false
anchors.fill: parent
sourceComponent: ListView {
id: appListView
property color highlightColor: DynamicColors.tPalette.m3onSurface
anchors.fill: parent
model: ScriptModel {
id: appModel
onValuesChanged: {
appListView.currentIndex = 0;
}
}
verticalLayoutDirection: ListView.BottomToTop
implicitHeight: Math.min( count, Config.launcher.maxAppsShown ) * 48
preferredHighlightBegin: 0
preferredHighlightEnd: appListView.height
highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
focus: true
highlight: Rectangle {
radius: 4
color: appListView.highlightColor
opacity: 0.20
y: appListView.currentItem?.y
implicitWidth: appListView.width
implicitHeight: appListView.currentItem?.implicitHeight ?? 0
Behavior on y {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
property list<var> search: Search.search( searchInput.text )
state: {
const text = searchInput.text
if ( search.length === 0 ) {
return "noresults"
} else {
return "apps"
}
}
states: [
State {
name: "apps"
PropertyChanges {
appModel.values: Search.search(searchInput.text)
appListView.delegate: appItem
}
},
State {
name: "noresults"
PropertyChanges {
appModel.values: [1]
appListView.delegate: noResultsItem
}
}
]
Component {
id: appItem
AppItem {
}
}
Component {
id: noResultsItem
Item {
width: appListView.width
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
})
}
Text {
anchors.left: icon.right
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: "No results found"
color: "#cccccc"
renderType: Text.NativeRendering
font.pointSize: 12
font.family: "Rubik"
}
}
}
Component {
id: wallpaperItem
WallpaperItem { }
}
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
Anim {
target: appListView
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]
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 {
enabled: !appListView.state
Anim {
properties: "opacity"
from: 0
to: 1
}
Anim {
properties: "scale"
from: 0.95
to: 1
}
}
remove: Transition {
enabled: !appListView.state
Anim {
properties: "opacity"
from: 1
to: 0
}
Anim {
properties: "scale"
from: 1
to: 0.95
}
}
move: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
}
addDisplaced: Transition {
Anim {
property: "y"
duration: 200
}
Anim {
properties: "opacity,scale"
to: 1
}
}
displaced: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
}
}
}
}
}
}
}
+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);
}
}
}
}
@@ -2,26 +2,31 @@ pragma Singleton
import ZShell
import Quickshell
import Quickshell.Io
import qs.Config
import qs.Helpers
import qs.Paths
Searcher {
id: root
readonly property string home: Quickshell.env("HOME")
function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id);
console.log( "Search command:", entry.command );
Quickshell.execDetached({
command: ["app2unit", "--", ...entry.command],
workingDirectory: entry.workingDirectory || Quickshell.env("HOME")
});
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 = ">";
const prefix = Config.launcher.specialPrefix;
if (search.startsWith(`${prefix}i `)) {
keys = ["id", "name"];
weights = [0.9, 0.1];
@@ -62,12 +67,12 @@ Searcher {
}
list: appDb.apps
useFuzzy: true
useFuzzy: Config.launcher.useFuzzy.apps
AppDb {
id: appDb
path: `${root.home}/.local/share/z-cast-qt/apps.sqlite`
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
}
}
}
-55
View File
@@ -1,55 +0,0 @@
import Quickshell
import "../scripts/fzf.js" as Fzf
import "../scripts/fuzzysort.js" as Fuzzy
import QtQuick
Singleton {
required property list<QtObject> list
property string key: "name"
property bool useFuzzy: false
property var extraOpts: ({})
// Extra stuff for fuzzy
property list<string> keys: [key]
property list<real> weights: [1]
readonly property var fzf: useFuzzy ? [] : new Fzf.Finder(list, Object.assign({
selector
}, extraOpts))
readonly property list<var> fuzzyPrepped: useFuzzy ? list.map(e => {
const obj = {
_item: e
};
for (const k of keys)
obj[k] = Fuzzy.prepare(e[k]);
return obj;
}) : []
function transformSearch(search: string): string {
return search;
}
function selector(item: var): string {
// Only for fzf
return item[key];
}
function query(search: string): list<var> {
search = transformSearch(search);
if (!search)
return [...list];
if (useFuzzy)
return Fuzzy.go(search, fuzzyPrepped, Object.assign({
all: true,
keys,
scoreFn: r => weights.reduce((a, w, i) => a + r[i].score * w, 0)
}, extraOpts)).map(r => r.obj._item);
return fzf.find(search).sort((a, b) => {
if (a.score === b.score)
return selector(a.item).trim().length - selector(b.item).trim().length;
return b.score - a.score;
}).map(r => r.item);
}
}
+25
View File
@@ -0,0 +1,25 @@
import ZShell
import Quickshell
import Quickshell.Io
import qs.Components
import qs.Helpers
Scope {
id: root
property bool launcherInterrupted
readonly property bool hasFullscreen: Hypr.focusedWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) ?? false
CustomShortcut {
name: "toggle-launcher"
description: "Toggle launcher"
onPressed: root.launcherInterrupted = false
onReleased: {
if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher;
}
root.launcherInterrupted = false;
}
}
}
-248
View File
@@ -1,248 +0,0 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick.Layouts
import QtQuick
import qs.Components
import qs.Config
import qs.Daemons
import qs.Helpers
import qs.Effects
PanelWindow {
id: root
color: "transparent"
anchors {
top: true
right: true
left: true
bottom: true
}
WlrLayershell.namespace: "ZShell-Notifs"
WlrLayershell.layer: WlrLayer.Overlay
mask: Region { regions: root.notifRegions }
exclusionMode: ExclusionMode.Ignore
property list<Region> notifRegions: []
required property bool centerShown
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "white"
property color backgroundColor: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor
// visible: Hyprland.monitorFor(screen).focused
ListView {
id: notifListView
model: ScriptModel {
values: NotifServer.list.filter( n => n.popup )
onValuesChanged: {
if ( values.length === 0 )
root.notifRegions = [];
}
}
anchors.top: parent.top
anchors.bottom: parent.bottom
x: root.centerShown ? root.screen.width - width - 420 : root.screen.width - width - 20
z: 0
anchors.topMargin: 54
width: 400
spacing: 10
Behavior on x {
NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
displaced: Transition {
NumberAnimation {
property: "y"
duration: 100
easing.type: Easing.InOutQuad
}
}
remove: Transition {
id: hideTransition
ParallelAnimation {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: 200
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
to: hideTransition.ViewTransition.destination.x + 200
duration: 200
easing.type: Easing.InOutQuad
}
}
}
add: Transition {
id: showTransition
ParallelAnimation {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 200
easing.type: Easing.InOutQuad
}
NumberAnimation {
property: "x"
from: showTransition.ViewTransition.destination.x + 200
to: showTransition.ViewTransition.destination.x
duration: 200
easing.type: Easing.InOutQuad
}
}
}
component NotifRegion: Region { }
Component {
id: notifRegion
NotifRegion {}
}
delegate: Item {
id: rootItem
implicitWidth: 400
implicitHeight: contentLayout.childrenRect.height + 16
required property NotifServer.Notif modelData
ShadowRect {
anchors.fill: backgroundRect
radius: backgroundRect.radius
}
CustomClippingRect {
id: backgroundRect
implicitWidth: 400
implicitHeight: contentLayout.childrenRect.height + 16
color: root.backgroundColor
border.width: Config.useDynamicColors ? 0 : 1
border.color: "#555555"
radius: 8
CustomRect {
anchors.bottom: parent.bottom
anchors.right: parent.right
color: DynamicColors.palette.m3primary
implicitHeight: 4
implicitWidth: ( rootItem.modelData.timer.remainingTime / rootItem.modelData.timer.totalTime ) * parent.width
Behavior on implicitWidth {
Anim {}
}
}
Component.onCompleted: {
root.notifRegions.push( notifRegion.createObject(root, { item: backgroundRect }));
}
Column {
id: contentLayout
z: 0
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 10
spacing: 8
RowLayout {
spacing: 12
IconImage {
source: rootItem.modelData.image === "" ? Qt.resolvedUrl(rootItem.modelData.appIcon) : Qt.resolvedUrl(rootItem.modelData.image)
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.alignment: Qt.AlignHCenter | Qt.AlignLeft
// visible: rootItem.modelData.image !== ""
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 0
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Text {
text: rootItem.modelData.appName
color: root.textColor
font.bold: true
font.pointSize: 14
elide: Text.ElideRight
wrapMode: Text.NoWrap
Layout.fillWidth: true
}
Text {
text: rootItem.modelData.summary
color: root.textColor
font.pointSize: 12
font.bold: true
elide: Text.ElideRight
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}
Text {
text: rootItem.modelData.body
color: root.textColor
font.pointSize: 14
textFormat: Text.MarkdownText
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 4
width: parent.width
linkColor: Config.accentColor.accents.primaryAlt
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
}
}
}
Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 6
anchors.topMargin: 6
width: 18
height: 18
color: closeArea.containsMouse ? "#FF6077" : "transparent"
radius: 9
Text {
anchors.centerIn: parent
text: "✕"
color: closeArea.containsMouse ? "white" : "#888888"
font.pointSize: 12
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
rootItem.modelData.close();
}
}
}
ElapsedTimer {
id: timer
}
}
}
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ import qs.Config
Item {
id: root
property string source: SearchWallpapers.current
property string source: Wallpapers.current
property Image current: one
anchors.fill: parent
@@ -50,7 +50,7 @@ Item {
anchors.fill: parent
opacity: 0
scale: SearchWallpapers.showPreview ? 1 : 0.8
scale: Wallpapers.showPreview ? 1 : 0.8
asynchronous: true
onStatusChanged: {
if (status === Image.Ready) {
-44
View File
@@ -1,44 +0,0 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import ZShell.Models
Item {
id: root
required property FileSystemEntry modelData
implicitWidth: 288
implicitHeight: 162
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);
}
ClippingRectangle {
anchors.fill: parent
radius: 8
color: "#10FFFFFF"
Image {
id: thumbnailImage
asynchronous: true
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: root.modelData.path
sourceSize.width: 960
sourceSize.height: 540
}
}
Behavior on scale {
Anim {}
}
Behavior on opacity {
Anim {}
}
}
-68
View File
@@ -1,68 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import qs.Helpers
import qs.Config
import qs.Components
Item {
id: root
property string currentTitle: Hypr.activeName
Layout.fillHeight: true
Layout.preferredWidth: Math.max( titleText1.implicitWidth, titleText2.implicitWidth ) + 10
clip: true
property bool showFirst: true
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "white"
// Component.onCompleted: {
// Hyprland.rawEvent.connect(( event ) => {
// if (event.name === "activewindow") {
// InitialTitle.getInitialTitle( function( initialTitle ) {
// root.currentTitle = initialTitle
// })
// }
// })
// }
onCurrentTitleChanged: {
if (showFirst) {
titleText2.text = currentTitle
showFirst = false
} else {
titleText1.text = currentTitle
showFirst = true
}
}
CustomText {
id: titleText1
anchors.fill: parent
anchors.margins: 5
text: root.currentTitle
color: root.textColor
elide: Text.ElideRight
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: root.showFirst ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
}
CustomText {
id: titleText2
anchors.fill: parent
anchors.margins: 5
color: root.textColor
elide: Text.ElideRight
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: root.showFirst ? 0 : 1
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
}
}