feat: added widgets capture, music visualiser, bluetooth, notes

This commit is contained in:
2026-09-22 01:22:27 +02:00
parent 9b023a0993
commit d15bb4687c
25 changed files with 1675 additions and 55 deletions
+199
View File
@@ -0,0 +1,199 @@
import QtQuick
import qs.Common
import qs.Widgets
Item {
id: widget
property var service: null
property var panel: null
readonly property bool recording: service ? service.recording : false
readonly property bool replayArmed: service ? service.replayArmed : false
readonly property bool busy: service ? service.busy : false
function monitorName() {
const screen = panel ? panel.targetScreen : null;
return (screen && screen.name) ? screen.name : "focused";
}
function elapsedText() {
const total = service ? service.elapsedSeconds : 0;
const mins = Math.floor(total / 60);
const secs = total % 60;
return (mins < 10 ? "0" : "") + mins + ":" + (secs < 10 ? "0" : "") + secs;
}
component CaptureButton: Rectangle {
id: button
property string icon: ""
property string label: ""
property bool active: false
property bool enabled: true
signal triggered
height: 46
radius: Theme.cornerRadius
color: active ? Theme.primarySelected : (buttonArea.containsMouse && enabled ? Theme.surfaceHover : Theme.withAlpha(Theme.surfaceLight, Theme.popupTransparency))
border.color: active ? Theme.primary : Theme.outlineLight
border.width: active ? 1 : Theme.layerOutlineWidth
opacity: enabled ? 1 : 0.45
Row {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: button.icon
size: Theme.iconSize - 6
color: button.active ? Theme.primary : Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: button.label
font.pixelSize: Theme.fontSizeSmall
font.weight: button.active ? Font.Medium : Font.Normal
color: button.active ? Theme.primary : Theme.surfaceText
}
}
MouseArea {
id: buttonArea
anchors.fill: parent
hoverEnabled: true
enabled: button.enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: button.triggered()
}
}
Column {
anchors.fill: parent
spacing: Theme.spacingS
Rectangle {
width: parent.width
height: 40
radius: Theme.cornerRadius
color: widget.busy ? Theme.withAlpha(Theme.error, 0.15) : Theme.withAlpha(Theme.surfaceLight, Theme.popupTransparency)
border.color: widget.busy ? Theme.error : Theme.outlineLight
border.width: Theme.layerOutlineWidth
Rectangle {
id: statusDot
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
width: 10
height: 10
radius: 5
color: widget.recording ? Theme.error : (widget.replayArmed ? Theme.warning : Theme.surfaceVariantText)
SequentialAnimation on opacity {
running: widget.busy
loops: Animation.Infinite
NumberAnimation {
to: 0.25
duration: 700
}
NumberAnimation {
to: 1
duration: 700
}
}
}
StyledText {
anchors.left: statusDot.right
anchors.leftMargin: Theme.spacingS
anchors.right: elapsed.left
anchors.verticalCenter: parent.verticalCenter
text: {
if (widget.recording)
return "Recording " + widget.monitorName();
if (widget.replayArmed)
return "Replay buffer armed";
return "Idle · " + widget.monitorName();
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
elide: Text.ElideRight
}
StyledText {
id: elapsed
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
text: widget.elapsedText()
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: widget.busy ? Theme.error : Theme.surfaceVariantText
visible: widget.busy
}
}
Grid {
width: parent.width
columns: 2
rowSpacing: Theme.spacingS
columnSpacing: Theme.spacingS
readonly property real cellWidth: (width - columnSpacing) / 2
CaptureButton {
width: parent.cellWidth
icon: widget.recording ? "stop_circle" : "videocam"
label: widget.recording ? "Stop & save" : "Record"
active: widget.recording
enabled: !widget.replayArmed
onTriggered: widget.service.toggleRecording(widget.monitorName())
}
CaptureButton {
width: parent.cellWidth
icon: widget.replayArmed ? "stop_circle" : "history"
label: widget.replayArmed ? "Disarm replay" : "Arm replay"
active: widget.replayArmed
enabled: !widget.recording
onTriggered: widget.service.toggleReplay(widget.monitorName())
}
CaptureButton {
width: parent.cellWidth
icon: "save"
label: "Save last " + (widget.service ? widget.service.replaySeconds : 30) + "s"
enabled: widget.replayArmed
onTriggered: widget.service.saveReplay()
}
CaptureButton {
width: parent.cellWidth
icon: "screenshot_region"
label: "Screenshot"
onTriggered: {
if (widget.panel)
widget.panel.requestClose();
widget.service.screenshot("region");
}
}
}
StyledText {
width: parent.width
text: (widget.service && widget.service.lastFile !== "") ? "Last: " + widget.service.lastFile.split("/").pop() : "Saves to " + (widget.service ? widget.service.saveDirectory : "")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideMiddle
}
}
}