utilities/record: allow pause/resume recording
This commit is contained in:
parent
4f54763e02
commit
7a9fce9dd4
8 changed files with 475 additions and 263 deletions
|
|
@ -15,7 +15,7 @@ StyledRect {
|
||||||
property alias icon: label.text
|
property alias icon: label.text
|
||||||
property bool checked
|
property bool checked
|
||||||
property bool toggle
|
property bool toggle
|
||||||
property real padding: type == IconButton.Text ? Appearance.padding.small / 2 : Appearance.padding.smaller
|
property real padding: type === IconButton.Text ? Appearance.padding.small / 2 : Appearance.padding.smaller
|
||||||
property alias font: label.font
|
property alias font: label.font
|
||||||
property int type: IconButton.Filled
|
property int type: IconButton.Filled
|
||||||
|
|
||||||
|
|
@ -23,18 +23,25 @@ StyledRect {
|
||||||
property alias label: label
|
property alias label: label
|
||||||
|
|
||||||
property bool internalChecked
|
property bool internalChecked
|
||||||
property color activeColour: type == IconButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
property color activeColour: type === IconButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
||||||
property color inactiveColour: type == IconButton.Filled ? Colours.palette.m3surfaceContainer : Colours.palette.m3secondaryContainer
|
property color inactiveColour: {
|
||||||
property color activeOnColour: type == IconButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
if (!toggle && type === IconButton.Filled)
|
||||||
property color inactiveOnColour: type == IconButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
return Colours.palette.m3primary;
|
||||||
|
return type === IconButton.Filled ? Colours.palette.m3surfaceContainer : Colours.palette.m3secondaryContainer;
|
||||||
function onClicked(): void {
|
|
||||||
}
|
}
|
||||||
|
property color activeOnColour: type === IconButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
||||||
|
property color inactiveOnColour: {
|
||||||
|
if (!toggle && type === IconButton.Filled)
|
||||||
|
return Colours.palette.m3onPrimary;
|
||||||
|
return type === IconButton.Filled ? Colours.palette.m3onSurfaceVariant : Colours.palette.m3onSecondaryContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal clicked
|
||||||
|
|
||||||
onCheckedChanged: internalChecked = checked
|
onCheckedChanged: internalChecked = checked
|
||||||
|
|
||||||
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
|
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
|
||||||
color: type == IconButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
|
color: type === IconButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
|
||||||
|
|
||||||
implicitWidth: implicitHeight
|
implicitWidth: implicitHeight
|
||||||
implicitHeight: label.implicitHeight + padding * 2
|
implicitHeight: label.implicitHeight + padding * 2
|
||||||
|
|
@ -47,7 +54,7 @@ StyledRect {
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
if (root.toggle)
|
if (root.toggle)
|
||||||
root.internalChecked = !root.internalChecked;
|
root.internalChecked = !root.internalChecked;
|
||||||
root.onClicked();
|
root.clicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +63,7 @@ StyledRect {
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
|
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
|
||||||
fill: root.internalChecked ? 1 : 0
|
fill: !root.toggle || root.internalChecked ? 1 : 0
|
||||||
|
|
||||||
Behavior on fill {
|
Behavior on fill {
|
||||||
Anim {}
|
Anim {}
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,17 @@ StyledRect {
|
||||||
property alias label: label
|
property alias label: label
|
||||||
|
|
||||||
property bool internalChecked
|
property bool internalChecked
|
||||||
property color activeColour: type == IconTextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
property color activeColour: type === IconTextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
||||||
property color inactiveColour: type == IconTextButton.Filled ? Colours.palette.m3surfaceContainer : Colours.palette.m3secondaryContainer
|
property color inactiveColour: type === IconTextButton.Filled ? Colours.palette.m3surfaceContainer : Colours.palette.m3secondaryContainer
|
||||||
property color activeOnColour: type == IconTextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
property color activeOnColour: type === IconTextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
||||||
property color inactiveOnColour: type == IconTextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
property color inactiveOnColour: type === IconTextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
||||||
|
|
||||||
function onClicked(): void {
|
signal clicked
|
||||||
}
|
|
||||||
|
|
||||||
onCheckedChanged: internalChecked = checked
|
onCheckedChanged: internalChecked = checked
|
||||||
|
|
||||||
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
|
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
|
||||||
color: type == IconTextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
|
color: type === IconTextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
|
||||||
|
|
||||||
implicitWidth: row.implicitWidth + horizontalPadding * 2
|
implicitWidth: row.implicitWidth + horizontalPadding * 2
|
||||||
implicitHeight: row.implicitHeight + verticalPadding * 2
|
implicitHeight: row.implicitHeight + verticalPadding * 2
|
||||||
|
|
@ -50,7 +49,7 @@ StyledRect {
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
if (root.toggle)
|
if (root.toggle)
|
||||||
root.internalChecked = !root.internalChecked;
|
root.internalChecked = !root.internalChecked;
|
||||||
root.onClicked();
|
root.clicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ Row {
|
||||||
property real horizontalPadding: Appearance.padding.normal
|
property real horizontalPadding: Appearance.padding.normal
|
||||||
property real verticalPadding: Appearance.padding.smaller
|
property real verticalPadding: Appearance.padding.smaller
|
||||||
property int type: SplitButton.Filled
|
property int type: SplitButton.Filled
|
||||||
|
property bool disabled
|
||||||
property alias menuItems: menu.items
|
property alias menuItems: menu.items
|
||||||
property alias active: menu.active
|
property alias active: menu.active
|
||||||
property alias expanded: menu.expanded
|
property alias expanded: menu.expanded
|
||||||
|
|
@ -22,6 +23,8 @@ Row {
|
||||||
|
|
||||||
property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
||||||
property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
||||||
|
readonly property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
|
||||||
|
readonly property color disabledTextColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
|
||||||
|
|
||||||
spacing: Math.floor(Appearance.spacing.small / 2)
|
spacing: Math.floor(Appearance.spacing.small / 2)
|
||||||
|
|
||||||
|
|
@ -29,7 +32,7 @@ Row {
|
||||||
radius: implicitHeight / 2
|
radius: implicitHeight / 2
|
||||||
topRightRadius: Appearance.rounding.small / 2
|
topRightRadius: Appearance.rounding.small / 2
|
||||||
bottomRightRadius: Appearance.rounding.small / 2
|
bottomRightRadius: Appearance.rounding.small / 2
|
||||||
color: root.colour
|
color: root.disabled ? root.disabledColour : root.colour
|
||||||
|
|
||||||
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
|
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
|
||||||
implicitHeight: expandBtn.implicitHeight
|
implicitHeight: expandBtn.implicitHeight
|
||||||
|
|
@ -40,6 +43,7 @@ Row {
|
||||||
rect.topRightRadius: parent.topRightRadius
|
rect.topRightRadius: parent.topRightRadius
|
||||||
rect.bottomRightRadius: parent.bottomRightRadius
|
rect.bottomRightRadius: parent.bottomRightRadius
|
||||||
color: root.textColour
|
color: root.textColour
|
||||||
|
disabled: root.disabled
|
||||||
|
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
root.active?.clicked();
|
root.active?.clicked();
|
||||||
|
|
@ -59,7 +63,7 @@ Row {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
animate: true
|
animate: true
|
||||||
text: root.active?.activeIcon ?? ""
|
text: root.active?.activeIcon ?? ""
|
||||||
color: root.textColour
|
color: root.disabled ? root.disabledTextColour : root.textColour
|
||||||
fill: 1
|
fill: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +74,7 @@ Row {
|
||||||
Layout.preferredWidth: implicitWidth
|
Layout.preferredWidth: implicitWidth
|
||||||
animate: true
|
animate: true
|
||||||
text: root.active?.activeText ?? ""
|
text: root.active?.activeText ?? ""
|
||||||
color: root.textColour
|
color: root.disabled ? root.disabledTextColour : root.textColour
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Behavior on Layout.preferredWidth {
|
Behavior on Layout.preferredWidth {
|
||||||
|
|
@ -90,7 +94,7 @@ Row {
|
||||||
radius: implicitHeight / 2
|
radius: implicitHeight / 2
|
||||||
topLeftRadius: rad
|
topLeftRadius: rad
|
||||||
bottomLeftRadius: rad
|
bottomLeftRadius: rad
|
||||||
color: root.colour
|
color: root.disabled ? root.disabledColour : root.colour
|
||||||
|
|
||||||
implicitWidth: implicitHeight
|
implicitWidth: implicitHeight
|
||||||
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
|
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
|
||||||
|
|
@ -101,6 +105,7 @@ Row {
|
||||||
rect.topRightRadius: parent.topRightRadius
|
rect.topRightRadius: parent.topRightRadius
|
||||||
rect.bottomRightRadius: parent.bottomRightRadius
|
rect.bottomRightRadius: parent.bottomRightRadius
|
||||||
color: root.textColour
|
color: root.textColour
|
||||||
|
disabled: root.disabled
|
||||||
|
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
root.expanded = !root.expanded;
|
root.expanded = !root.expanded;
|
||||||
|
|
@ -114,7 +119,7 @@ Row {
|
||||||
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
|
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
|
||||||
|
|
||||||
text: "expand_more"
|
text: "expand_more"
|
||||||
color: root.textColour
|
color: root.disabled ? root.disabledTextColour : root.textColour
|
||||||
rotation: root.expanded ? 180 : 0
|
rotation: root.expanded ? 180 : 0
|
||||||
|
|
||||||
Behavior on anchors.horizontalCenterOffset {
|
Behavior on anchors.horizontalCenterOffset {
|
||||||
|
|
|
||||||
|
|
@ -24,18 +24,17 @@ StyledRect {
|
||||||
property alias label: label
|
property alias label: label
|
||||||
|
|
||||||
property bool internalChecked
|
property bool internalChecked
|
||||||
property color activeColour: type == TextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
property color activeColour: type === TextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
||||||
property color inactiveColour: type == TextButton.Filled ? Colours.palette.m3surfaceContainer : Colours.palette.m3secondaryContainer
|
property color inactiveColour: type === TextButton.Filled ? Colours.palette.m3surfaceContainer : Colours.palette.m3secondaryContainer
|
||||||
property color activeOnColour: type == TextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
property color activeOnColour: type === TextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
||||||
property color inactiveOnColour: type == TextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
property color inactiveOnColour: type === TextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
||||||
|
|
||||||
function onClicked(): void {
|
signal clicked
|
||||||
}
|
|
||||||
|
|
||||||
onCheckedChanged: internalChecked = checked
|
onCheckedChanged: internalChecked = checked
|
||||||
|
|
||||||
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
|
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
|
||||||
color: type == TextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
|
color: type === TextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
|
||||||
|
|
||||||
implicitWidth: label.implicitWidth + horizontalPadding * 2
|
implicitWidth: label.implicitWidth + horizontalPadding * 2
|
||||||
implicitHeight: label.implicitHeight + verticalPadding * 2
|
implicitHeight: label.implicitHeight + verticalPadding * 2
|
||||||
|
|
@ -48,7 +47,7 @@ StyledRect {
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
if (root.toggle)
|
if (root.toggle)
|
||||||
root.internalChecked = !root.internalChecked;
|
root.internalChecked = !root.internalChecked;
|
||||||
root.onClicked();
|
root.clicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -181,10 +181,7 @@ Loader {
|
||||||
type: TextButton.Text
|
type: TextButton.Text
|
||||||
label.color: Colours.palette.m3primary
|
label.color: Colours.palette.m3primary
|
||||||
stateLayer.color: Colours.palette.m3primary
|
stateLayer.color: Colours.palette.m3primary
|
||||||
|
onClicked: root.props.recordingConfirmDelete = ""
|
||||||
function onClicked(): void {
|
|
||||||
root.props.recordingConfirmDelete = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextButton {
|
TextButton {
|
||||||
|
|
@ -192,8 +189,7 @@ Loader {
|
||||||
type: TextButton.Text
|
type: TextButton.Text
|
||||||
label.color: Colours.palette.m3primary
|
label.color: Colours.palette.m3primary
|
||||||
stateLayer.color: Colours.palette.m3primary
|
stateLayer.color: Colours.palette.m3primary
|
||||||
|
onClicked: {
|
||||||
function onClicked(): void {
|
|
||||||
CUtils.deleteFile(Qt.resolvedUrl(root.props.recordingConfirmDelete));
|
CUtils.deleteFile(Qt.resolvedUrl(root.props.recordingConfirmDelete));
|
||||||
root.props.recordingConfirmDelete = "";
|
root.props.recordingConfirmDelete = "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,8 @@ pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.components.controls
|
import qs.components.controls
|
||||||
import qs.components.containers
|
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.config
|
import qs.config
|
||||||
import qs.utils
|
|
||||||
import Caelestia
|
|
||||||
import Caelestia.Models
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
|
@ -18,7 +12,6 @@ StyledRect {
|
||||||
|
|
||||||
required property var props
|
required property var props
|
||||||
required property var visibilities
|
required property var visibilities
|
||||||
property bool modePickerExpanded
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
|
||||||
|
|
@ -80,6 +73,7 @@ StyledRect {
|
||||||
}
|
}
|
||||||
|
|
||||||
SplitButton {
|
SplitButton {
|
||||||
|
disabled: Recorder.running
|
||||||
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
|
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
|
||||||
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
|
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
|
||||||
|
|
||||||
|
|
@ -88,250 +82,181 @@ StyledRect {
|
||||||
icon: "fullscreen"
|
icon: "fullscreen"
|
||||||
text: qsTr("Record fullscreen")
|
text: qsTr("Record fullscreen")
|
||||||
activeText: qsTr("Fullscreen")
|
activeText: qsTr("Fullscreen")
|
||||||
onClicked: Recorder.toggle()
|
onClicked: Recorder.start()
|
||||||
},
|
},
|
||||||
MenuItem {
|
MenuItem {
|
||||||
icon: "screenshot_region"
|
icon: "screenshot_region"
|
||||||
text: qsTr("Record region")
|
text: qsTr("Record region")
|
||||||
activeText: qsTr("Region")
|
activeText: qsTr("Region")
|
||||||
onClicked: Recorder.toggle(["-r"])
|
onClicked: Recorder.start(["-r"])
|
||||||
},
|
},
|
||||||
MenuItem {
|
MenuItem {
|
||||||
icon: "select_to_speak"
|
icon: "select_to_speak"
|
||||||
text: qsTr("Record fullscreen with sound")
|
text: qsTr("Record fullscreen with sound")
|
||||||
activeText: qsTr("Fullscreen")
|
activeText: qsTr("Fullscreen")
|
||||||
onClicked: Recorder.toggle(["-s"])
|
onClicked: Recorder.start(["-s"])
|
||||||
},
|
},
|
||||||
MenuItem {
|
MenuItem {
|
||||||
icon: "volume_up"
|
icon: "volume_up"
|
||||||
text: qsTr("Record region with sound")
|
text: qsTr("Record region with sound")
|
||||||
activeText: qsTr("Region")
|
activeText: qsTr("Region")
|
||||||
onClicked: Recorder.toggle(["-sr"])
|
onClicked: Recorder.start(["-sr"])
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WrapperMouseArea {
|
Loader {
|
||||||
Layout.bottomMargin: -layout.spacing
|
id: listOrControls
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
property bool running: Recorder.running
|
||||||
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.fillWidth: true
|
||||||
Layout.rightMargin: -Appearance.spacing.small
|
Layout.preferredHeight: implicitHeight
|
||||||
implicitHeight: (Appearance.font.size.larger + Appearance.padding.small) * (root.props.recordingListExpanded ? 10 : 3)
|
sourceComponent: running ? recordingControls : recordingList
|
||||||
clip: true
|
|
||||||
|
|
||||||
StyledScrollBar.vertical: StyledScrollBar {}
|
Behavior on Layout.preferredHeight {
|
||||||
|
Anim {}
|
||||||
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(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton {
|
|
||||||
icon: "folder"
|
|
||||||
type: IconButton.Text
|
|
||||||
|
|
||||||
function onClicked(): void {
|
|
||||||
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
|
|
||||||
|
|
||||||
function onClicked(): void {
|
|
||||||
root.props.recordingConfirmDelete = recording.modelData.path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add: Transition {
|
Behavior on running {
|
||||||
Anim {
|
SequentialAnimation {
|
||||||
property: "opacity"
|
ParallelAnimation {
|
||||||
from: 0
|
Anim {
|
||||||
to: 1
|
target: listOrControls
|
||||||
}
|
property: "scale"
|
||||||
Anim {
|
to: 0.7
|
||||||
property: "scale"
|
duration: Appearance.anim.durations.small
|
||||||
from: 0.5
|
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
||||||
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 {}
|
|
||||||
}
|
}
|
||||||
|
Anim {
|
||||||
Behavior on scale {
|
target: listOrControls
|
||||||
Anim {}
|
property: "opacity"
|
||||||
}
|
to: 0
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
Behavior on Layout.preferredHeight {
|
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
||||||
Anim {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PropertyAction {}
|
||||||
RowLayout {
|
ParallelAnimation {
|
||||||
spacing: Appearance.spacing.smaller
|
Anim {
|
||||||
|
target: listOrControls
|
||||||
MaterialIcon {
|
property: "scale"
|
||||||
Layout.alignment: Qt.AlignHCenter
|
to: 1
|
||||||
text: "scan_delete"
|
duration: Appearance.anim.durations.small
|
||||||
color: Colours.palette.m3outline
|
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||||
|
|
||||||
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 {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Anim {
|
||||||
StyledText {
|
target: listOrControls
|
||||||
text: qsTr("No recordings found")
|
property: "opacity"
|
||||||
color: Colours.palette.m3outline
|
to: 1
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Behavior on opacity {
|
}
|
||||||
Anim {}
|
}
|
||||||
}
|
|
||||||
}
|
Component {
|
||||||
|
id: recordingList
|
||||||
Behavior on implicitHeight {
|
|
||||||
Anim {
|
RecordingList {
|
||||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
props: root.props
|
||||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
visibilities: root.visibilities
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: recordingControls
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: Recorder.paused ? Colours.palette.m3tertiary : Colours.palette.m3error
|
||||||
|
|
||||||
|
implicitWidth: recText.implicitWidth + Appearance.padding.normal * 2
|
||||||
|
implicitHeight: recText.implicitHeight + Appearance.padding.smaller * 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: recText
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: !Recorder.paused
|
||||||
|
alwaysRunToEnd: true
|
||||||
|
loops: Animation.Infinite
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
const elapsed = Recorder.elapsed;
|
||||||
|
|
||||||
|
const hours = Math.floor(elapsed / 3600);
|
||||||
|
const mins = Math.floor((elapsed % 3600) / 60);
|
||||||
|
const secs = Math.floor(elapsed % 60).toString().padStart(2, "0");
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
icon: "stop"
|
||||||
|
inactiveColour: Colours.palette.m3error
|
||||||
|
inactiveOnColour: Colours.palette.m3onError
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
onClicked: Recorder.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
237
modules/utilities/cards/RecordingList.qml
Normal file
237
modules/utilities/cards/RecordingList.qml
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,23 +2,33 @@ pragma Singleton
|
||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property alias running: props.running
|
readonly property alias running: props.running
|
||||||
readonly property alias paused: props.paused
|
readonly property alias paused: props.paused
|
||||||
|
readonly property alias elapsed: props.elapsed
|
||||||
|
property bool needsStart
|
||||||
|
property list<string> startArgs
|
||||||
|
property bool needsStop
|
||||||
|
property bool needsPause
|
||||||
|
|
||||||
function toggle(extraArgs: list<string>): void {
|
function start(extraArgs: list<string>): void {
|
||||||
Quickshell.execDetached(["caelestia", "record", ...extraArgs]);
|
needsStart = true;
|
||||||
props.running = !props.running;
|
startArgs = extraArgs;
|
||||||
if (!props.running)
|
checkProc.running = true;
|
||||||
props.paused = false;
|
}
|
||||||
|
|
||||||
|
function stop(): void {
|
||||||
|
needsStop = true;
|
||||||
|
checkProc.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePause(): void {
|
function togglePause(): void {
|
||||||
Quickshell.execDetached(["caelestia", "record", "-p"]);
|
needsPause = true;
|
||||||
props.paused = !props.paused;
|
checkProc.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistentProperties {
|
PersistentProperties {
|
||||||
|
|
@ -26,13 +36,47 @@ Singleton {
|
||||||
|
|
||||||
property bool running: false
|
property bool running: false
|
||||||
property bool paused: false
|
property bool paused: false
|
||||||
|
property real elapsed: 0 // Might get too large for int
|
||||||
|
|
||||||
reloadableId: "recorder"
|
reloadableId: "recorder"
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
|
id: checkProc
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
command: ["pidof", "gpu-screen-recorder"]
|
command: ["pidof", "gpu-screen-recorder"]
|
||||||
onExited: code => props.running = code === 0
|
onExited: code => {
|
||||||
|
props.running = code === 0;
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
if (root.needsStop) {
|
||||||
|
Quickshell.execDetached(["caelestia", "record"]);
|
||||||
|
props.running = false;
|
||||||
|
props.paused = false;
|
||||||
|
} else if (root.needsPause) {
|
||||||
|
Quickshell.execDetached(["caelestia", "record", "-p"]);
|
||||||
|
props.paused = !props.paused;
|
||||||
|
}
|
||||||
|
} else if (root.needsStart) {
|
||||||
|
Quickshell.execDetached(["caelestia", "record", ...root.startArgs]);
|
||||||
|
props.running = true;
|
||||||
|
props.paused = false;
|
||||||
|
props.elapsed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.needsStart = false;
|
||||||
|
root.needsStop = false;
|
||||||
|
root.needsPause = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Time
|
||||||
|
// enabled: props.running && !props.paused
|
||||||
|
|
||||||
|
function onSecondsChanged(): void {
|
||||||
|
props.elapsed++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue