summaryrefslogtreecommitdiff
path: root/modules/utilities/cards/Record.qml
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-09-15 21:55:10 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-09-15 21:55:10 +1000
commit7a9fce9dd417db42d75dc80a86df45ed0402d6d4 (patch)
tree1a3671200433275f017073e70e895971bfd94951 /modules/utilities/cards/Record.qml
parentutilities/record: select mode (diff)
downloadcaelestia-shell-7a9fce9dd417db42d75dc80a86df45ed0402d6d4.tar.gz
caelestia-shell-7a9fce9dd417db42d75dc80a86df45ed0402d6d4.tar.bz2
caelestia-shell-7a9fce9dd417db42d75dc80a86df45ed0402d6d4.zip
utilities/record: allow pause/resume recording
Diffstat (limited to 'modules/utilities/cards/Record.qml')
-rw-r--r--modules/utilities/cards/Record.qml317
1 files changed, 121 insertions, 196 deletions
diff --git a/modules/utilities/cards/Record.qml b/modules/utilities/cards/Record.qml
index fa45cf7..963b028 100644
--- a/modules/utilities/cards/Record.qml
+++ b/modules/utilities/cards/Record.qml
@@ -2,14 +2,8 @@ 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
@@ -18,7 +12,6 @@ StyledRect {
required property var props
required property var visibilities
- property bool modePickerExpanded
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
@@ -80,6 +73,7 @@ StyledRect {
}
SplitButton {
+ disabled: Recorder.running
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
@@ -88,250 +82,181 @@ StyledRect {
icon: "fullscreen"
text: qsTr("Record fullscreen")
activeText: qsTr("Fullscreen")
- onClicked: Recorder.toggle()
+ onClicked: Recorder.start()
},
MenuItem {
icon: "screenshot_region"
text: qsTr("Record region")
activeText: qsTr("Region")
- onClicked: Recorder.toggle(["-r"])
+ onClicked: Recorder.start(["-r"])
},
MenuItem {
icon: "select_to_speak"
text: qsTr("Record fullscreen with sound")
activeText: qsTr("Fullscreen")
- onClicked: Recorder.toggle(["-s"])
+ onClicked: Recorder.start(["-s"])
},
MenuItem {
icon: "volume_up"
text: qsTr("Record region with sound")
activeText: qsTr("Region")
- onClicked: Recorder.toggle(["-sr"])
+ onClicked: Recorder.start(["-sr"])
}
]
}
}
- WrapperMouseArea {
- Layout.bottomMargin: -layout.spacing
- Layout.fillWidth: true
-
- cursorShape: Qt.PointingHandCursor
- onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
-
- RowLayout {
- spacing: Appearance.spacing.smaller
+ Loader {
+ id: listOrControls
- MaterialIcon {
- Layout.alignment: Qt.AlignVCenter
- text: "list"
- font.pointSize: Appearance.font.size.large
- }
+ property bool running: Recorder.running
- StyledText {
- Layout.alignment: Qt.AlignVCenter
- Layout.fillWidth: true
- text: qsTr("Recordings")
- font.pointSize: Appearance.font.size.normal
- }
+ Layout.fillWidth: true
+ Layout.preferredHeight: implicitHeight
+ sourceComponent: running ? recordingControls : recordingList
- IconButton {
- icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more"
- type: IconButton.Text
- label.animate: true
+ Behavior on Layout.preferredHeight {
+ Anim {}
+ }
- function onClicked(): void {
- root.props.recordingListExpanded = !root.props.recordingListExpanded;
+ Behavior on running {
+ SequentialAnimation {
+ ParallelAnimation {
+ Anim {
+ target: listOrControls
+ property: "scale"
+ to: 0.7
+ duration: Appearance.anim.durations.small
+ easing.bezierCurve: Appearance.anim.curves.standardAccel
+ }
+ Anim {
+ target: listOrControls
+ property: "opacity"
+ to: 0
+ duration: Appearance.anim.durations.small
+ easing.bezierCurve: Appearance.anim.curves.standardAccel
+ }
+ }
+ PropertyAction {}
+ ParallelAnimation {
+ Anim {
+ target: listOrControls
+ property: "scale"
+ to: 1
+ duration: Appearance.anim.durations.small
+ easing.bezierCurve: Appearance.anim.curves.standardDecel
+ }
+ Anim {
+ target: listOrControls
+ property: "opacity"
+ to: 1
+ duration: Appearance.anim.durations.small
+ easing.bezierCurve: Appearance.anim.curves.standardDecel
+ }
}
}
}
}
+ }
- 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
+ Component {
+ id: recordingList
- StyledScrollBar.vertical: StyledScrollBar {}
+ RecordingList {
+ props: root.props
+ visibilities: root.visibilities
+ }
+ }
- delegate: RowLayout {
- id: recording
+ Component {
+ id: recordingControls
- required property FileSystemEntry modelData
- property string baseName
+ RowLayout {
+ spacing: Appearance.spacing.normal
- anchors.left: recordingList.contentItem.left
- anchors.right: recordingList.contentItem.right
- anchors.rightMargin: Appearance.spacing.small
- spacing: Appearance.spacing.small / 2
+ StyledRect {
+ radius: Appearance.rounding.full
+ color: Recorder.paused ? Colours.palette.m3tertiary : Colours.palette.m3error
- Component.onCompleted: baseName = modelData.baseName
+ implicitWidth: recText.implicitWidth + Appearance.padding.normal * 2
+ implicitHeight: recText.implicitHeight + Appearance.padding.smaller * 2
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
- }
+ id: recText
- IconButton {
- icon: "play_arrow"
- type: IconButton.Text
-
- function onClicked(): void {
- root.visibilities.utilities = false;
- Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
- }
+ anchors.centerIn: parent
+ animate: true
+ text: Recorder.paused ? "PAUSED" : "REC"
+ color: Recorder.paused ? Colours.palette.m3onTertiary : Colours.palette.m3onError
+ font.family: Appearance.font.family.mono
}
- IconButton {
- icon: "folder"
- type: IconButton.Text
-
- function onClicked(): void {
- root.visibilities.utilities = false;
- Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
- }
+ Behavior on implicitWidth {
+ Anim {}
}
- IconButton {
- icon: "delete_forever"
- type: IconButton.Text
- label.color: Colours.palette.m3error
- stateLayer.color: Colours.palette.m3error
+ SequentialAnimation on opacity {
+ running: !Recorder.paused
+ alwaysRunToEnd: true
+ loops: Animation.Infinite
- function onClicked(): void {
- root.props.recordingConfirmDelete = recording.modelData.path;
+ Anim {
+ from: 1
+ to: 0
+ duration: Appearance.anim.durations.large
+ easing.bezierCurve: Appearance.anim.curves.emphasizedAccel
+ }
+ Anim {
+ from: 0
+ to: 1
+ duration: Appearance.anim.durations.extraLarge
+ easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
}
}
- add: Transition {
- Anim {
- property: "opacity"
- from: 0
- to: 1
- }
- Anim {
- property: "scale"
- from: 0.5
- to: 1
- }
- }
+ StyledText {
+ text: {
+ const elapsed = Recorder.elapsed;
- remove: Transition {
- Anim {
- property: "opacity"
- to: 0
- }
- Anim {
- property: "scale"
- to: 0.5
- }
- }
+ const hours = Math.floor(elapsed / 3600);
+ const mins = Math.floor((elapsed % 3600) / 60);
+ const secs = Math.floor(elapsed % 60).toString().padStart(2, "0");
- displaced: Transition {
- Anim {
- properties: "opacity,scale"
- to: 1
- }
- Anim {
- property: "y"
+ let time;
+ if (hours > 0)
+ time = `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
+ else
+ time = `${mins}:${secs}`;
+
+ return qsTr("Recording for %1").arg(time);
}
+ font.pointSize: Appearance.font.size.normal
}
- 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
- }
- }
- }
+ Item {
+ Layout.fillWidth: true
+ }
- Behavior on opacity {
- Anim {}
+ IconButton {
+ label.animate: true
+ icon: Recorder.paused ? "play_arrow" : "pause"
+ toggle: true
+ checked: Recorder.paused
+ type: IconButton.Tonal
+ font.pointSize: Appearance.font.size.large
+ onClicked: {
+ Recorder.togglePause();
+ internalChecked = Recorder.paused;
}
}
- Behavior on implicitHeight {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
+ IconButton {
+ icon: "stop"
+ inactiveColour: Colours.palette.m3error
+ inactiveOnColour: Colours.palette.m3onError
+ font.pointSize: Appearance.font.size.large
+ onClicked: Recorder.stop()
}
}
}