utilities: add recording control
This commit is contained in:
parent
b2b862135a
commit
4b221e2fd5
14 changed files with 536 additions and 23 deletions
68
components/controls/IconButton.qml
Normal file
68
components/controls/IconButton.qml
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import ".."
|
||||||
|
import qs.services
|
||||||
|
import qs.config
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
Filled,
|
||||||
|
Tonal,
|
||||||
|
Text
|
||||||
|
}
|
||||||
|
|
||||||
|
required property string icon
|
||||||
|
property bool checked
|
||||||
|
property bool toggle
|
||||||
|
property real padding: type == IconButton.Text ? Appearance.padding.small / 2 : Appearance.padding.smaller
|
||||||
|
property alias font: label.font
|
||||||
|
property int type: IconButton.Filled
|
||||||
|
|
||||||
|
property alias label: label
|
||||||
|
|
||||||
|
property bool internalChecked
|
||||||
|
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 activeOnColour: type == IconButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
||||||
|
property color inactiveOnColour: type == IconButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
onCheckedChanged: internalChecked = checked
|
||||||
|
|
||||||
|
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
|
||||||
|
color: type == IconButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
|
||||||
|
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
implicitHeight: label.implicitHeight + padding * 2
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
if (root.toggle)
|
||||||
|
root.internalChecked = !root.internalChecked;
|
||||||
|
root.onClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: label
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
text: root.icon
|
||||||
|
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
|
||||||
|
fill: root.internalChecked ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on fill {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on radius {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,12 +7,24 @@ import QtQuick.Controls
|
||||||
ScrollBar {
|
ScrollBar {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
|
||||||
contentItem: StyledRect {
|
contentItem: StyledRect {
|
||||||
|
x: 0
|
||||||
implicitWidth: 6
|
implicitWidth: 6
|
||||||
opacity: root.pressed ? 1 : root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1) ? 0.8 : 0
|
opacity: root.pressed ? 1 : mouse.containsMouse ? 0.8 : root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1) ? 0.6 : 0
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
color: Colours.palette.m3secondary
|
color: Colours.palette.m3secondary
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouse
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,7 @@ JsonObject {
|
||||||
component Apps: JsonObject {
|
component Apps: JsonObject {
|
||||||
property list<string> terminal: ["foot"]
|
property list<string> terminal: ["foot"]
|
||||||
property list<string> audio: ["pavucontrol"]
|
property list<string> audio: ["pavucontrol"]
|
||||||
|
property list<string> playback: ["mpv"]
|
||||||
|
property list<string> explorer: ["thunar"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,18 @@ import QtQuick.Layouts
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
required property var props
|
||||||
required property var visibilities
|
required property var visibilities
|
||||||
|
|
||||||
spacing: Appearance.spacing.normal
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
IdleInhibit {}
|
IdleInhibit {}
|
||||||
|
|
||||||
|
Record {
|
||||||
|
props: root.props
|
||||||
|
visibilities: root.visibilities
|
||||||
|
}
|
||||||
|
|
||||||
Toggles {
|
Toggles {
|
||||||
visibilities: root.visibilities
|
visibilities: root.visibilities
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,18 @@ pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.config
|
import qs.config
|
||||||
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var visibilities
|
required property var visibilities
|
||||||
|
readonly property PersistentProperties props: PersistentProperties {
|
||||||
|
property bool recordingListExpanded: false
|
||||||
|
|
||||||
|
reloadableId: "utilities"
|
||||||
|
}
|
||||||
|
|
||||||
visible: height > 0
|
visible: height > 0
|
||||||
implicitHeight: 0
|
implicitHeight: 0
|
||||||
|
|
@ -76,6 +82,7 @@ Item {
|
||||||
active: true
|
active: true
|
||||||
|
|
||||||
sourceComponent: Content {
|
sourceComponent: Content {
|
||||||
|
props: root.props
|
||||||
visibilities: root.visibilities
|
visibilities: root.visibilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,9 @@ StyledRect {
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
Behavior on implicitHeight {
|
||||||
Anim {}
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
315
modules/utilities/cards/Record.qml
Normal file
315
modules/utilities/cards/Record.qml
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
CUtils.deleteFile(Qt.resolvedUrl(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -89,7 +89,7 @@ StyledRect {
|
||||||
required property string icon
|
required property string icon
|
||||||
required property string text
|
required property string text
|
||||||
property bool checked
|
property bool checked
|
||||||
property bool toggle
|
property bool toggle: true
|
||||||
property bool internalChecked
|
property bool internalChecked
|
||||||
|
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ QString FileSystemEntry::name() const {
|
||||||
return m_fileInfo.fileName();
|
return m_fileInfo.fileName();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QString FileSystemEntry::baseName() const {
|
||||||
|
return m_fileInfo.baseName();
|
||||||
|
};
|
||||||
|
|
||||||
QString FileSystemEntry::parentDir() const {
|
QString FileSystemEntry::parentDir() const {
|
||||||
return m_fileInfo.absolutePath();
|
return m_fileInfo.absolutePath();
|
||||||
};
|
};
|
||||||
|
|
@ -149,6 +153,21 @@ void FileSystemModel::setShowHidden(bool showHidden) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FileSystemModel::sortReverse() const {
|
||||||
|
return m_sortReverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSystemModel::setSortReverse(bool sortReverse) {
|
||||||
|
if (m_sortReverse == sortReverse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sortReverse = sortReverse;
|
||||||
|
emit sortReverseChanged();
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
FileSystemModel::Filter FileSystemModel::filter() const {
|
FileSystemModel::Filter FileSystemModel::filter() const {
|
||||||
return m_filter;
|
return m_filter;
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +183,21 @@ void FileSystemModel::setFilter(Filter filter) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList FileSystemModel::nameFilters() const {
|
||||||
|
return m_nameFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
|
||||||
|
if (m_nameFilters == nameFilters) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nameFilters = nameFilters;
|
||||||
|
emit nameFiltersChanged();
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
QList<FileSystemEntry*> FileSystemModel::entries() const {
|
QList<FileSystemEntry*> FileSystemModel::entries() const {
|
||||||
return m_entries;
|
return m_entries;
|
||||||
}
|
}
|
||||||
|
|
@ -238,22 +272,22 @@ void FileSystemModel::updateEntries() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||||
const bool recursive = m_recursive;
|
const auto recursive = m_recursive;
|
||||||
const bool showHidden = m_showHidden;
|
const auto showHidden = m_showHidden;
|
||||||
const auto filter = m_filter;
|
const auto filter = m_filter;
|
||||||
|
const auto nameFilters = m_nameFilters;
|
||||||
const auto oldEntries = m_entries;
|
const auto oldEntries = m_entries;
|
||||||
const auto baseDir = m_dir;
|
const auto baseDir = m_dir;
|
||||||
|
|
||||||
const auto future = QtConcurrent::run([dir, recursive, showHidden, filter, oldEntries, baseDir](
|
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
||||||
QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
|
||||||
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
||||||
|
|
||||||
std::optional<QDirIterator> iter;
|
std::optional<QDirIterator> iter;
|
||||||
|
|
||||||
if (filter == Images) {
|
if (filter == Images) {
|
||||||
QStringList nameFilters;
|
QStringList extraNameFilters = nameFilters;
|
||||||
for (const auto& format : QImageReader::supportedImageFormats()) {
|
for (const auto& format : QImageReader::supportedImageFormats()) {
|
||||||
nameFilters << "*." + format;
|
extraNameFilters << "*." + format;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir::Filters filters = QDir::Files;
|
QDir::Filters filters = QDir::Files;
|
||||||
|
|
@ -261,7 +295,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||||
filters |= QDir::Hidden;
|
filters |= QDir::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
iter.emplace(dir, nameFilters, filters, flags);
|
iter.emplace(dir, extraNameFilters, filters, flags);
|
||||||
} else {
|
} else {
|
||||||
QDir::Filters filters;
|
QDir::Filters filters;
|
||||||
|
|
||||||
|
|
@ -277,7 +311,11 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||||
filters |= QDir::Hidden;
|
filters |= QDir::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nameFilters.isEmpty()) {
|
||||||
iter.emplace(dir, filters, flags);
|
iter.emplace(dir, filters, flags);
|
||||||
|
} else {
|
||||||
|
iter.emplace(dir, nameFilters, filters, flags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSet<QString> newPaths;
|
QSet<QString> newPaths;
|
||||||
|
|
@ -376,13 +414,16 @@ void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet
|
||||||
for (const auto& path : addedPaths) {
|
for (const auto& path : addedPaths) {
|
||||||
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
|
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
|
||||||
}
|
}
|
||||||
std::sort(newEntries.begin(), newEntries.end(), &FileSystemModel::compareEntries);
|
const auto comp = [this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
||||||
|
return compareEntries(a, b);
|
||||||
|
};
|
||||||
|
std::sort(newEntries.begin(), newEntries.end(), comp);
|
||||||
|
|
||||||
int insertStart = -1;
|
int insertStart = -1;
|
||||||
int prevRow = -1;
|
int prevRow = -1;
|
||||||
QList<FileSystemEntry*> batchItems;
|
QList<FileSystemEntry*> batchItems;
|
||||||
for (const auto& entry : newEntries) {
|
for (const auto& entry : newEntries) {
|
||||||
const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), entry, &FileSystemModel::compareEntries);
|
const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), entry, comp);
|
||||||
int row = static_cast<int>(it - m_entries.begin());
|
int row = static_cast<int>(it - m_entries.begin());
|
||||||
|
|
||||||
if (insertStart == -1) {
|
if (insertStart == -1) {
|
||||||
|
|
@ -420,11 +461,11 @@ void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet
|
||||||
emit entriesChanged();
|
emit entriesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) {
|
bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const {
|
||||||
if (a->isDir() != b->isDir()) {
|
if (a->isDir() != b->isDir()) {
|
||||||
return a->isDir();
|
return m_sortReverse ^ a->isDir();
|
||||||
}
|
}
|
||||||
return a->relativePath().localeAwareCompare(b->relativePath()) < 0;
|
return m_sortReverse ^ (a->relativePath().localeAwareCompare(b->relativePath()) < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace caelestia
|
} // namespace caelestia
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class FileSystemEntry : public QObject {
|
||||||
Q_PROPERTY(QString path READ path CONSTANT)
|
Q_PROPERTY(QString path READ path CONSTANT)
|
||||||
Q_PROPERTY(QString relativePath READ relativePath CONSTANT)
|
Q_PROPERTY(QString relativePath READ relativePath CONSTANT)
|
||||||
Q_PROPERTY(QString name READ name CONSTANT)
|
Q_PROPERTY(QString name READ name CONSTANT)
|
||||||
|
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
||||||
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
||||||
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
||||||
Q_PROPERTY(qint64 size READ size CONSTANT)
|
Q_PROPERTY(qint64 size READ size CONSTANT)
|
||||||
|
|
@ -32,6 +33,7 @@ public:
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
[[nodiscard]] QString relativePath() const;
|
[[nodiscard]] QString relativePath() const;
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
|
[[nodiscard]] QString baseName() const;
|
||||||
[[nodiscard]] QString parentDir() const;
|
[[nodiscard]] QString parentDir() const;
|
||||||
[[nodiscard]] QString suffix() const;
|
[[nodiscard]] QString suffix() const;
|
||||||
[[nodiscard]] qint64 size() const;
|
[[nodiscard]] qint64 size() const;
|
||||||
|
|
@ -60,7 +62,9 @@ class FileSystemModel : public QAbstractListModel {
|
||||||
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
||||||
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
||||||
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
||||||
|
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
||||||
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||||
|
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
|
||||||
|
|
||||||
Q_PROPERTY(QList<FileSystemEntry*> entries READ entries NOTIFY entriesChanged)
|
Q_PROPERTY(QList<FileSystemEntry*> entries READ entries NOTIFY entriesChanged)
|
||||||
|
|
||||||
|
|
@ -91,9 +95,15 @@ public:
|
||||||
[[nodiscard]] bool showHidden() const;
|
[[nodiscard]] bool showHidden() const;
|
||||||
void setShowHidden(bool showHidden);
|
void setShowHidden(bool showHidden);
|
||||||
|
|
||||||
|
[[nodiscard]] bool sortReverse() const;
|
||||||
|
void setSortReverse(bool sortReverse);
|
||||||
|
|
||||||
[[nodiscard]] Filter filter() const;
|
[[nodiscard]] Filter filter() const;
|
||||||
void setFilter(Filter filter);
|
void setFilter(Filter filter);
|
||||||
|
|
||||||
|
[[nodiscard]] QStringList nameFilters() const;
|
||||||
|
void setNameFilters(const QStringList& nameFilters);
|
||||||
|
|
||||||
[[nodiscard]] QList<FileSystemEntry*> entries() const;
|
[[nodiscard]] QList<FileSystemEntry*> entries() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
@ -101,7 +111,9 @@ signals:
|
||||||
void recursiveChanged();
|
void recursiveChanged();
|
||||||
void watchChangesChanged();
|
void watchChangesChanged();
|
||||||
void showHiddenChanged();
|
void showHiddenChanged();
|
||||||
|
void sortReverseChanged();
|
||||||
void filterChanged();
|
void filterChanged();
|
||||||
|
void nameFiltersChanged();
|
||||||
void entriesChanged();
|
void entriesChanged();
|
||||||
|
|
||||||
void added(const FileSystemEntry* entry);
|
void added(const FileSystemEntry* entry);
|
||||||
|
|
@ -117,7 +129,9 @@ private:
|
||||||
bool m_recursive;
|
bool m_recursive;
|
||||||
bool m_watchChanges;
|
bool m_watchChanges;
|
||||||
bool m_showHidden;
|
bool m_showHidden;
|
||||||
|
bool m_sortReverse;
|
||||||
Filter m_filter;
|
Filter m_filter;
|
||||||
|
QStringList m_nameFilters;
|
||||||
|
|
||||||
void watchDirIfRecursive(const QString& path);
|
void watchDirIfRecursive(const QString& path);
|
||||||
void update();
|
void update();
|
||||||
|
|
@ -125,7 +139,7 @@ private:
|
||||||
void updateEntries();
|
void updateEntries();
|
||||||
void updateEntriesForDir(const QString& dir);
|
void updateEntriesForDir(const QString& dir);
|
||||||
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
||||||
[[nodiscard]] static bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b);
|
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace caelestia
|
} // namespace caelestia
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,6 @@ void CUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CUtils::copyFile(const QUrl& source, const QUrl& target) const {
|
|
||||||
return this->copyFile(source, target, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
|
bool CUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
|
||||||
if (!source.isLocalFile()) {
|
if (!source.isLocalFile()) {
|
||||||
qWarning() << "CUtils::copyFile: source" << source << "is not a local file";
|
qWarning() << "CUtils::copyFile: source" << source << "is not a local file";
|
||||||
|
|
@ -108,6 +104,15 @@ bool CUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) co
|
||||||
return QFile::copy(source.toLocalFile(), target.toLocalFile());
|
return QFile::copy(source.toLocalFile(), target.toLocalFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CUtils::deleteFile(const QUrl& path) const {
|
||||||
|
if (!path.isLocalFile()) {
|
||||||
|
qWarning() << "CUtils::deleteFile: path" << path << "is not a local file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QFile::remove(path.toLocalFile());
|
||||||
|
}
|
||||||
|
|
||||||
void CUtils::getDominantColour(QQuickItem* item, QJSValue callback) {
|
void CUtils::getDominantColour(QQuickItem* item, QJSValue callback) {
|
||||||
this->getDominantColour(item, 128, callback);
|
this->getDominantColour(item, 128, callback);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ public:
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target) const;
|
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
||||||
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite) const;
|
Q_INVOKABLE bool deleteFile(const QUrl& path) const;
|
||||||
|
|
||||||
Q_INVOKABLE void getDominantColour(QQuickItem* item, QJSValue callback);
|
Q_INVOKABLE void getDominantColour(QQuickItem* item, QJSValue callback);
|
||||||
Q_INVOKABLE void getDominantColour(QQuickItem* item, int rescaleSize, QJSValue callback);
|
Q_INVOKABLE void getDominantColour(QQuickItem* item, int rescaleSize, QJSValue callback);
|
||||||
|
|
|
||||||
38
services/Recorder.qml
Normal file
38
services/Recorder.qml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property alias running: props.running
|
||||||
|
readonly property alias paused: props.paused
|
||||||
|
|
||||||
|
function toggle(): void {
|
||||||
|
Quickshell.execDetached(["caelestia", "record"]);
|
||||||
|
props.running = !props.running;
|
||||||
|
if (!props.running)
|
||||||
|
props.paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePause(): void {
|
||||||
|
Quickshell.execDetached(["caelestia", "record", "-p"]);
|
||||||
|
props.paused = !props.paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistentProperties {
|
||||||
|
id: props
|
||||||
|
|
||||||
|
property bool running: false
|
||||||
|
property bool paused: false
|
||||||
|
|
||||||
|
reloadableId: "recorder"
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
running: true
|
||||||
|
command: ["pidof", "gpu-screen-recorder"]
|
||||||
|
onExited: code => props.running = code === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ Singleton {
|
||||||
|
|
||||||
readonly property string home: Quickshell.env("HOME")
|
readonly property string home: Quickshell.env("HOME")
|
||||||
readonly property string pictures: Quickshell.env("XDG_PICTURES_DIR") || `${home}/Pictures`
|
readonly property string pictures: Quickshell.env("XDG_PICTURES_DIR") || `${home}/Pictures`
|
||||||
|
readonly property string videos: Quickshell.env("XDG_VIDEOS_DIR") || `${home}/Videos`
|
||||||
|
|
||||||
readonly property string data: `${Quickshell.env("XDG_DATA_HOME") || `${home}/.local/share`}/caelestia`
|
readonly property string data: `${Quickshell.env("XDG_DATA_HOME") || `${home}/.local/share`}/caelestia`
|
||||||
readonly property string state: `${Quickshell.env("XDG_STATE_HOME") || `${home}/.local/state`}/caelestia`
|
readonly property string state: `${Quickshell.env("XDG_STATE_HOME") || `${home}/.local/state`}/caelestia`
|
||||||
|
|
@ -17,6 +18,7 @@ Singleton {
|
||||||
|
|
||||||
readonly property string imagecache: `${cache}/imagecache`
|
readonly property string imagecache: `${cache}/imagecache`
|
||||||
readonly property string wallsdir: Quickshell.env("CAELESTIA_WALLPAPERS_DIR") || absolutePath(Config.paths.wallpaperDir)
|
readonly property string wallsdir: Quickshell.env("CAELESTIA_WALLPAPERS_DIR") || absolutePath(Config.paths.wallpaperDir)
|
||||||
|
readonly property string recsdir: Quickshell.env("CAELESTIA_RECORDINGS_DIR") || `${videos}/Recordings`
|
||||||
readonly property string libdir: Quickshell.env("CAELESTIA_LIB_DIR") || "/usr/lib/caelestia"
|
readonly property string libdir: Quickshell.env("CAELESTIA_LIB_DIR") || "/usr/lib/caelestia"
|
||||||
|
|
||||||
function toLocalFile(path: url): string {
|
function toLocalFile(path: url): string {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue