utilities/record: select mode

This commit is contained in:
2 * r + 2 * t 2025-09-15 15:59:24 +10:00
parent 9a92ad2d62
commit 4f54763e02
11 changed files with 392 additions and 18 deletions

View file

@ -8,6 +8,7 @@ MouseArea {
property bool disabled
property color color: Colours.palette.m3onSurface
property real radius: parent?.radius ?? 0
property alias rect: hoverLayer
function onClicked(): void {
}

View file

@ -0,0 +1,86 @@
import ".."
import qs.services
import qs.config
import QtQuick
StyledRect {
id: root
enum Type {
Filled,
Tonal,
Text
}
property alias icon: iconLabel.text
property alias text: label.text
property bool checked
property bool toggle
property real horizontalPadding: Appearance.padding.normal
property real verticalPadding: Appearance.padding.smaller
property alias font: label.font
property int type: IconTextButton.Filled
property alias stateLayer: stateLayer
property alias iconLabel: iconLabel
property alias label: label
property bool internalChecked
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 activeOnColour: type == IconTextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
property color inactiveOnColour: type == IconTextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
function onClicked(): void {
}
onCheckedChanged: internalChecked = checked
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
color: type == IconTextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
implicitWidth: row.implicitWidth + horizontalPadding * 2
implicitHeight: row.implicitHeight + verticalPadding * 2
StateLayer {
id: stateLayer
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
function onClicked(): void {
if (root.toggle)
root.internalChecked = !root.internalChecked;
root.onClicked();
}
}
Row {
id: row
anchors.centerIn: parent
spacing: Appearance.spacing.small
MaterialIcon {
id: iconLabel
anchors.verticalCenter: parent.verticalCenter
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
fill: root.internalChecked ? 1 : 0
Behavior on fill {
Anim {}
}
}
StyledText {
id: label
anchors.verticalCenter: parent.verticalCenter
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
}
}
Behavior on radius {
Anim {}
}
}

View file

@ -0,0 +1,111 @@
pragma ComponentBehavior: Bound
import ".."
import "../effects"
import qs.services
import qs.config
import QtQuick
import QtQuick.Layouts
Elevation {
id: root
property list<MenuItem> items
property MenuItem active: items[0] ?? null
property bool expanded
signal itemSelected(item: MenuItem)
radius: Appearance.rounding.small / 2
level: 2
implicitWidth: column.implicitWidth
implicitHeight: root.expanded ? column.implicitHeight : 0
opacity: root.expanded ? 1 : 0
StyledClippingRect {
anchors.fill: parent
radius: parent.radius
color: Colours.palette.m3surfaceContainer
ColumnLayout {
id: column
spacing: 0
Repeater {
model: root.items
StyledRect {
id: item
required property int index
required property MenuItem modelData
readonly property bool active: modelData === root.active
Layout.fillWidth: true
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2
color: Qt.alpha(Colours.palette.m3secondaryContainer, active ? 1 : 0)
StateLayer {
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
disabled: !root.expanded
function onClicked(): void {
root.itemSelected(item.modelData);
root.active = item.modelData;
root.expanded = false;
}
}
RowLayout {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
text: item.modelData.icon
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurfaceVariant
}
StyledText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
text: item.modelData.text
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0
visible: active
sourceComponent: MaterialIcon {
text: item.modelData.trailingIcon
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
}
}
}
}
}
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
}
}
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}

View file

@ -0,0 +1,11 @@
import QtQuick
QtObject {
required property string text
property string icon
property string trailingIcon
property string activeIcon: icon
property string activeText: text
signal clicked
}

View file

@ -0,0 +1,141 @@
import ".."
import qs.services
import qs.config
import QtQuick
import QtQuick.Layouts
Row {
id: root
enum Type {
Filled,
Tonal
}
property real horizontalPadding: Appearance.padding.normal
property real verticalPadding: Appearance.padding.smaller
property int type: SplitButton.Filled
property alias menuItems: menu.items
property alias active: menu.active
property alias expanded: menu.expanded
property alias menu: menu
property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
spacing: Math.floor(Appearance.spacing.small / 2)
StyledRect {
radius: implicitHeight / 2
topRightRadius: Appearance.rounding.small / 2
bottomRightRadius: Appearance.rounding.small / 2
color: root.colour
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
implicitHeight: expandBtn.implicitHeight
StateLayer {
id: stateLayer
rect.topRightRadius: parent.topRightRadius
rect.bottomRightRadius: parent.bottomRightRadius
color: root.textColour
function onClicked(): void {
root.active?.clicked();
}
}
RowLayout {
id: textRow
anchors.centerIn: parent
anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4)
spacing: Appearance.spacing.small
MaterialIcon {
id: iconLabel
Layout.alignment: Qt.AlignVCenter
animate: true
text: root.active?.activeIcon ?? ""
color: root.textColour
fill: 1
}
StyledText {
id: label
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: implicitWidth
animate: true
text: root.active?.activeText ?? ""
color: root.textColour
clip: true
Behavior on Layout.preferredWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
}
}
}
StyledRect {
id: expandBtn
property real rad: root.expanded ? implicitHeight / 2 : Appearance.rounding.small / 2
radius: implicitHeight / 2
topLeftRadius: rad
bottomLeftRadius: rad
color: root.colour
implicitWidth: implicitHeight
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
StateLayer {
id: expandStateLayer
rect.topRightRadius: parent.topRightRadius
rect.bottomRightRadius: parent.bottomRightRadius
color: root.textColour
function onClicked(): void {
root.expanded = !root.expanded;
}
}
MaterialIcon {
id: expandIcon
anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
text: "expand_more"
color: root.textColour
rotation: root.expanded ? 180 : 0
Behavior on anchors.horizontalCenterOffset {
Anim {}
}
Behavior on rotation {
Anim {}
}
}
Behavior on rad {
Anim {}
}
Menu {
id: menu
anchors.top: parent.bottom
anchors.right: parent.right
anchors.topMargin: Appearance.spacing.small
}
}
}

View file

@ -18,16 +18,16 @@ StyledRect {
property real horizontalPadding: Appearance.padding.normal
property real verticalPadding: Appearance.padding.smaller
property alias font: label.font
property int type: IconButton.Filled
property int type: TextButton.Filled
property alias stateLayer: stateLayer
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
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 activeOnColour: type == TextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
property color inactiveOnColour: type == TextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
function onClicked(): void {
}
@ -35,7 +35,7 @@ StyledRect {
onCheckedChanged: internalChecked = checked
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
color: type == IconButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
color: type == TextButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
implicitWidth: label.implicitWidth + horizontalPadding * 2
implicitHeight: label.implicitHeight + verticalPadding * 2

View file

@ -6,6 +6,6 @@ JsonObject {
property Sizes sizes: Sizes {}
component Sizes: JsonObject {
property int width: 400
property int width: 430
}
}

View file

@ -23,6 +23,7 @@ Item {
Record {
props: root.props
visibilities: root.visibilities
z: 1
}
Toggles {

View file

@ -12,6 +12,7 @@ Item {
readonly property PersistentProperties props: PersistentProperties {
property bool recordingListExpanded: false
property string recordingConfirmDelete
property string recordingMode
reloadableId: "utilities"
}

View file

@ -18,6 +18,7 @@ StyledRect {
required property var props
required property var visibilities
property bool modePickerExpanded
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
@ -34,6 +35,7 @@ StyledRect {
RowLayout {
spacing: Appearance.spacing.normal
z: 1
StyledRect {
implicitWidth: implicitHeight
@ -77,16 +79,36 @@ StyledRect {
}
}
IconButton {
icon: "not_started"
checked: Recorder.running
type: IconButton.Filled
inactiveColour: Colours.palette.m3surfaceContainerHighest
toggle: true
SplitButton {
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
function onClicked(): void {
Recorder.toggle();
}
menuItems: [
MenuItem {
icon: "fullscreen"
text: qsTr("Record fullscreen")
activeText: qsTr("Fullscreen")
onClicked: Recorder.toggle()
},
MenuItem {
icon: "screenshot_region"
text: qsTr("Record region")
activeText: qsTr("Region")
onClicked: Recorder.toggle(["-r"])
},
MenuItem {
icon: "select_to_speak"
text: qsTr("Record fullscreen with sound")
activeText: qsTr("Fullscreen")
onClicked: Recorder.toggle(["-s"])
},
MenuItem {
icon: "volume_up"
text: qsTr("Record region with sound")
activeText: qsTr("Region")
onClicked: Recorder.toggle(["-sr"])
}
]
}
}

View file

@ -9,8 +9,8 @@ Singleton {
readonly property alias running: props.running
readonly property alias paused: props.paused
function toggle(): void {
Quickshell.execDetached(["caelestia", "record"]);
function toggle(extraArgs: list<string>): void {
Quickshell.execDetached(["caelestia", "record", ...extraArgs]);
props.running = !props.running;
if (!props.running)
props.paused = false;