summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--components/controls/SplitButtonRow.qml62
-rw-r--r--components/controls/ToggleButton.qml2
-rw-r--r--components/filedialog/FolderContents.qml228
-rw-r--r--components/filedialog/HeaderBar.qml139
4 files changed, 429 insertions, 2 deletions
diff --git a/components/controls/SplitButtonRow.qml b/components/controls/SplitButtonRow.qml
new file mode 100644
index 0000000..4ecc8bf
--- /dev/null
+++ b/components/controls/SplitButtonRow.qml
@@ -0,0 +1,62 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import qs.components
+import qs.components.effects
+import qs.services
+import qs.config
+import QtQuick
+import QtQuick.Layouts
+
+StyledRect {
+ id: root
+
+ required property string label
+ property int expandedZ: 100
+ property bool enabled: true
+
+ property alias menuItems: splitButton.menuItems
+ property alias active: splitButton.active
+ property alias expanded: splitButton.expanded
+ property alias type: splitButton.type
+
+ signal selected(item: MenuItem)
+
+ Layout.fillWidth: true
+ implicitHeight: row.implicitHeight + Appearance.padding.large * 2
+ radius: Appearance.rounding.normal
+ color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
+
+ clip: false
+ z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1
+ opacity: enabled ? 1.0 : 0.5
+
+ RowLayout {
+ id: row
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.large
+ spacing: Appearance.spacing.normal
+
+ StyledText {
+ Layout.fillWidth: true
+ text: root.label
+ color: root.enabled ? Colours.palette.m3onSurface : Colours.palette.m3onSurfaceVariant
+ }
+
+ SplitButton {
+ id: splitButton
+ enabled: root.enabled
+ type: SplitButton.Filled
+
+ menu.z: 1
+
+ stateLayer.onClicked: {
+ splitButton.expanded = !splitButton.expanded
+ }
+
+ menu.onItemSelected: (item) => {
+ root.selected(item);
+ }
+ }
+ }
+}
diff --git a/components/controls/ToggleButton.qml b/components/controls/ToggleButton.qml
index b2c2afe..8bdcb04 100644
--- a/components/controls/ToggleButton.qml
+++ b/components/controls/ToggleButton.qml
@@ -74,7 +74,6 @@ StyledRect {
}
Loader {
- asynchronous: true
active: !!root.label
visible: active
@@ -103,7 +102,6 @@ StyledRect {
Loader {
id: tooltipLoader
active: root.tooltip !== ""
- asynchronous: true
z: 10000
width: 0
height: 0
diff --git a/components/filedialog/FolderContents.qml b/components/filedialog/FolderContents.qml
new file mode 100644
index 0000000..e16c7a1
--- /dev/null
+++ b/components/filedialog/FolderContents.qml
@@ -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
+ }
+}
diff --git a/components/filedialog/HeaderBar.qml b/components/filedialog/HeaderBar.qml
new file mode 100644
index 0000000..c9a3feb
--- /dev/null
+++ b/components/filedialog/HeaderBar.qml
@@ -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
+ }
+ }
+ }
+ }
+}