summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-07-20 14:10:07 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-07-20 14:10:07 +1000
commitabf4bd8e6b361efdef8fae264d9a775e74ec8dc2 (patch)
tree013edc950181b11b5413c920022c8dd70c4cd77f
parentfiledialog: styling tweaks (diff)
downloadcaelestia-shell-abf4bd8e6b361efdef8fae264d9a775e74ec8dc2.tar.gz
caelestia-shell-abf4bd8e6b361efdef8fae264d9a775e74ec8dc2.tar.bz2
caelestia-shell-abf4bd8e6b361efdef8fae264d9a775e74ec8dc2.zip
filedialog: add nav rail
-rw-r--r--widgets/filedialog/FileDialog.qml9
-rw-r--r--widgets/filedialog/FolderContents.qml196
-rw-r--r--widgets/filedialog/Sidebar.qml120
-rw-r--r--widgets/filedialog/Sizes.qml1
4 files changed, 244 insertions, 82 deletions
diff --git a/widgets/filedialog/FileDialog.qml b/widgets/filedialog/FileDialog.qml
index a709122..53f1b57 100644
--- a/widgets/filedialog/FileDialog.qml
+++ b/widgets/filedialog/FileDialog.qml
@@ -18,13 +18,18 @@ FloatingWindow {
RowLayout {
anchors.fill: parent
- spacing: Appearance.spacing.normal
+ spacing: 0
+
+ Sidebar {
+ Layout.fillHeight: true
+ dialog: root
+ }
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
- spacing: Appearance.spacing.small
+ spacing: 0
HeaderBar {
Layout.fillWidth: true
diff --git a/widgets/filedialog/FolderContents.qml b/widgets/filedialog/FolderContents.qml
index 70ac11f..b5d2c5a 100644
--- a/widgets/filedialog/FolderContents.qml
+++ b/widgets/filedialog/FolderContents.qml
@@ -8,117 +8,153 @@ import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import QtQuick
+import QtQuick.Effects
import Qt.labs.folderlistmodel
-GridView {
+Item {
id: root
required property var dialog
- cellWidth: Sizes.itemWidth + Appearance.spacing.small
- cellHeight: Sizes.itemWidth + Appearance.spacing.small * 2 + Appearance.padding.normal * 2 + 1
+ StyledRect {
+ anchors.fill: parent
+ color: Colours.palette.m3surfaceContainer
- clip: true
- focus: true
- currentIndex: -1
- Keys.onEscapePressed: root.currentIndex = -1
-
- model: FolderListModel {
- showDirsFirst: true
- folder: {
- let url = "file://";
- if (root.dialog.cwd[0] === "Home")
- url += `${Paths.strip(Paths.home)}/${root.dialog.cwd.slice(1).join("/")}`;
- else
- url += root.dialog.cwd.join("/");
- return url;
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ maskSource: mask
+ maskEnabled: true
+ maskInverted: true
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1
}
- onFolderChanged: root.currentIndex = -1
}
- delegate: StyledRect {
- id: item
+ Item {
+ id: mask
- required property int index
- required property string fileName
- required property string filePath
- required property url fileUrl
- required property string fileSuffix
- required property bool fileIsDir
+ anchors.fill: parent
+ layer.enabled: true
+ visible: false
- readonly property real nonAnimHeight: icon.implicitHeight + name.anchors.topMargin + name.implicitHeight + Appearance.padding.normal * 2
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.small
+ radius: Appearance.rounding.small
+ }
+ }
- implicitWidth: Sizes.itemWidth
- implicitHeight: nonAnimHeight
+ GridView {
+ id: view
- radius: Appearance.rounding.normal
- color: root.currentItem === item ? Colours.palette.m3primary : "transparent"
- z: root.currentItem === item || implicitHeight !== nonAnimHeight ? 1 : 0
- clip: true
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.small + Appearance.padding.normal
- StateLayer {
- color: root.currentItem === item ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
+ cellWidth: Sizes.itemWidth + Appearance.spacing.small
+ cellHeight: Sizes.itemWidth + Appearance.spacing.small * 2 + Appearance.padding.normal * 2 + 1
- onDoubleClicked: {
- if (item.fileIsDir)
- root.dialog.cwd.push(item.fileName);
- else
- root.dialog.accepted(item.filePath);
- }
+ clip: true
+ focus: true
+ currentIndex: -1
+ Keys.onEscapePressed: view.currentIndex = -1
- function onClicked(): void {
- root.currentIndex = item.index;
+ model: FolderListModel {
+ showDirsFirst: true
+ folder: {
+ let url = "file://";
+ if (root.dialog.cwd[0] === "Home")
+ url += `${Paths.strip(Paths.home)}/${root.dialog.cwd.slice(1).join("/")}`;
+ else
+ url += root.dialog.cwd.join("/");
+ return url;
}
+ onFolderChanged: view.currentIndex = -1
}
- IconImage {
- id: icon
+ delegate: StyledRect {
+ id: item
+
+ required property int index
+ required property string fileName
+ required property string filePath
+ 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: view.currentItem === item ? Colours.palette.m3primary : "transparent"
+ z: view.currentItem === item || implicitHeight !== nonAnimHeight ? 1 : 0
+ clip: true
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.topMargin: Appearance.padding.normal
+ StateLayer {
+ color: view.currentItem === item ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
- asynchronous: true
- implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
- source: Quickshell.iconPath(item.fileIsDir ? "inode-directory" : "application-x-zerosize")
- onStatusChanged: {
- if (status === Image.Error)
- source = Quickshell.iconPath("error");
+ onDoubleClicked: {
+ if (item.fileIsDir)
+ root.dialog.cwd.push(item.fileName);
+ else
+ root.dialog.accepted(item.filePath);
+ }
+
+ function onClicked(): void {
+ view.currentIndex = item.index;
+ }
}
- Process {
- running: !item.fileIsDir
- command: ["file", "--mime", "-b", item.filePath]
- stdout: StdioCollector {
- onStreamFinished: {
- const mime = text.split(";")[0].replace("/", "-");
- icon.source = mime.startsWith("image-") ? item.fileUrl : Quickshell.iconPath(mime, "image-missing");
+ IconImage {
+ id: icon
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.topMargin: Appearance.padding.normal
+
+ asynchronous: true
+ implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
+ source: Quickshell.iconPath(item.fileIsDir ? "inode-directory" : "application-x-zerosize")
+ onStatusChanged: {
+ if (status === Image.Error)
+ source = Quickshell.iconPath("error");
+ }
+
+ Process {
+ running: !item.fileIsDir
+ command: ["file", "--mime", "-b", item.filePath]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const mime = text.split(";")[0].replace("/", "-");
+ icon.source = mime.startsWith("image-") ? item.fileUrl : Quickshell.iconPath(mime, "image-missing");
+ }
}
}
}
- }
- StyledText {
- id: name
+ 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
+ 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
- }
+ horizontalAlignment: Text.AlignHCenter
+ text: item.fileName
+ color: view.currentItem === item ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
+ elide: view.currentItem === item ? Text.ElideNone : Text.ElideRight
+ wrapMode: view.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
+ Behavior on implicitHeight {
+ NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
}
}
}
diff --git a/widgets/filedialog/Sidebar.qml b/widgets/filedialog/Sidebar.qml
new file mode 100644
index 0000000..f0bcc58
--- /dev/null
+++ b/widgets/filedialog/Sidebar.qml
@@ -0,0 +1,120 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import qs.services
+import qs.config
+import Quickshell
+import Quickshell.Widgets
+import QtQuick
+import QtQuick.Layouts
+
+StyledRect {
+ id: root
+
+ required property var dialog
+
+ implicitWidth: Sizes.sidebarWidth
+ implicitHeight: inner.implicitHeight + Appearance.padding.normal * 2
+
+ color: Colours.palette.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]
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ implicitHeight: placeInner.implicitHeight + Appearance.padding.normal * 2
+
+ radius: Appearance.rounding.full
+ color: selected ? Colours.palette.m3secondaryContainer : "transparent"
+
+ 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 {
+ NumberAnimation {
+ duration: Appearance.anim.durations.normal
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: Appearance.anim.curves.standard
+ }
+ }
+ }
+
+ 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
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/widgets/filedialog/Sizes.qml b/widgets/filedialog/Sizes.qml
index d332ab3..c8cacba 100644
--- a/widgets/filedialog/Sizes.qml
+++ b/widgets/filedialog/Sizes.qml
@@ -4,4 +4,5 @@ import Quickshell
Singleton {
property int itemWidth: 100
+ property int sidebarWidth: 200
}