diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-09-15 15:59:24 +1000 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-09-15 15:59:24 +1000 |
| commit | 4f54763e02dab4e33764cdd57c204368da57dd1c (patch) | |
| tree | 3f0626f2ba67b1c95b352061ab090f441c7cb694 | |
| parent | utilities/record: app2unit (diff) | |
| download | caelestia-shell-4f54763e02dab4e33764cdd57c204368da57dd1c.tar.gz caelestia-shell-4f54763e02dab4e33764cdd57c204368da57dd1c.tar.bz2 caelestia-shell-4f54763e02dab4e33764cdd57c204368da57dd1c.zip | |
utilities/record: select mode
| -rw-r--r-- | components/StateLayer.qml | 1 | ||||
| -rw-r--r-- | components/controls/IconTextButton.qml | 86 | ||||
| -rw-r--r-- | components/controls/Menu.qml | 111 | ||||
| -rw-r--r-- | components/controls/MenuItem.qml | 11 | ||||
| -rw-r--r-- | components/controls/SplitButton.qml | 141 | ||||
| -rw-r--r-- | components/controls/TextButton.qml | 12 | ||||
| -rw-r--r-- | config/UtilitiesConfig.qml | 2 | ||||
| -rw-r--r-- | modules/utilities/Content.qml | 1 | ||||
| -rw-r--r-- | modules/utilities/Wrapper.qml | 1 | ||||
| -rw-r--r-- | modules/utilities/cards/Record.qml | 40 | ||||
| -rw-r--r-- | services/Recorder.qml | 4 |
11 files changed, 392 insertions, 18 deletions
diff --git a/components/StateLayer.qml b/components/StateLayer.qml index 35acfbf..d86e782 100644 --- a/components/StateLayer.qml +++ b/components/StateLayer.qml @@ -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 { } diff --git a/components/controls/IconTextButton.qml b/components/controls/IconTextButton.qml new file mode 100644 index 0000000..ba60f3b --- /dev/null +++ b/components/controls/IconTextButton.qml @@ -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 {} + } +} diff --git a/components/controls/Menu.qml b/components/controls/Menu.qml new file mode 100644 index 0000000..7121536 --- /dev/null +++ b/components/controls/Menu.qml @@ -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 + } + } +} diff --git a/components/controls/MenuItem.qml b/components/controls/MenuItem.qml new file mode 100644 index 0000000..5348bbe --- /dev/null +++ b/components/controls/MenuItem.qml @@ -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 +} diff --git a/components/controls/SplitButton.qml b/components/controls/SplitButton.qml new file mode 100644 index 0000000..6c92285 --- /dev/null +++ b/components/controls/SplitButton.qml @@ -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 + } + } +} diff --git a/components/controls/TextButton.qml b/components/controls/TextButton.qml index 55749cb..a4c8666 100644 --- a/components/controls/TextButton.qml +++ b/components/controls/TextButton.qml @@ -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 diff --git a/config/UtilitiesConfig.qml b/config/UtilitiesConfig.qml index ca1ea0b..13d436b 100644 --- a/config/UtilitiesConfig.qml +++ b/config/UtilitiesConfig.qml @@ -6,6 +6,6 @@ JsonObject { property Sizes sizes: Sizes {} component Sizes: JsonObject { - property int width: 400 + property int width: 430 } } diff --git a/modules/utilities/Content.qml b/modules/utilities/Content.qml index ea8f93f..d5be824 100644 --- a/modules/utilities/Content.qml +++ b/modules/utilities/Content.qml @@ -23,6 +23,7 @@ Item { Record { props: root.props visibilities: root.visibilities + z: 1 } Toggles { diff --git a/modules/utilities/Wrapper.qml b/modules/utilities/Wrapper.qml index 137f0ce..3f84319 100644 --- a/modules/utilities/Wrapper.qml +++ b/modules/utilities/Wrapper.qml @@ -12,6 +12,7 @@ Item { readonly property PersistentProperties props: PersistentProperties { property bool recordingListExpanded: false property string recordingConfirmDelete + property string recordingMode reloadableId: "utilities" } diff --git a/modules/utilities/cards/Record.qml b/modules/utilities/cards/Record.qml index 001dda3..fa45cf7 100644 --- a/modules/utilities/cards/Record.qml +++ b/modules/utilities/cards/Record.qml @@ -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"]) + } + ] } } diff --git a/services/Recorder.qml b/services/Recorder.qml index 370bfd8..d5c99ca 100644 --- a/services/Recorder.qml +++ b/services/Recorder.qml @@ -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; |