quickshell and hyprland additions

This commit is contained in:
2026-03-15 13:56:00 +02:00
parent c9c27d1554
commit 1ad06b82a6
509 changed files with 68371 additions and 19 deletions

View File

@@ -0,0 +1,257 @@
pragma ComponentBehavior: Bound
import "items"
import "services"
import qs.components
import qs.components.controls
import qs.components.containers
import qs.services
import qs.config
import Quickshell
import QtQuick
StyledListView {
id: root
required property StyledTextField search
required property PersistentProperties visibilities
model: ScriptModel {
id: model
onValuesChanged: root.currentIndex = 0
}
spacing: Appearance.spacing.small
orientation: Qt.Vertical
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxShown, count) - spacing
preferredHighlightBegin: 0
preferredHighlightEnd: height
highlightRangeMode: ListView.ApplyRange
highlightFollowsCurrentItem: false
highlight: StyledRect {
radius: Appearance.rounding.normal
color: Colours.palette.m3onSurface
opacity: 0.08
y: root.currentItem?.y ?? 0
implicitWidth: root.width
implicitHeight: root.currentItem?.implicitHeight ?? 0
Behavior on y {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
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";
}
onStateChanged: {
if (state === "scheme" || state === "variant")
Schemes.reload();
}
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
}
},
State {
name: "scheme"
PropertyChanges {
model.values: Schemes.query(search.text)
root.delegate: schemeItem
}
},
State {
name: "variant"
PropertyChanges {
model.values: M3Variants.query(search.text)
root.delegate: variantItem
}
}
]
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
Anim {
target: root
property: "opacity"
from: 1
to: 0
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
Anim {
target: root
property: "scale"
from: 1
to: 0.9
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
}
PropertyAction {
targets: [model, root]
properties: "values,delegate"
}
ParallelAnimation {
Anim {
target: root
property: "opacity"
from: 0
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
Anim {
target: root
property: "scale"
from: 0.9
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
}
PropertyAction {
targets: [root.add, root.remove]
property: "enabled"
value: true
}
}
}
StyledScrollBar.vertical: StyledScrollBar {
flickable: root
}
add: Transition {
enabled: !root.state
Anim {
properties: "opacity,scale"
from: 0
to: 1
}
}
remove: Transition {
enabled: !root.state
Anim {
properties: "opacity,scale"
from: 1
to: 0
}
}
move: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
}
addDisplaced: Transition {
Anim {
property: "y"
duration: Appearance.anim.durations.small
}
Anim {
properties: "opacity,scale"
to: 1
}
}
displaced: Transition {
Anim {
property: "y"
}
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
}
}
Component {
id: schemeItem
SchemeItem {
list: root
}
}
Component {
id: variantItem
VariantItem {
list: root
}
}
}

View File

@@ -0,0 +1,60 @@
import qs.components
import qs.services
import qs.config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
required property Wrapper wrapper
readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
strokeWidth: -1
fillColor: Colours.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 {
CAnim {}
}
}

View File

@@ -0,0 +1,191 @@
pragma ComponentBehavior: Bound
import "services"
import qs.components
import qs.components.controls
import qs.services
import qs.config
import Quickshell
import QtQuick
Item {
id: root
required property PersistentProperties visibilities
required property var panels
required property real maxHeight
readonly property int padding: Appearance.padding.large
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
}
}
StyledRect {
id: searchWrapper
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Appearance.rounding.full
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
text: "search"
color: Colours.palette.m3onSurfaceVariant
}
StyledTextField {
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 (Colours.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?.decrementCurrentIndex()
Keys.onDownPressed: list.currentList?.incrementCurrentIndex()
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
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: Colours.palette.m3onSurfaceVariant
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: search.text ? Qt.PointingHandCursor : undefined
onClicked: search.text = ""
}
Behavior on width {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
}
}
}

View File

@@ -0,0 +1,170 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.components.controls
import qs.services
import qs.config
import qs.utils
import Quickshell
import QtQuick
Item {
id: root
required property var content
required property PersistentProperties visibilities
required property var panels
required property real maxHeight
required property StyledTextField 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: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.extraLarge
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: root.state === "wallpapers" ? qsTr("No wallpapers found") : qsTr("No results")
color: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
StyledText {
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: Colours.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.large
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
}
Behavior on implicitHeight {
enabled: root.visibilities.launcher
Anim {
duration: Appearance.anim.durations.large
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
}
}

View File

@@ -0,0 +1,97 @@
pragma ComponentBehavior: Bound
import "items"
import qs.components.controls
import qs.services
import qs.config
import Quickshell
import QtQuick
PathView {
id: root
required property StyledTextField 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 = Math.max(Config.border.thickness, panels.bar.implicitWidth);
let outerMargins = 0;
if (panels.popouts.hasCurrent && panels.popouts.currentCenter + panels.popouts.nonAnimHeight / 2 > screen.height - content.implicitHeight - Config.border.thickness * 2)
outerMargins = panels.popouts.nonAnimWidth;
if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins)
outerMargins = panels.utilities.implicitWidth;
const maxWidth = screen.width - Config.border.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
}
}
}

View File

@@ -0,0 +1,130 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.config
import Quickshell
import QtQuick
Item {
id: root
required property ShellScreen screen
required property PersistentProperties visibilities
required property var panels
readonly property bool shouldBeActive: visibilities.launcher && Config.launcher.enabled
property int contentHeight
readonly property real maxHeight: {
let max = screen.height - Config.border.thickness * 2 - 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
Anim {
target: root
property: "implicitHeight"
to: root.contentHeight
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
ScriptAction {
script: root.implicitHeight = Qt.binding(() => content.implicitHeight)
}
}
SequentialAnimation {
id: hideAnim
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
Anim {
target: root
property: "implicitHeight"
to: 0
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
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.maxShown)
timer.start();
}
}
Timer {
id: timer
interval: Appearance.anim.durations.extraLarge
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
}
}
}

View File

@@ -0,0 +1,70 @@
import "../services"
import qs.components
import qs.services
import qs.config
import QtQuick
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
StyledText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.normal
}
StyledText {
id: desc
text: root.modelData?.desc ?? ""
font.pointSize: Appearance.font.size.small
color: Colours.palette.m3outline
elide: Text.ElideRight
width: root.width - icon.width - Appearance.rounding.normal * 2
anchors.top: name.bottom
}
}
}
}

View File

@@ -0,0 +1,88 @@
import "../services"
import qs.components
import qs.services
import qs.config
import qs.utils
import Quickshell
import Quickshell.Widgets
import QtQuick
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 - favouriteIcon.width
implicitHeight: name.implicitHeight + comment.implicitHeight
StyledText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.normal
}
StyledText {
id: comment
text: (root.modelData?.comment || root.modelData?.genericName || root.modelData?.name) ?? ""
font.pointSize: Appearance.font.size.small
color: Colours.palette.m3outline
elide: Text.ElideRight
width: root.width - icon.width - favouriteIcon.width - Appearance.rounding.normal * 2
anchors.top: name.bottom
}
}
Loader {
id: favouriteIcon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
active: modelData && Strings.testRegexList(Config.launcher.favouriteApps, modelData.id)
sourceComponent: MaterialIcon {
text: "favorite"
fill: 1
color: Colours.palette.m3primary
}
}
}
}

View File

@@ -0,0 +1,123 @@
import qs.components
import qs.services
import qs.config
import Caelestia
import Quickshell
import QtQuick
import QtQuick.Layouts
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
}
StyledText {
id: result
color: {
if (text.includes("error: ") || text.includes("warning: "))
return Colours.palette.m3error;
if (!root.math)
return Colours.palette.m3onSurfaceVariant;
return Colours.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
}
StyledRect {
color: Colours.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: Colours.palette.m3onTertiary
function onClicked(): void {
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.terminal, "fish", "-C", `exec qalc -i '${root.math}'`]);
root.list.visibilities.launcher = false;
}
}
StyledText {
id: label
anchors.verticalCenter: parent.verticalCenter
anchors.right: icon.left
anchors.rightMargin: Appearance.spacing.small
text: qsTr("Open in calculator")
color: Colours.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: Colours.palette.m3onTertiary
font.pointSize: Appearance.font.size.large
}
Behavior on implicitWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
}
}
}

View File

@@ -0,0 +1,104 @@
import "../services"
import qs.components
import qs.services
import qs.config
import QtQuick
Item {
id: root
required property Schemes.Scheme 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
StyledRect {
id: preview
anchors.verticalCenter: parent.verticalCenter
border.width: 1
border.color: Qt.alpha(`#${root.modelData?.colours?.outline}`, 0.5)
color: `#${root.modelData?.colours?.surface}`
radius: Appearance.rounding.full
implicitWidth: parent.height * 0.8
implicitHeight: parent.height * 0.8
Item {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: parent.implicitWidth / 2
clip: true
StyledRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: preview.implicitWidth
color: `#${root.modelData?.colours?.primary}`
radius: Appearance.rounding.full
}
}
}
Column {
anchors.left: preview.right
anchors.leftMargin: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
width: parent.width - preview.width - anchors.leftMargin - (current.active ? current.width + Appearance.spacing.normal : 0)
spacing: 0
StyledText {
text: root.modelData?.flavour ?? ""
font.pointSize: Appearance.font.size.normal
}
StyledText {
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.small
color: Colours.palette.m3outline
elide: Text.ElideRight
anchors.left: parent.left
anchors.right: parent.right
}
}
Loader {
id: current
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
active: `${root.modelData?.name} ${root.modelData?.flavour}` === Schemes.currentScheme
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
}
}
}

View File

@@ -0,0 +1,80 @@
import "../services"
import qs.components
import qs.services
import qs.config
import QtQuick
Item {
id: root
required property M3Variants.Variant 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
}
Column {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.larger
anchors.verticalCenter: icon.verticalCenter
width: parent.width - icon.width - anchors.leftMargin - (current.active ? current.width + Appearance.spacing.normal : 0)
spacing: 0
StyledText {
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.normal
}
StyledText {
text: root.modelData?.description ?? ""
font.pointSize: Appearance.font.size.small
color: Colours.palette.m3outline
elide: Text.ElideRight
anchors.left: parent.left
anchors.right: parent.right
}
}
Loader {
id: current
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
active: root.modelData?.variant === Schemes.currentVariant
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
}
}
}

View File

@@ -0,0 +1,98 @@
import qs.components
import qs.components.effects
import qs.components.images
import qs.services
import qs.config
import Caelestia.Models
import Quickshell
import QtQuick
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 {}
}
}
StyledClippingRect {
id: image
anchors.horizontalCenter: parent.horizontalCenter
y: Appearance.padding.large
color: Colours.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
implicitWidth: Config.launcher.sizes.wallpaperWidth
implicitHeight: implicitWidth / 16 * 9
MaterialIcon {
anchors.centerIn: parent
text: "image"
color: Colours.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
}
}
StyledText {
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 {}
}
}

View File

@@ -0,0 +1,52 @@
pragma Singleton
import ".."
import qs.services
import qs.config
import qs.utils
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) && (Config.launcher.enableDangerousActions || !(a.dangerous ?? false)))
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);
}
}
}
}

View File

@@ -0,0 +1,78 @@
pragma Singleton
import qs.config
import qs.utils
import Caelestia
import Quickshell
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`
// favouriteApps: Config.launcher.favouriteApps
entries: DesktopEntries.applications.values.filter(a => !Strings.testRegexList(Config.launcher.hiddenApps, a.id))
}
}

View File

@@ -0,0 +1,85 @@
pragma Singleton
import ".."
import qs.config
import qs.utils
import Quickshell
import QtQuick
Searcher {
id: root
function transformSearch(search: string): string {
return search.slice(`${Config.launcher.actionPrefix}variant `.length);
}
list: [
Variant {
variant: "vibrant"
icon: "sentiment_very_dissatisfied"
name: qsTr("Vibrant")
description: qsTr("A high chroma palette. The primary palette's chroma is at maximum.")
},
Variant {
variant: "tonalspot"
icon: "android"
name: qsTr("Tonal Spot")
description: qsTr("Default for Material theme colours. A pastel palette with a low chroma.")
},
Variant {
variant: "expressive"
icon: "compare_arrows"
name: qsTr("Expressive")
description: qsTr("A medium chroma palette. The primary palette's hue is different from the seed colour, for variety.")
},
Variant {
variant: "fidelity"
icon: "compare"
name: qsTr("Fidelity")
description: qsTr("Matches the seed colour, even if the seed colour is very bright (high chroma).")
},
Variant {
variant: "content"
icon: "sentiment_calm"
name: qsTr("Content")
description: qsTr("Almost identical to fidelity.")
},
Variant {
variant: "fruitsalad"
icon: "nutrition"
name: qsTr("Fruit Salad")
description: qsTr("A playful theme - the seed colour's hue does not appear in the theme.")
},
Variant {
variant: "rainbow"
icon: "looks"
name: qsTr("Rainbow")
description: qsTr("A playful theme - the seed colour's hue does not appear in the theme.")
},
Variant {
variant: "neutral"
icon: "contrast"
name: qsTr("Neutral")
description: qsTr("Close to grayscale, a hint of chroma.")
},
Variant {
variant: "monochrome"
icon: "filter_b_and_w"
name: qsTr("Monochrome")
description: qsTr("All colours are grayscale, no chroma.")
}
]
useFuzzy: Config.launcher.useFuzzy.variants
component Variant: QtObject {
required property string variant
required property string icon
required property string name
required property string description
function onClicked(list: AppList): void {
list.visibilities.launcher = false;
Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]);
}
}
}

View File

@@ -0,0 +1,88 @@
pragma Singleton
import ".."
import qs.config
import qs.utils
import Quickshell
import Quickshell.Io
import QtQuick
Searcher {
id: root
property string currentScheme
property string currentVariant
function transformSearch(search: string): string {
return search.slice(`${Config.launcher.actionPrefix}scheme `.length);
}
function selector(item: var): string {
return `${item.name} ${item.flavour}`;
}
function reload(): void {
getCurrent.running = true;
}
list: schemes.instances
useFuzzy: Config.launcher.useFuzzy.schemes
keys: ["name", "flavour"]
weights: [0.9, 0.1]
Variants {
id: schemes
Scheme {}
}
Process {
id: getSchemes
running: true
command: ["caelestia", "scheme", "list"]
stdout: StdioCollector {
onStreamFinished: {
const schemeData = JSON.parse(text);
const list = Object.entries(schemeData).map(([name, f]) => Object.entries(f).map(([flavour, colours]) => ({
name,
flavour,
colours
})));
const flat = [];
for (const s of list)
for (const f of s)
flat.push(f);
schemes.model = flat.sort((a, b) => (a.name + a.flavour).localeCompare((b.name + b.flavour)));
}
}
}
Process {
id: getCurrent
running: true
command: ["caelestia", "scheme", "get", "-nfv"]
stdout: StdioCollector {
onStreamFinished: {
const [name, flavour, variant] = text.trim().split("\n");
root.currentScheme = `${name} ${flavour}`;
root.currentVariant = variant;
}
}
}
component Scheme: QtObject {
required property var modelData
readonly property string name: modelData.name
readonly property string flavour: modelData.flavour
readonly property var colours: modelData.colours
function onClicked(list: AppList): void {
list.visibilities.launcher = false;
Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]);
}
}
}