diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am index daabd5e5508ec58959662bf2abce9148d22e4724..e442a0309395bf32d9c83e09d4bd771299d115de 100644 --- a/modules/gui/qt/Makefile.am +++ b/modules/gui/qt/Makefile.am @@ -1302,7 +1302,8 @@ libqml_module_widgets_a_QML = \ widgets/qml/RectangularGlow.qml \ widgets/qml/ImageExt.qml \ widgets/qml/ScrollBarExt.qml \ - widgets/qml/FastBlend.qml + widgets/qml/FastBlend.qml \ + widgets/qml/RadioButtonExt.qml if HAVE_QT65 libqml_module_widgets_a_QML += \ widgets/qml/DynamicShadow.qml \ diff --git a/modules/gui/qt/meson.build b/modules/gui/qt/meson.build index 6c6b3dbac48f32607561789852faf539a3c5e59c..fd17c66599117f8ca3e86bd73e6221f01ab69311 100644 --- a/modules/gui/qt/meson.build +++ b/modules/gui/qt/meson.build @@ -902,6 +902,7 @@ qml_modules += { 'widgets/qml/ImageExt.qml', 'widgets/qml/ScrollBarExt.qml', 'widgets/qml/FastBlend.qml', + 'widgets/qml/RadioButtonExt.qml', ), } diff --git a/modules/gui/qt/player/qml/PlaybackSpeed.qml b/modules/gui/qt/player/qml/PlaybackSpeed.qml index ff5491a2fb86cb6dcb2d7295c13ae6b36f748c8d..311426a4ff30ad2bebc93ed7b96411c22f03127e 100644 --- a/modules/gui/qt/player/qml/PlaybackSpeed.qml +++ b/modules/gui/qt/player/qml/PlaybackSpeed.qml @@ -18,6 +18,7 @@ import QtQuick import QtQuick.Controls +import QtQuick.Templates as T import QtQuick.Layouts @@ -32,16 +33,9 @@ ColumnLayout { property alias slider: slider - // Private + signal radioButtonClicked(T.AbstractButton button) - property var _model: [{ "value": 0.25 }, - { "value": 0.5 }, - { "value": 0.75 }, - { "value": 1, "title": qsTr("Normal") }, - { "value": 1.25 }, - { "value": 1.5 }, - { "value": 1.75 }, - { "value": 2 }] + // Private // NOTE: 0.96 and 1.04 are useful for video enthusiasts. property var _values: [ 0.25, 0.5, 0.75, 0.96, 1.0, 1.04, 1.25, 1.5, 1.75, 2 ] @@ -67,39 +61,19 @@ ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true + spacing: VLCStyle.margin_normal + // Events Component.onCompleted: _updateValue(Player.rate) // Function - function _updateComboBox(value) { - // NOTE: We want a rounded 1.xx value. - value = Math.round(value * 100) / 100 - - for (let i = 0; i < _model.length; i++) { - if (Helpers.compareFloat(_model[i].value, value) === false) - continue - - comboBox.currentIndex = i - - _value = value - - return; - } - - comboBox.currentIndex = -1 - - _value = value - } - function _updateValue(value) { _update = false _applySlider(value) - _updateComboBox(value) - _update = true } @@ -138,8 +112,6 @@ ColumnLayout { Player.rate = value - _updateComboBox(value) - _update = true } @@ -180,7 +152,6 @@ ColumnLayout { id: rowA Layout.fillWidth: true - Layout.topMargin: VLCStyle.margin_xsmall Layout.alignment: Qt.AlignTop @@ -214,7 +185,7 @@ ColumnLayout { id: slider Layout.fillWidth: true - Layout.topMargin: VLCStyle.margin_xsmall + topPadding: 0 // NOTE: These values come from the VLC 3.x implementation. @@ -232,7 +203,6 @@ ColumnLayout { toolTipFollowsMouse: true Navigation.parentItem: root - Navigation.downItem: comboBox Keys.priority: Keys.AfterItem Keys.onPressed: (event) => Navigation.defaultKeyAction(event) @@ -266,56 +236,48 @@ ColumnLayout { } } - RowLayout { - id: rowB - + Widgets.ListLabel { + text: qsTr("Presets:") + color: colorContext.fg.primary Layout.fillWidth: true - Layout.topMargin: VLCStyle.margin_xsmall + } - Navigation.parentItem: root - Navigation.upItem: slider + GridLayout { + Layout.fillWidth: true + Layout.fillHeight: true - Widgets.ListLabel { - text: qsTr("Presets") - color: colorContext.fg.primary - Layout.fillWidth: true - } + rows: radioButtonRepeater.count / 2 // two columns + flow: GridLayout.TopToBottom - Widgets.ComboBoxExt { - id: comboBox + rowSpacing: VLCStyle.margin_small + columnSpacing: VLCStyle.margin_small - Layout.preferredWidth: VLCStyle.combobox_width_normal - Layout.preferredHeight: VLCStyle.combobox_height_normal + ButtonGroup { + id: buttonGroup - model: ListModel {} + onClicked: function(button /* : AbstractButton */) { + Player.rate = button.modelData + root.radioButtonClicked(button) + } + } - // NOTE: We display the 'Normal' string when the Slider is centered. - displayText: (currentIndex === 3) ? currentText - : root._value + Repeater { + id: radioButtonRepeater - Navigation.parentItem: rowB + model: root._values - // NOTE: This makes the navigation possible since 'up' is changing the comboBox value. - Navigation.leftItem: slider + delegate: Widgets.RadioButtonExt { + required property double modelData - Component.onCompleted: { - for (let i = 0; i < _model.length; i++) { - const item = _model[i] + Layout.fillWidth: true - const title = item.title + text: modelData - if (title) - model.append({ "title": title }) - else - model.append({ "title": String(item.value) }) - } - } + checked: Math.abs(Player.rate - modelData) < 0.01 // need some generous epsilon here - onCurrentIndexChanged: { - if (root._update === false || currentIndex === -1) - return + padding: 0 // we use spacing instead of paddings here - root._applySlider(_model[currentIndex].value) + ButtonGroup.group: buttonGroup } } } diff --git a/modules/gui/qt/player/qml/controlbarcontrols/PlaybackSpeedButton.qml b/modules/gui/qt/player/qml/controlbarcontrols/PlaybackSpeedButton.qml index 7658e5034f81bc48fe84b6cc94e244763416ace2..04c5b858efd9600b664f49047d007da1036355a0 100644 --- a/modules/gui/qt/player/qml/controlbarcontrols/PlaybackSpeedButton.qml +++ b/modules/gui/qt/player/qml/controlbarcontrols/PlaybackSpeedButton.qml @@ -42,6 +42,10 @@ PopupIconToolButton { // NOTE: Mapping the right direction because the down action triggers the ComboBox. Navigation.rightItem: root + + onRadioButtonClicked: { + root.popup.close() + } } contentItem: Item { diff --git a/modules/gui/qt/widgets/qml/RadioButtonExt.qml b/modules/gui/qt/widgets/qml/RadioButtonExt.qml new file mode 100644 index 0000000000000000000000000000000000000000..d36f1f3aaf422cc5bd72cd23dca78eb8af4c1f9a --- /dev/null +++ b/modules/gui/qt/widgets/qml/RadioButtonExt.qml @@ -0,0 +1,79 @@ +/***************************************************************************** + * Copyright (C) 2025 VLC authors and VideoLAN + * Copyright (C) 2017 The Qt Company Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * ( at your option ) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +import QtQuick +import QtQuick.Templates as T + +import VLC.Style + +// Based on Qt Quick Basic Style: +T.RadioButton { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + + padding: VLCStyle.margin_xxsmall + spacing: VLCStyle.margin_xxsmall + + readonly property ColorContext colorContext: ColorContext { + id: theme + colorSet: ColorContext.ButtonStandard + + focused: control.visualFocus + hovered: control.hovered + enabled: control.enabled + pressed: control.down + } + + // keep in sync with RadioDelegate.qml (shared RadioIndicator.qml was removed for performance reasons) + indicator: Rectangle { + implicitWidth: control.contentItem ? implicitHeight : VLCStyle.dp(28, VLCStyle.scale) + implicitHeight: control.contentItem ? control.contentItem.implicitHeight : VLCStyle.dp(28, VLCStyle.scale) + + x: control.text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 + y: control.topPadding + (control.availableHeight - height) / 2 + + radius: width / 2 + color: control.down ? Qt.lighter(theme.accent) : theme.bg.primary + border.width: control.visualFocus ? VLCStyle.dp(2, VLCStyle.scale) : VLCStyle.dp(1, VLCStyle.scale) + border.color: control.visualFocus ? theme.accent : (theme.palette.isDark ? Qt.lighter(theme.separator) : Qt.darker(theme.separator)) + + Rectangle { + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: parent.width * 3 / 4 + height: parent.height * 3 / 4 + radius: width / 2 + color: theme.accent + visible: control.checked + } + } + + contentItem: ListLabel { + leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 + rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 + + text: control.text + color: theme.fg.primary + } +}