summaryrefslogtreecommitdiff
path: root/widgets/filedialog
diff options
context:
space:
mode:
Diffstat (limited to 'widgets/filedialog')
-rw-r--r--widgets/filedialog/FileDialog.qml40
-rw-r--r--widgets/filedialog/FolderContents.qml122
-rw-r--r--widgets/filedialog/HeaderBar.qml128
-rw-r--r--widgets/filedialog/Sizes.qml7
4 files changed, 297 insertions, 0 deletions
diff --git a/widgets/filedialog/FileDialog.qml b/widgets/filedialog/FileDialog.qml
new file mode 100644
index 0000000..915c09c
--- /dev/null
+++ b/widgets/filedialog/FileDialog.qml
@@ -0,0 +1,40 @@
+import qs.services
+import qs.config
+import Quickshell
+import QtQuick.Layouts
+
+FloatingWindow {
+ id: root
+
+ property list<string> cwd: ["Home", "Downloads"]
+
+ implicitWidth: 1000
+ implicitHeight: 600
+ color: Colours.palette.m3surface
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.normal
+
+ spacing: Appearance.spacing.normal
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ spacing: Appearance.spacing.small
+
+ HeaderBar {
+ Layout.fillWidth: true
+ cwd: root.cwd
+ }
+
+ FolderContents {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ cwd: root.cwd
+ }
+ }
+ }
+}
diff --git a/widgets/filedialog/FolderContents.qml b/widgets/filedialog/FolderContents.qml
new file mode 100644
index 0000000..c59d87f
--- /dev/null
+++ b/widgets/filedialog/FolderContents.qml
@@ -0,0 +1,122 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import qs.services
+import qs.config
+import qs.utils
+import Quickshell
+import Quickshell.Io
+import Quickshell.Widgets
+import QtQuick
+import Qt.labs.folderlistmodel
+
+GridView {
+ id: root
+
+ required property list<string> cwd
+
+ property var mimes: ({})
+
+ clip: true
+ focus: true
+ Keys.onEscapePressed: root.currentIndex = -1
+
+ model: FolderListModel {
+ folder: {
+ let url = "file://";
+ if (root.cwd[0] === "Home")
+ url += `${Paths.strip(Paths.home)}/${root.cwd.slice(1).join("/")}`;
+ else
+ url += root.cwd.join("/");
+ return url;
+ }
+ }
+
+ delegate: StyledRect {
+ id: item
+
+ required property int index
+ required property string fileName
+ required property url fileUrl
+ required property string fileSuffix
+ required property bool fileIsDir
+
+ 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: root.currentItem === item ? Colours.palette.m3primary : "transparent"
+ z: root.currentItem === item || implicitHeight !== nonAnimHeight ? 1 : 0
+ clip: true
+
+ StateLayer {
+ color: root.currentItem === item ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
+ onDoubleClicked: console.log("double clicked", item)
+
+ function onClicked(): void {
+ root.currentIndex = item.index;
+ }
+ }
+
+ IconImage {
+ id: icon
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.topMargin: Appearance.spacing.normal
+
+ asynchronous: true
+ implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
+ source: {
+ const mime = root.mimes[item.fileSuffix];
+
+ if (mime?.startsWith("image-"))
+ return item.fileUrl;
+
+ return Quickshell.iconPath(item.fileIsDir ? "inode-directory" : root.mimes[item.fileSuffix] ?? "application-x-zerosize", "image-missing");
+ }
+ onStatusChanged: {
+ if (status === Image.Error)
+ source = Quickshell.iconPath("error");
+ }
+ }
+
+ 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
+ text: item.fileName
+ color: root.currentItem === item ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
+ elide: root.currentItem === item ? Text.ElideNone : Text.ElideRight
+ wrapMode: root.currentItem === item ? Text.WrapAtWordBoundaryOrAnywhere : Text.NoWrap
+ }
+
+ Behavior on implicitHeight {
+ NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+ }
+
+ FileView {
+ path: "/etc/mime.types"
+ onLoaded: {
+ root.mimes = text().split("\n").filter(l => !l.startsWith("#")).reduce((mimes, line) => {
+ const [type, ext] = line.split(/\s+/);
+ if (ext)
+ mimes[ext] = type.replace("/", "-");
+ return mimes;
+ }, {});
+ }
+ }
+}
diff --git a/widgets/filedialog/HeaderBar.qml b/widgets/filedialog/HeaderBar.qml
new file mode 100644
index 0000000..40177a3
--- /dev/null
+++ b/widgets/filedialog/HeaderBar.qml
@@ -0,0 +1,128 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import qs.services
+import qs.config
+import QtQuick
+import QtQuick.Layouts
+
+RowLayout {
+ id: root
+
+ required property list<string> cwd
+
+ spacing: Appearance.spacing.small
+
+ Item {
+ implicitWidth: implicitHeight
+ implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
+
+ StateLayer {
+ radius: Appearance.rounding.small
+
+ function onClicked(): void {
+ root.cwd.pop();
+ }
+ }
+
+ MaterialIcon {
+ id: upIcon
+
+ anchors.centerIn: parent
+ text: "drive_folder_upload"
+ }
+ }
+
+ StyledRect {
+ Layout.fillWidth: true
+
+ radius: Appearance.rounding.small
+ color: Colours.palette.m3surfaceContainer
+
+ implicitHeight: pathComponents.implicitHeight + pathComponents.anchors.margins * 2
+
+ RowLayout {
+ id: pathComponents
+
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.small
+ anchors.leftMargin: 0
+
+ spacing: Appearance.spacing.small
+
+ Repeater {
+ model: root.cwd
+
+ RowLayout {
+ id: folder
+
+ required property string modelData
+ required property int index
+
+ spacing: 0
+
+ Loader {
+ Layout.rightMargin: Appearance.spacing.small
+ active: folder.index > 0
+ asynchronous: true
+ 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: Math.max(homeIcon.implicitHeight, folderName.implicitHeight) + Appearance.padding.small * 2
+
+ Loader {
+ anchors.fill: parent
+ active: folder.index < root.cwd.length - 1
+ asynchronous: true
+ sourceComponent: StateLayer {
+ radius: Appearance.rounding.small
+
+ function onClicked(): void {
+ root.cwd = root.cwd.slice(0, folder.index);
+ }
+ }
+ }
+
+ Loader {
+ id: homeIcon
+
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: Appearance.padding.normal
+
+ active: folder.index === 0 && folder.modelData === "Home"
+ asynchronous: true
+ sourceComponent: MaterialIcon {
+ text: "home"
+ color: root.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.cwd.length - 1 ? Colours.palette.m3onSurfaceVariant : Colours.palette.m3onSurface
+ font.bold: true
+ }
+ }
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+ }
+}
diff --git a/widgets/filedialog/Sizes.qml b/widgets/filedialog/Sizes.qml
new file mode 100644
index 0000000..d332ab3
--- /dev/null
+++ b/widgets/filedialog/Sizes.qml
@@ -0,0 +1,7 @@
+pragma Singleton
+
+import Quickshell
+
+Singleton {
+ property int itemWidth: 100
+}