diff options
Diffstat (limited to '')
| -rw-r--r-- | components/controls/SplitButtonRow.qml | 62 | ||||
| -rw-r--r-- | components/controls/ToggleButton.qml | 2 | ||||
| -rw-r--r-- | components/filedialog/FolderContents.qml | 228 | ||||
| -rw-r--r-- | components/filedialog/HeaderBar.qml | 139 |
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 + } + } + } + } +} |