launcher overhaul
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user