pragma ComponentBehavior: Bound import qs.components import qs.components.controls import qs.components.containers import qs.services import qs.config import qs.utils import Caelestia import Caelestia.Models import Quickshell import Quickshell.Widgets import QtQuick import QtQuick.Layouts ColumnLayout { id: root required property var props required property var visibilities spacing: 0 WrapperMouseArea { Layout.fillWidth: true cursorShape: Qt.PointingHandCursor onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded RowLayout { spacing: Appearance.spacing.smaller MaterialIcon { Layout.alignment: Qt.AlignVCenter text: "list" font.pointSize: Appearance.font.size.large } StyledText { Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true text: qsTr("Recordings") font.pointSize: Appearance.font.size.normal } IconButton { icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more" type: IconButton.Text label.animate: true onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded } } } StyledListView { id: list model: FileSystemModel { path: Paths.recsdir nameFilters: ["recording_*.mp4"] sortReverse: true } Layout.fillWidth: true Layout.rightMargin: -Appearance.spacing.small implicitHeight: (Appearance.font.size.larger + Appearance.padding.small) * (root.props.recordingListExpanded ? 10 : 3) clip: true StyledScrollBar.vertical: StyledScrollBar {} delegate: RowLayout { id: recording required property FileSystemEntry modelData property string baseName anchors.left: list.contentItem.left anchors.right: list.contentItem.right anchors.rightMargin: Appearance.spacing.small spacing: Appearance.spacing.small / 2 Component.onCompleted: baseName = modelData.baseName StyledText { Layout.fillWidth: true Layout.rightMargin: Appearance.spacing.small / 2 text: { const time = recording.baseName; const matches = time.match(/^recording_(\d{4})(\d{2})(\d{2})_(\d{2})-(\d{2})-(\d{2})/); if (!matches) return time; const date = new Date(...matches.slice(1)); return qsTr("Recording at %1").arg(Qt.formatDateTime(date, Qt.locale())); } color: Colours.palette.m3onSurfaceVariant elide: Text.ElideRight } IconButton { icon: "play_arrow" type: IconButton.Text onClicked: { root.visibilities.utilities = false; Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]); } } IconButton { icon: "folder" type: IconButton.Text onClicked: { root.visibilities.utilities = false; Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]); } } IconButton { icon: "delete_forever" type: IconButton.Text label.color: Colours.palette.m3error stateLayer.color: Colours.palette.m3error onClicked: root.props.recordingConfirmDelete = recording.modelData.path } } add: Transition { Anim { property: "opacity" from: 0 to: 1 } Anim { property: "scale" from: 0.5 to: 1 } } remove: Transition { Anim { property: "opacity" to: 0 } Anim { property: "scale" to: 0.5 } } displaced: Transition { Anim { properties: "opacity,scale" to: 1 } Anim { property: "y" } } Loader { anchors.centerIn: parent opacity: list.count === 0 ? 1 : 0 active: opacity > 0 asynchronous: true sourceComponent: ColumnLayout { spacing: Appearance.spacing.small MaterialIcon { Layout.alignment: Qt.AlignHCenter text: "scan_delete" color: Colours.palette.m3outline font.pointSize: Appearance.font.size.extraLarge opacity: root.props.recordingListExpanded ? 1 : 0 scale: root.props.recordingListExpanded ? 1 : 0 Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0 Behavior on opacity { Anim {} } Behavior on scale { Anim {} } Behavior on Layout.preferredHeight { Anim {} } } RowLayout { spacing: Appearance.spacing.smaller MaterialIcon { Layout.alignment: Qt.AlignHCenter text: "scan_delete" color: Colours.palette.m3outline opacity: !root.props.recordingListExpanded ? 1 : 0 scale: !root.props.recordingListExpanded ? 1 : 0 Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0 Behavior on opacity { Anim {} } Behavior on scale { Anim {} } Behavior on Layout.preferredWidth { Anim {} } } StyledText { text: qsTr("No recordings found") color: Colours.palette.m3outline } } } Behavior on opacity { Anim {} } } Behavior on implicitHeight { Anim { duration: Appearance.anim.durations.expressiveDefaultSpatial easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial } } } }