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,102 @@
import ".."
import qs.services
import qs.config
import QtQuick
import QtQuick.Shapes
Item {
id: root
required property var currentItem
implicitWidth: content.implicitWidth + Appearance.padding.larger + content.anchors.rightMargin
implicitHeight: currentItem ? content.implicitHeight + Appearance.padding.normal + content.anchors.bottomMargin : 0
Shape {
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: path
readonly property real rounding: Appearance.rounding.small
readonly property bool flatten: root.implicitHeight < rounding * 2
readonly property real roundingY: flatten ? root.implicitHeight / 2 : rounding
strokeWidth: -1
fillColor: Colours.tPalette.m3surfaceContainer
startX: root.implicitWidth
startY: root.implicitHeight
PathLine {
relativeX: -(root.implicitWidth + path.rounding)
relativeY: 0
}
PathArc {
relativeX: path.rounding
relativeY: -path.roundingY
radiusX: path.rounding
radiusY: Math.min(path.rounding, root.implicitHeight)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: -(root.implicitHeight - path.roundingY * 2)
}
PathArc {
relativeX: path.rounding
relativeY: -path.roundingY
radiusX: path.rounding
radiusY: Math.min(path.rounding, root.implicitHeight)
}
PathLine {
relativeX: root.implicitHeight > 0 ? root.implicitWidth - path.rounding * 2 : root.implicitWidth
relativeY: 0
}
PathArc {
relativeX: path.rounding
relativeY: -path.rounding
radiusX: path.rounding
radiusY: path.rounding
direction: PathArc.Counterclockwise
}
Behavior on fillColor {
CAnim {}
}
}
}
Item {
anchors.fill: parent
clip: true
StyledText {
id: content
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: Appearance.padding.larger - Appearance.padding.small
anchors.bottomMargin: Appearance.padding.normal - Appearance.padding.small
Connections {
target: root
function onCurrentItemChanged(): void {
if (root.currentItem)
content.text = qsTr(`"%1" selected`).arg(root.currentItem.modelData.name);
}
}
}
}
Behavior on implicitWidth {
enabled: !!root.currentItem
Anim {}
}
Behavior on implicitHeight {
Anim {}
}
}

View File

@@ -0,0 +1,93 @@
import ".."
import qs.services
import qs.config
import QtQuick.Layouts
StyledRect {
id: root
required property var dialog
required property FolderContents folder
implicitHeight: inner.implicitHeight + Appearance.padding.normal * 2
color: Colours.tPalette.m3surfaceContainer
RowLayout {
id: inner
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
StyledText {
text: qsTr("Filter:")
}
StyledRect {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.rightMargin: Appearance.spacing.normal
color: Colours.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.small
StyledText {
anchors.fill: parent
anchors.margins: Appearance.padding.normal
text: `${root.dialog.filterLabel} (${root.dialog.filters.map(f => `*.${f}`).join(", ")})`
}
}
StyledRect {
color: Colours.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.small
implicitWidth: cancelText.implicitWidth + Appearance.padding.normal * 2
implicitHeight: cancelText.implicitHeight + Appearance.padding.normal * 2
StateLayer {
disabled: !root.dialog.selectionValid
function onClicked(): void {
root.dialog.accepted(root.folder.currentItem.modelData.path);
}
}
StyledText {
id: selectText
anchors.centerIn: parent
anchors.margins: Appearance.padding.normal
text: qsTr("Select")
color: root.dialog.selectionValid ? Colours.palette.m3onSurface : Colours.palette.m3outline
}
}
StyledRect {
color: Colours.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.small
implicitWidth: cancelText.implicitWidth + Appearance.padding.normal * 2
implicitHeight: cancelText.implicitHeight + Appearance.padding.normal * 2
StateLayer {
function onClicked(): void {
root.dialog.rejected();
}
}
StyledText {
id: cancelText
anchors.centerIn: parent
anchors.margins: Appearance.padding.normal
text: qsTr("Cancel")
}
}
}
}

View File

@@ -0,0 +1,102 @@
pragma ComponentBehavior: Bound
import qs.components
import qs.services
import Quickshell
import QtQuick
import QtQuick.Layouts
LazyLoader {
id: loader
property list<string> cwd: ["Home"]
property string filterLabel: "All files"
property list<string> filters: ["*"]
property string title: qsTr("Select a file")
signal accepted(path: string)
signal rejected
function open(): void {
activeAsync = true;
}
function close(): void {
rejected();
}
onAccepted: activeAsync = false
onRejected: activeAsync = false
FloatingWindow {
id: root
property list<string> cwd: loader.cwd
property string filterLabel: loader.filterLabel
property list<string> filters: loader.filters
readonly property bool selectionValid: {
const file = folderContents.currentItem?.modelData;
return (file && !file.isDir && (filters.includes("*") || filters.includes(file.suffix))) ?? false;
}
function accepted(path: string): void {
loader.accepted(path);
}
function rejected(): void {
loader.rejected();
}
implicitWidth: 1000
implicitHeight: 600
color: Colours.tPalette.m3surface
title: loader.title
onVisibleChanged: {
if (!visible)
rejected();
}
RowLayout {
anchors.fill: parent
spacing: 0
Sidebar {
Layout.fillHeight: true
dialog: root
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
HeaderBar {
Layout.fillWidth: true
dialog: root
}
FolderContents {
id: folderContents
Layout.fillWidth: true
Layout.fillHeight: true
dialog: root
}
DialogButtons {
Layout.fillWidth: true
dialog: root
folder: folderContents
}
}
}
Behavior on color {
CAnim {}
}
}
}

View File

@@ -0,0 +1,228 @@
pragma ComponentBehavior: Bound
import ".."
import "../controls"
import "../images"
import qs.services
import qs.config
import qs.utils
import Caelestia.Models
import Quickshell
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
Item {
id: root
required property var dialog
property alias currentItem: view.currentItem
StyledRect {
anchors.fill: parent
color: Colours.tPalette.m3surfaceContainer
layer.enabled: true
layer.effect: MultiEffect {
maskSource: mask
maskEnabled: true
maskInverted: true
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
Item {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
anchors.fill: parent
anchors.margins: Appearance.padding.small
radius: Appearance.rounding.small
}
}
Loader {
anchors.centerIn: parent
opacity: view.count === 0 ? 1 : 0
active: opacity > 0
sourceComponent: ColumnLayout {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "scan_delete"
color: Colours.palette.m3outline
font.pointSize: Appearance.font.size.extraLarge * 2
font.weight: 500
}
StyledText {
text: qsTr("This folder is empty")
color: Colours.palette.m3outline
font.pointSize: Appearance.font.size.large
font.weight: 500
}
}
Behavior on opacity {
Anim {}
}
}
GridView {
id: view
anchors.fill: parent
anchors.margins: Appearance.padding.small + Appearance.padding.normal
cellWidth: Sizes.itemWidth + Appearance.spacing.small
cellHeight: Sizes.itemWidth + Appearance.spacing.small * 2 + Appearance.padding.normal * 2 + 1
clip: true
focus: true
currentIndex: -1
Keys.onEscapePressed: currentIndex = -1
Keys.onReturnPressed: {
if (root.dialog.selectionValid)
root.dialog.accepted(currentItem.modelData.path);
}
Keys.onEnterPressed: {
if (root.dialog.selectionValid)
root.dialog.accepted(currentItem.modelData.path);
}
StyledScrollBar.vertical: StyledScrollBar {
flickable: view
}
model: FileSystemModel {
path: {
if (root.dialog.cwd[0] === "Home")
return `${Paths.home}/${root.dialog.cwd.slice(1).join("/")}`;
else
return root.dialog.cwd.join("/");
}
onPathChanged: view.currentIndex = -1
}
delegate: StyledRect {
id: item
required property int index
required property FileSystemEntry modelData
readonly property real nonAnimHeight: icon.implicitHeight + name.anchors.topMargin + name.implicitHeight + Appearance.padding.normal * 2
implicitWidth: Sizes.itemWidth
implicitHeight: nonAnimHeight
radius: Appearance.rounding.normal
color: Qt.alpha(Colours.tPalette.m3surfaceContainerHighest, GridView.isCurrentItem ? Colours.tPalette.m3surfaceContainerHighest.a : 0)
z: GridView.isCurrentItem || implicitHeight !== nonAnimHeight ? 1 : 0
clip: true
StateLayer {
onDoubleClicked: {
if (item.modelData.isDir)
root.dialog.cwd.push(item.modelData.name);
else if (root.dialog.selectionValid)
root.dialog.accepted(item.modelData.path);
}
function onClicked(): void {
view.currentIndex = item.index;
}
}
CachingIconImage {
id: icon
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Appearance.padding.normal
implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
Component.onCompleted: {
const file = item.modelData;
if (file.isImage)
source = Qt.resolvedUrl(file.path);
else if (!file.isDir)
source = Quickshell.iconPath(file.mimeType.replace("/", "-"), "application-x-zerosize");
else if (root.dialog.cwd.length === 1 && ["Desktop", "Documents", "Downloads", "Music", "Pictures", "Public", "Templates", "Videos"].includes(file.name))
source = Quickshell.iconPath(`folder-${file.name.toLowerCase()}`);
else
source = Quickshell.iconPath("inode-directory");
}
}
StyledText {
id: name
anchors.left: parent.left
anchors.right: parent.right
anchors.top: icon.bottom
anchors.topMargin: Appearance.spacing.small
anchors.margins: Appearance.padding.normal
horizontalAlignment: Text.AlignHCenter
elide: item.GridView.isCurrentItem ? Text.ElideNone : Text.ElideRight
wrapMode: item.GridView.isCurrentItem ? Text.WrapAtWordBoundaryOrAnywhere : Text.NoWrap
Component.onCompleted: text = item.modelData.name
}
Behavior on implicitHeight {
Anim {}
}
}
add: Transition {
Anim {
properties: "opacity,scale"
from: 0
to: 1
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
Anim {
property: "scale"
to: 0.5
}
}
displaced: Transition {
Anim {
properties: "opacity,scale"
to: 1
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
Anim {
properties: "x,y"
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
CurrentItem {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Appearance.padding.small
currentItem: view.currentItem
}
}

View File

@@ -0,0 +1,139 @@
pragma ComponentBehavior: Bound
import ".."
import qs.services
import qs.config
import QtQuick
import QtQuick.Layouts
StyledRect {
id: root
required property var dialog
implicitWidth: inner.implicitWidth + Appearance.padding.normal * 2
implicitHeight: inner.implicitHeight + Appearance.padding.normal * 2
color: Colours.tPalette.m3surfaceContainer
RowLayout {
id: inner
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
Item {
implicitWidth: implicitHeight
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
StateLayer {
radius: Appearance.rounding.small
disabled: root.dialog.cwd.length === 1
function onClicked(): void {
root.dialog.cwd.pop();
}
}
MaterialIcon {
id: upIcon
anchors.centerIn: parent
text: "drive_folder_upload"
color: root.dialog.cwd.length === 1 ? Colours.palette.m3outline : Colours.palette.m3onSurface
grade: 200
}
}
StyledRect {
Layout.fillWidth: true
radius: Appearance.rounding.small
color: Colours.tPalette.m3surfaceContainerHigh
implicitHeight: pathComponents.implicitHeight + pathComponents.anchors.margins * 2
RowLayout {
id: pathComponents
anchors.fill: parent
anchors.margins: Appearance.padding.small / 2
anchors.leftMargin: 0
spacing: Appearance.spacing.small
Repeater {
model: root.dialog.cwd
RowLayout {
id: folder
required property string modelData
required property int index
spacing: 0
Loader {
Layout.rightMargin: Appearance.spacing.small
active: folder.index > 0
sourceComponent: StyledText {
text: "/"
color: Colours.palette.m3onSurfaceVariant
font.bold: true
}
}
Item {
implicitWidth: homeIcon.implicitWidth + (homeIcon.active ? Appearance.padding.small : 0) + folderName.implicitWidth + Appearance.padding.normal * 2
implicitHeight: folderName.implicitHeight + Appearance.padding.small * 2
Loader {
anchors.fill: parent
active: folder.index < root.dialog.cwd.length - 1
sourceComponent: StateLayer {
radius: Appearance.rounding.small
function onClicked(): void {
root.dialog.cwd = root.dialog.cwd.slice(0, folder.index + 1);
}
}
}
Loader {
id: homeIcon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Appearance.padding.normal
active: folder.index === 0 && folder.modelData === "Home"
sourceComponent: MaterialIcon {
text: "home"
color: root.dialog.cwd.length === 1 ? Colours.palette.m3onSurface : Colours.palette.m3onSurfaceVariant
fill: 1
}
}
StyledText {
id: folderName
anchors.left: homeIcon.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: homeIcon.active ? Appearance.padding.small : 0
text: folder.modelData
color: folder.index < root.dialog.cwd.length - 1 ? Colours.palette.m3onSurfaceVariant : Colours.palette.m3onSurface
font.bold: true
}
}
}
}
Item {
Layout.fillWidth: true
}
}
}
}
}

View File

@@ -0,0 +1,113 @@
pragma ComponentBehavior: Bound
import ".."
import qs.services
import qs.config
import QtQuick
import QtQuick.Layouts
StyledRect {
id: root
required property var dialog
implicitWidth: Sizes.sidebarWidth
implicitHeight: inner.implicitHeight + Appearance.padding.normal * 2
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: inner
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small / 2
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.padding.small / 2
Layout.bottomMargin: Appearance.spacing.normal
text: qsTr("Files")
color: Colours.palette.m3onSurface
font.pointSize: Appearance.font.size.larger
font.bold: true
}
Repeater {
model: ["Home", "Downloads", "Desktop", "Documents", "Music", "Pictures", "Videos"]
StyledRect {
id: place
required property string modelData
readonly property bool selected: modelData === root.dialog.cwd[root.dialog.cwd.length - 1]
Layout.fillWidth: true
implicitHeight: placeInner.implicitHeight + Appearance.padding.normal * 2
radius: Appearance.rounding.full
color: Qt.alpha(Colours.palette.m3secondaryContainer, selected ? 1 : 0)
StateLayer {
color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
function onClicked(): void {
if (place.modelData === "Home")
root.dialog.cwd = ["Home"];
else
root.dialog.cwd = ["Home", place.modelData];
}
}
RowLayout {
id: placeInner
anchors.fill: parent
anchors.margins: Appearance.padding.normal
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large
spacing: Appearance.spacing.normal
MaterialIcon {
text: {
const p = place.modelData;
if (p === "Home")
return "home";
if (p === "Downloads")
return "file_download";
if (p === "Desktop")
return "desktop_windows";
if (p === "Documents")
return "description";
if (p === "Music")
return "music_note";
if (p === "Pictures")
return "image";
if (p === "Videos")
return "video_library";
return "folder";
}
color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
font.pointSize: Appearance.font.size.large
fill: place.selected ? 1 : 0
Behavior on fill {
Anim {}
}
}
StyledText {
Layout.fillWidth: true
text: place.modelData
color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
font.pointSize: Appearance.font.size.normal
elide: Text.ElideRight
}
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
pragma Singleton
import Quickshell
Singleton {
property int itemWidth: 103
property int sidebarWidth: 200
}