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 StyledRect { id: root required property var props required property var visibilities Layout.fillWidth: true implicitHeight: layout.implicitHeight + layout.anchors.margins * 2 radius: Appearance.rounding.normal color: Colours.palette.m3surfaceContainer ColumnLayout { id: layout anchors.fill: parent anchors.margins: Appearance.padding.large spacing: Appearance.spacing.normal RowLayout { spacing: Appearance.spacing.normal StyledRect { implicitWidth: implicitHeight implicitHeight: { const h = icon.implicitHeight + Appearance.padding.smaller * 2; return h - (h % 2); } radius: Appearance.rounding.full color: Recorder.running ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer MaterialIcon { id: icon anchors.centerIn: parent anchors.horizontalCenterOffset: -0.5 anchors.verticalCenterOffset: 1.5 text: "screen_record" color: Recorder.running ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer font.pointSize: Appearance.font.size.large } } ColumnLayout { Layout.fillWidth: true spacing: 0 StyledText { Layout.fillWidth: true text: qsTr("Screen Recorder") font.pointSize: Appearance.font.size.normal elide: Text.ElideRight } StyledText { Layout.fillWidth: true text: Recorder.paused ? qsTr("Recording paused") : Recorder.running ? qsTr("Recording running") : qsTr("Recording off") color: Colours.palette.m3onSurfaceVariant font.pointSize: Appearance.font.size.small elide: Text.ElideRight } } IconButton { icon: "not_started" checked: Recorder.running type: IconButton.Filled inactiveColour: Colours.palette.m3surfaceContainerHighest toggle: true function onClicked(): void { Recorder.toggle(); } } } WrapperMouseArea { Layout.bottomMargin: -layout.spacing 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 function onClicked(): void { root.props.recordingListExpanded = !root.props.recordingListExpanded; } } } } StyledListView { id: recordingList 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: recordingList.contentItem.left anchors.right: recordingList.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 function onClicked(): void { root.visibilities.utilities = false; Quickshell.execDetached([...Config.general.apps.playback, recording.modelData.path]); } } IconButton { icon: "folder" type: IconButton.Text function onClicked(): void { root.visibilities.utilities = false; Quickshell.execDetached([...Config.general.apps.explorer, recording.modelData.path]); } } IconButton { icon: "delete_forever" type: IconButton.Text label.color: Colours.palette.m3error stateLayer.color: Colours.palette.m3error function onClicked(): void { 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: recordingList.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 } } } } }