[vlc-commits] [Git][videolan/vlc][master] 13 commits: qml: export ButtonExt default content components

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Thu Oct 15 08:44:05 UTC 2026



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
e2ac4235 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: export ButtonExt default content components

Co-authored-by: Fatih Uzunoglu <fuzun54 at outlook.com>

- - - - -
8b2048d6 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: introduce SplitButton

A SplitButton combines a primary action button with a secondary dropdown menu

- - - - -
7f6dde7b by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: introduce OpenFileSplitButton

- - - - -
ad573422 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: use SplitButton at home page

- - - - -
cd32a41f by Leon Vitanos at 2026-10-15T10:33:32+02:00
qt: allow StringListMenu entries to have icons

Co-authored-by: Fatih Uzunoglu <fuzun54 at outlook.com>

- - - - -
0b8d2fc1 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: add icons to the OpenFileSplitButton menu

- - - - -
4dd80eb9 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qt: introduce icon ic_fluent_folder_open_24_regular

- - - - -
e18da324 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: use folder open icon on OpenFileSplitButton

- - - - -
6f8e6209 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: use custom margins when icon is used in SplitButton

- - - - -
493c278a by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: support keyboard navigation between SplitButton halves

- - - - -
98d03865 by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: show full-button or menu-half focus on SplitButton

- - - - -
40c5971e by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: add hover and pressed to SplitButton

- - - - -
96e3d1fd by Leon Vitanos at 2026-10-15T10:33:32+02:00
qml: show SplitButton tooltip only on the menu indicator

- - - - -


12 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/qml/NoMedialibHome.qml
- + modules/gui/qt/maininterface/qml/OpenFileSplitButton.qml
- modules/gui/qt/menus/qml_menu_wrapper.cpp
- modules/gui/qt/meson.build
- modules/gui/qt/pixmaps/VLCIcons.json
- modules/gui/qt/pixmaps/VLCIcons.ttf
- + modules/gui/qt/pixmaps/icons/ic_fluent_folder_open_24_regular.svg
- modules/gui/qt/style/vlcicons.hpp
- modules/gui/qt/widgets/qml/ButtonExt.qml
- + modules/gui/qt/widgets/qml/SplitButton.qml
- po/POTFILES.in


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -1063,7 +1063,8 @@ libqml_module_maininterface_a_QML = \
 	maininterface/qml/MainDisplay.qml \
 	maininterface/qml/SideNavigationPane.qml \
 	maininterface/qml/SideNavigationDelegate.qml \
-	maininterface/qml/NoMedialibHome.qml
+	maininterface/qml/NoMedialibHome.qml \
+	maininterface/qml/OpenFileSplitButton.qml
 
 if UPDATE_CHECK
 libqml_module_maininterface_a_QML += maininterface/qml/UpdatePane.qml
@@ -1356,6 +1357,7 @@ libqml_module_widgets_a_QML = \
 	widgets/qml/SearchBox.qml \
 	widgets/qml/SliderExt.qml \
 	widgets/qml/SortControl.qml \
+	widgets/qml/SplitButton.qml \
 	widgets/qml/SpinBoxExt.qml \
 	widgets/qml/StackViewExt.qml \
 	widgets/qml/PageLoader.qml \


=====================================
modules/gui/qt/maininterface/qml/NoMedialibHome.qml
=====================================
@@ -19,10 +19,8 @@ import QtQuick
 import QtQuick.Window
 
 import VLC.MainInterface
-import VLC.Widgets as Widgets
 import VLC.Util
 import VLC.Style
-import VLC.Dialogs
 
 FocusScope {
     id: root
@@ -45,9 +43,9 @@ FocusScope {
 
         property real spacing: VLCStyle.margin_large
 
-        implicitWidth: orientation === Qt.Vertical ? Math.max(cone.implicitWidth, buttons.implicitWidth)
-                                                   : cone.implicitWidth + spacing + buttons.implicitWidth
-        implicitHeight: orientation === Qt.Vertical ? cone.implicitHeight + spacing + buttons.implicitHeight
+        implicitWidth: orientation === Qt.Vertical ? Math.max(cone.implicitWidth, button.implicitWidth)
+                                                   : cone.implicitWidth + spacing + button.implicitWidth
+        implicitHeight: orientation === Qt.Vertical ? cone.implicitHeight + spacing + button.implicitHeight
                                                     : cone.implicitHeight
 
         states: [
@@ -60,7 +58,7 @@ FocusScope {
                     anchors.horizontalCenter: coneNButtons.horizontalCenter
                 }
                 AnchorChanges {
-                    target: buttons
+                    target: button
 
                     anchors.left: undefined
                     anchors.verticalCenter: undefined
@@ -68,7 +66,7 @@ FocusScope {
                     anchors.horizontalCenter: coneNButtons.horizontalCenter
                 }
                 PropertyChanges {
-                    target: buttons
+                    target: button
 
                     anchors.topMargin: coneNButtons.spacing
                 }
@@ -82,7 +80,7 @@ FocusScope {
                     anchors.left: coneNButtons.left
                 }
                 AnchorChanges {
-                    target: buttons
+                    target: button
 
                     anchors.top: undefined
                     anchors.horizontalCenter: undefined
@@ -90,7 +88,7 @@ FocusScope {
                     anchors.verticalCenter: coneNButtons.verticalCenter
                 }
                 PropertyChanges {
-                    target: buttons
+                    target: button
 
                     anchors.leftMargin: coneNButtons.spacing
                 }
@@ -106,7 +104,7 @@ FocusScope {
             property real _eDPR: MainCtx.effectiveDevicePixelRatio(Window.window)
 
             sourceSize: Qt.size(0, orientation === Qt.Vertical ? VLCStyle.colWidth(1)
-                                                               : buttons.implicitHeight * 1.618 * _eDPR) // 1.618 = golden ratio approximation
+                                                               : button.implicitHeight * 1.618 * _eDPR) // 1.618 = golden ratio approximation
 
             source: MainCtx.useXmasCone() ? "qrc:///logo/vlc48-xmas.png" // TODO: new xmas cone designs
                                           : "qrc:///misc/cone.svg"
@@ -125,50 +123,12 @@ FocusScope {
             }
         }
 
-        Row {
-            id: buttons
+        OpenFileSplitButton {
+            id: button
 
-            spacing: coneNButtons.spacing
+            focus: true
 
-            Widgets.ActionButtonPrimary {
-                id: fileButton
-
-                AccessibleCompat.id: "openFileButton"
-
-                focus: true
-
-                text: qsTr("Open File")
-
-                // NOTE: Use the same width for the buttons (give more width if necessary) to have bilateral symmetry:
-                width: Math.max(fileButton.implicitWidth, discButton.implicitWidth)
-
-                Navigation.parentItem: coneNButtons
-                Navigation.rightItem: discButton
-
-                // TODO: The full-fledged open dialog is advertised as "Open Multiple Files" in the menu.
-                //       In the future, we can have the "Open File" button as a combo box button that has these options,
-                //       with the default being a simple open dialog:
-                //       - Default: simple open dialog.
-                //       - Combo box option 1 (user clicks the down button, and the combo box reveals all buttons): open multiple files (Ctrl+Shift+O).
-                //       - Combo box option 2: open location from clipboard (Ctrl+V) / simple text edit dialog.
-                onClicked: DialogsProvider.simpleOpenDialog()
-            }
-
-            Widgets.ActionButtonPrimary {
-                id: discButton
-
-                AccessibleCompat.id: "discButton"
-
-                text: qsTr("Open Disc")
-
-                // NOTE: Use the same width for the buttons (give more width if necessary) to have bilateral symmetry:
-                width: Math.max(fileButton.implicitWidth, discButton.implicitWidth)
-
-                Navigation.parentItem: coneNButtons
-                Navigation.leftItem: fileButton
-
-                onClicked: DialogsProvider.openDiscDialog()
-            }
+            Navigation.parentItem: coneNButtons
         }
     }
 


=====================================
modules/gui/qt/maininterface/qml/OpenFileSplitButton.qml
=====================================
@@ -0,0 +1,70 @@
+/*****************************************************************************
+ * Copyright (C) 2026 VLC authors and VideoLAN
+ *
+ * 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 VLC.Widgets as Widgets
+import VLC.Style
+import VLC.Dialogs
+import VLC.Util
+
+Widgets.SplitButton {
+    id: root
+
+    text: qsTr("Open File")
+
+    iconTxt: VLCIcons.ic_fluent_folder_open_24_regular
+    iconSize: VLCStyle.fontSize_xxlarge
+
+    model: [
+        { text: qsTr("Open Multiple Files"), icon_svg: ":/menu/file.svg" },
+        { text: qsTr("Open Directory"), icon_svg: ":/menu/folder.svg" },
+        { text: qsTr("Open Disc"), icon_svg: ":/menu/disc.svg" },
+        { text: qsTr("Open Network Stream"), icon_svg: ":/menu/network.svg" },
+        { text: qsTr("Open Capture Device"), icon_svg: ":/menu/capture-card.svg" },
+        qsTr("Open Location from clipboard"),
+    ]
+
+    AccessibleCompat.id: "openFileButton"
+
+    onClicked: DialogsProvider.simpleOpenDialog()
+
+    onMenuSelected: (index) => {
+        switch (index) {
+        case 0:
+            DialogsProvider.openFileDialog()
+            break
+        case 1:
+            DialogsProvider.PLOpenDir()
+            break
+        case 2:
+            DialogsProvider.openDiscDialog()
+            break
+        case 3:
+            DialogsProvider.openNetDialog()
+            break
+        case 4:
+            DialogsProvider.openCaptureDialog()
+            break
+        case 5:
+            DialogsProvider.openUrlDialog()
+            break
+        default:
+            break
+        }
+    }
+}


=====================================
modules/gui/qt/menus/qml_menu_wrapper.cpp
=====================================
@@ -77,11 +77,33 @@ void StringListMenu::popup(const QPoint &point, const QVariantList &stringList)
 
     for (int i = 0; i != stringList.size(); ++i)
     {
-        const auto str = stringList[i].toString();
-        m->addAction(str, this, [this, i, str]()
+        const QVariant& variant = stringList[i];
+        QString str;
+        QString icon;
+
+        if (variant.typeId() == QMetaType::QString)
         {
-            emit selected(i, str);
-        });
+            str = variant.toString();
+        }
+        else
+        {
+            assert(variant.typeId() == QMetaType::QVariantMap);
+            const auto variantMap = variant.toMap();
+
+            str = variantMap[QStringLiteral("text")].toString();
+            icon = variantMap[QStringLiteral("icon_svg")].toString();
+        }
+
+        const auto action = m->addAction(str, this, [this, i, str]()
+                                         {
+                                             emit selected(i, str);
+                                         });
+
+        if (!icon.isEmpty())
+        {
+            assert(action);
+            action->setIcon(ColorizedSvgIcon::colorizedIconForWidget(icon, m));
+        }
     }
 
     m->popup(point);


=====================================
modules/gui/qt/meson.build
=====================================
@@ -641,6 +641,7 @@ qml_modules += {
         'maininterface/qml/MainViewLoader.qml',
         'maininterface/qml/MainDisplay.qml',
         'maininterface/qml/NoMedialibHome.qml',
+        'maininterface/qml/OpenFileSplitButton.qml',
         qml_maininterface_extra_files,
         'maininterface/qml/SideNavigationPane.qml',
         'maininterface/qml/SideNavigationDelegate.qml',
@@ -910,6 +911,7 @@ qml_modules += {
         'widgets/qml/NavigableRow.qml',
         'widgets/qml/PlayCover.qml',
         'widgets/qml/RoundButtonExt.qml',
+        'widgets/qml/SplitButton.qml',
         'widgets/qml/ScaledImage.qml',
         'widgets/qml/TextAutoScroller.qml',
         'widgets/qml/SearchBox.qml',


=====================================
modules/gui/qt/pixmaps/VLCIcons.json
=====================================
@@ -97,6 +97,7 @@
         {"key":"ic_fluent_document_copy_24_regular", "path": "./icons/ic_fluent_document_copy_24_regular.svg"},
         {"key":"ic_fluent_arrow_download_24_regular", "path": "./icons/ic_fluent_arrow_download_24_regular.svg"},
         {"key":"ic_fluent_arrow_sync_24_regular", "path": "./icons/ic_fluent_arrow_sync_24_regular.svg"},
+        {"key":"ic_fluent_folder_open_24_regular", "path": "./icons/ic_fluent_folder_open_24_regular.svg"},
         {"key":"medialibrary_add", "path": "./icons/ic_medialibrary_add.svg"},
         {"key":"medialibrary_remove", "path": "./icons/ic_medialibrary_remove.svg"},
         {"key":"regex", "path": "./icons/regex.svg"},


=====================================
modules/gui/qt/pixmaps/VLCIcons.ttf
=====================================
Binary files a/modules/gui/qt/pixmaps/VLCIcons.ttf and b/modules/gui/qt/pixmaps/VLCIcons.ttf differ


=====================================
modules/gui/qt/pixmaps/icons/ic_fluent_folder_open_24_regular.svg
=====================================
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M3.50014 6.25C3.50014 5.2835 4.28364 4.5 5.25014 4.5H8.12882C8.32773 4.5 8.5185 4.57902 8.65915 4.71967L10.7198 6.78033C10.8605 6.92098 11.0512 7 11.2501 7H16.7501C17.7166 7 18.5001 7.7835 18.5001 8.75C18.5001 8.83602 18.5146 8.91866 18.5413 8.99561L8.71867 8.99561C7.37893 8.99561 6.14095 9.71035 5.47108 10.8706L3.50014 14.2844V6.25ZM2.00036 17.7883C2.02089 19.5656 3.468 21 5.25014 21H11.0001C11.0292 21 11.058 20.9983 11.0862 20.9951H16.2812C17.6209 20.9951 18.8589 20.2804 19.5288 19.1201L22.5596 13.8706C23.7748 11.7657 22.3337 9.14964 19.9567 9.00214C19.9848 8.92334 20.0001 8.83846 20.0001 8.75C20.0001 6.95507 18.5451 5.5 16.7501 5.5H11.5608L9.71981 3.65901C9.29786 3.23705 8.72556 3 8.12882 3H5.25014C3.45522 3 2.00014 4.45507 2.00014 6.25V17.71C1.9999 17.7362 1.99997 17.7623 2.00036 17.7883ZM8.71867 10.4956L19.745 10.4956C21.0921 10.4956 21.9341 11.9539 21.2605 13.1206L18.2297 18.3701C17.8278 19.0663 17.085 19.4951 16.2812 19.4951H5.25485C3.9077 19.4951 3.06573 18.0368 3.7393 16.8701L6.77011 11.6206C7.17204 10.9245 7.91482 10.4956 8.71867 10.4956Z" fill="#212121"/>
+</svg>


=====================================
modules/gui/qt/style/vlcicons.hpp
=====================================
@@ -106,6 +106,7 @@ class VLCIcons : public QObject {
     Q_PROPERTY(QChar ic_fluent_document_copy_24_regular MEMBER ic_fluent_document_copy_24_regular CONSTANT FINAL)
     Q_PROPERTY(QChar ic_fluent_arrow_download_24_regular MEMBER ic_fluent_arrow_download_24_regular CONSTANT FINAL)
     Q_PROPERTY(QChar ic_fluent_arrow_sync_24_regular MEMBER ic_fluent_arrow_sync_24_regular CONSTANT FINAL)
+    Q_PROPERTY(QChar ic_fluent_folder_open_24_regular MEMBER ic_fluent_folder_open_24_regular CONSTANT FINAL)
     Q_PROPERTY(QChar medialibrary_add MEMBER medialibrary_add CONSTANT FINAL)
     Q_PROPERTY(QChar medialibrary_remove MEMBER medialibrary_remove CONSTANT FINAL)
     Q_PROPERTY(QChar regex MEMBER regex CONSTANT FINAL)
@@ -212,10 +213,11 @@ public:
     static constexpr inline QChar ic_fluent_document_copy_24_regular{ u'\ue059' };
     static constexpr inline QChar ic_fluent_arrow_download_24_regular{ u'\ue05a' };
     static constexpr inline QChar ic_fluent_arrow_sync_24_regular{ u'\ue05b' };
-    static constexpr inline QChar medialibrary_add{ u'\ue05c' };
-    static constexpr inline QChar medialibrary_remove{ u'\ue05d' };
-    static constexpr inline QChar regex{ u'\ue05e' };
-    static constexpr inline QChar ic_fluent_text_change_case_24_regular{ u'\ue05f' };
+    static constexpr inline QChar ic_fluent_folder_open_24_regular{ u'\ue05c' };
+    static constexpr inline QChar medialibrary_add{ u'\ue05d' };
+    static constexpr inline QChar medialibrary_remove{ u'\ue05e' };
+    static constexpr inline QChar regex{ u'\ue05f' };
+    static constexpr inline QChar ic_fluent_text_change_case_24_regular{ u'\ue060' };
 
 };
 


=====================================
modules/gui/qt/widgets/qml/ButtonExt.qml
=====================================
@@ -27,7 +27,7 @@ import VLC.Widgets as Widgets
 import VLC.Style
 
 T.Button {
-    id: control
+    id: root
 
     // Properties
 
@@ -53,6 +53,10 @@ T.Button {
     // Aliases
     property real iconRotation
 
+    property var onKeyPressedFunc: function (event) {
+        Navigation.defaultKeyAction(event)
+    }
+
     // Settings
 
     implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
@@ -69,16 +73,16 @@ T.Button {
 
     Keys.priority: Keys.AfterItem
 
-    Keys.onPressed: (event) => Navigation.defaultKeyAction(event)
+    Keys.onPressed: (event) => { root.onKeyPressedFunc(event) }
 
 
     // Accessible
 
-    Accessible.onPressAction: control.clicked()
+    Accessible.onPressAction: root.clicked()
 
     // Tooltip
 
-    T.ToolTip.visible: (T.ToolTip.text && (!showText || label.implicitWidth > label.width) && (hovered || visualFocus))
+    T.ToolTip.visible: (T.ToolTip.text && (!showText || (root.contentItem?.label && root.contentItem.label.implicitWidth > root.contentItem.label.width)) && (hovered || visualFocus))
 
     T.ToolTip.delay: VLCStyle.delayToolTipAppear
 
@@ -91,20 +95,20 @@ T.Button {
         id: theme
         colorSet: ColorContext.ButtonStandard
 
-        focused: control.visualFocus
-        hovered: control.hovered
-        enabled: control.enabled
-        pressed: control.down
+        focused: root.visualFocus
+        hovered: root.hovered
+        enabled: root.enabled
+        pressed: root.down
     }
 
     background: Widgets.AnimatedBackground {
-        enabled: theme.initialized && !control.extBackgroundAnimation
+        enabled: theme.initialized && !root.extBackgroundAnimation
 
         color: theme.bg.primary
-        border.color: control.visualFocus ? control.colorFocus
-                                          : (theme.border.a > 0.0 ? theme.border : color)
+        border.color: root.visualFocus ? root.colorFocus
+                                       : (theme.border.a > 0.0 ? theme.border : color)
 
-        radius: control.radius
+        radius: root.radius
 
         Rectangle {
             anchors {
@@ -116,73 +120,87 @@ T.Button {
             implicitWidth: (parent.width - VLCStyle.margin_xsmall)
             implicitHeight: VLCStyle.heightBar_xxxsmall
 
-            width: control.contentItem?.implicitWidth ?? implicitWidth
+            width: root.contentItem?.implicitWidth ?? implicitWidth
 
             radius: parent.radius
 
-            visible: (width > 0 && control.checked)
+            visible: (width > 0 && root.checked)
         }
     }
 
-    contentItem: RowLayout {
-        spacing: 0
+    component DefaultContentIconLabel : Widgets.IconLabel {
+        required property ButtonExt control
 
-        Item {
-            Layout.fillWidth: true
-        }
+        visible: text.length > 0 && control.display !== AbstractButton.TextOnly
 
-        Widgets.IconLabel {
-            id: iconLabel
+        rotation: control.iconRotation
 
-            visible: text.length > 0 && control.display !== AbstractButton.TextOnly
+        text: control.iconTxt
 
-            rotation: control.iconRotation
+        horizontalAlignment: Text.AlignHCenter
+        verticalAlignment: Text.AlignVCenter
 
-            text: control.iconTxt
+        color: control.busy ? "transparent" : control.color
 
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
+        font.pixelSize: control.iconSize
 
-            color: control.busy ? "transparent" : control.color
+        // FIXME: use `BusyIndicatorExt` when it is ready (!7180)
+        BusyIndicator {
+            anchors.fill: parent
 
-            font.pixelSize: control.iconSize
+            padding: 0
 
-            Layout.fillWidth: !label.visible
-            Layout.fillHeight: true
+            running: control.busy
 
-            // FIXME: use `BusyIndicatorExt` when it is ready (!7180)
-            BusyIndicator {
-                anchors.fill: parent
+            palette.text: control.color
+            palette.dark: control.color
+        }
+    }
 
-                padding: 0
+    component DefaultContentLabel : Label {
+        required property ButtonExt control
 
-                running: control.busy
+        visible: control.showText && control.display !== AbstractButton.IconOnly
 
-                palette.text: control.color
-                palette.dark: control.color
-            }
-        }
+        text: control.text
 
-        T.Label {
-            id: label
+        verticalAlignment: Text.AlignVCenter
 
-            visible: control.showText && control.display !== AbstractButton.IconOnly
+        color: control.color
 
-            text: control.text
+        elide: Text.ElideRight
 
-            verticalAlignment: Text.AlignVCenter
+        font.pixelSize: VLCStyle.fontSize_normal
+        font.weight: Font.DemiBold
 
-            color: control.color
+        textFormat: Text.PlainText
 
-            elide: Text.ElideRight
+        //button text is already exposed
+        Accessible.ignored: true
+    }
 
-            font.pixelSize: VLCStyle.fontSize_normal
-            font.weight: Font.DemiBold
+    contentItem: RowLayout {
+        property alias label: label
+
+        spacing: 0
+
+        Item {
+            Layout.fillWidth: true
+        }
 
-            textFormat: Text.PlainText
+        DefaultContentIconLabel {
+            id: iconLabel
+
+            control: root
+
+            Layout.fillWidth: !label.visible
+            Layout.fillHeight: true
+        }
+
+        DefaultContentLabel {
+            id: label
 
-            //button text is already exposed
-            Accessible.ignored: true
+            control: root
 
             Layout.fillWidth: true
             Layout.fillHeight: true


=====================================
modules/gui/qt/widgets/qml/SplitButton.qml
=====================================
@@ -0,0 +1,211 @@
+/*****************************************************************************
+ * Copyright (C) 2026 VLC authors and VideoLAN
+ *
+ * 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 QtQuick.Layouts
+
+import VLC.MainInterface
+import VLC.Menus
+import VLC.Widgets as Widgets
+import VLC.Style
+import VLC.Util
+
+ButtonExt {
+    id: root
+
+    property var model: []
+
+    property alias menu: _menu
+
+    signal menuSelected(int index, string text)
+
+    signal menuIndicatorClicked()
+
+    rightPadding: leftPadding + implicitIndicatorWidth
+
+    spacing: VLCStyle.margin_xxsmall
+
+    onKeyPressedFunc: function(event) {
+        if (KeyHelper.matchRight(event) && contentItem?.menuIndicatorButton) {
+            contentItem.menuIndicatorButton.forceActiveFocus(Qt.TabFocusReason)
+            event.accepted = true
+            return
+        }
+
+        Navigation.defaultKeyAction(event)
+    }
+
+    colorContext {
+        colorSet: ColorContext.ButtonAccent
+        focused: root.visualFocus
+        hovered: root.hovered && !_menu.visible && !root.contentItem?.menuIndicatorButton?.hovered
+    }
+
+    Keys.onMenuPressed: root.clickMenuIndicator()
+
+    contentItem: RowLayout {
+        property alias menuIndicatorButton: menuIndicator
+
+        property alias label: label
+
+        spacing: 0
+
+        Item {
+            Layout.fillWidth: true
+        }
+
+        ButtonExt.DefaultContentIconLabel {
+            id: iconLabel
+
+            control: root
+
+            topPadding: -VLCStyle.margin_xxsmall
+            bottomPadding: topPadding
+
+            Layout.fillWidth: !label.visible
+            Layout.fillHeight: true
+            Layout.rightMargin: label.visible ? root.spacing : 0
+        }
+
+        ButtonExt.DefaultContentLabel {
+            id: label
+
+            control: root
+
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Layout.maximumWidth: implicitWidth + 1
+        }
+
+        Item {
+            Layout.fillWidth: true
+        }
+    }
+
+    indicator: Row {
+        // It is intentional that we use content item in visible, but background item for anchoring:
+        visible: !!root.contentItem?.visible
+
+        anchors.right: root.background?.right ?? undefined
+        anchors.top: root.background?.top ?? undefined
+        anchors.bottom: root.background?.bottom ?? undefined
+
+        Rectangle {
+            anchors.top: parent.top
+            anchors.bottom: parent.bottom
+            anchors.topMargin: root.background?.border.width ?? 0
+            anchors.bottomMargin: anchors.topMargin
+
+            width: MainCtx.dp(1, VLCStyle.scale)
+
+            color: menuIndicator.visualFocus ? "transparent" : Qt.alpha(root.colorContext.fg.primary, 0.4)
+        }
+
+        T.AbstractButton {
+            id: menuIndicator
+
+            anchors.top: parent.top
+            anchors.bottom: parent.bottom
+
+            implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+                                    implicitContentWidth + leftPadding + rightPadding)
+            implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+                                     implicitContentHeight + topPadding + bottomPadding)
+
+            padding: VLCStyle.margin_xsmall
+
+            topInset: (background.border.width > 0 && background.border.color.a > 0) ? 0
+                                                                                     : (root.background?.border.width ?? 0)
+            bottomInset: topInset
+            leftInset: 0
+            rightInset: topInset
+
+            Accessible.name: qsTr("More options")
+
+            T.ToolTip.text: menuIndicator.Accessible.name
+            T.ToolTip.delay: VLCStyle.delayToolTipAppear
+            T.ToolTip.visible: menuIndicator.hovered || menuIndicator.visualFocus
+
+            Navigation.parentItem: root
+
+            Keys.priority: Keys.AfterItem
+            Keys.onPressed: (event) => {
+                if (KeyHelper.matchLeft(event)) {
+                    root.forceActiveFocus(Qt.BacktabFocusReason)
+                    event.accepted = true
+                    return
+                }
+
+                Navigation.defaultKeyAction(event)
+            }
+
+            onClicked: {
+                root.popupMenu()
+                root.menuIndicatorClicked()
+            }
+
+            contentItem: Widgets.IconLabel {
+                text: VLCIcons.chevron_down
+                font.pixelSize: VLCStyle.icon_small
+                color: root.color
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+            }
+
+            background: Widgets.AnimatedBackground {
+                implicitWidth: VLCStyle.icon_small
+                implicitHeight: VLCStyle.icon_small
+
+                enabled: menuTheme.initialized
+                color: (menuTheme.hovered || menuTheme.pressed) ? menuTheme.bg.primary
+                                                                : Qt.alpha(menuTheme.bg.primary, 0.0)
+                border.width: root.background?.border.width ?? 0
+                border.color: menuIndicator.visualFocus ? menuTheme.visualFocus
+                                                        : Qt.alpha(menuTheme.visualFocus, 0.0)
+                radius: root.radius - menuIndicator.topInset
+            }
+
+            ColorContext {
+                id: menuTheme
+
+                colorSet: ColorContext.ButtonAccent
+
+                enabled: root.enabled
+                focused: menuIndicator.visualFocus
+                hovered: _menu.visible || menuIndicator.hovered
+                pressed: menuIndicator.down
+            }
+        }
+    }
+
+    StringListMenu {
+        id: _menu
+
+        ctx: MainCtx
+
+        onSelected: (index, text) => root.menuSelected(index, text)
+    }
+
+    function clickMenuIndicator() {
+        root.contentItem?.menuIndicatorButton?.click()
+    }
+
+    function popupMenu() {
+        _menu.popup(root.mapToGlobal(0, root.height), root.model)
+    }
+}


=====================================
po/POTFILES.in
=====================================
@@ -750,6 +750,7 @@ modules/gui/qt/maininterface/qml/LocalTopbar.qml
 modules/gui/qt/maininterface/qml/MainDisplay.qml
 modules/gui/qt/maininterface/qml/MainInterface.qml
 modules/gui/qt/maininterface/qml/NoMedialibHome.qml
+modules/gui/qt/maininterface/qml/OpenFileSplitButton.qml
 modules/gui/qt/maininterface/qml/UpdatePane.qml
 modules/gui/qt/maininterface/qml/SideNavigationPane.qml
 modules/gui/qt/widgets/qml/EmptyLabelButton.qml
@@ -921,6 +922,7 @@ modules/gui/qt/widgets/qml/SortControl.qml
 modules/gui/qt/widgets/qml/TableColumns.qml
 modules/gui/qt/widgets/qml/ViewHeader.qml
 modules/gui/qt/widgets/qml/TableViewDelegateExt.qml
+modules/gui/qt/widgets/qml/SplitButton.qml
 modules/gui/skins2/commands/async_queue.cpp
 modules/gui/skins2/commands/async_queue.hpp
 modules/gui/skins2/commands/cmd_add_item.cpp



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/ff10c6cf720d73cba5ac89efda22e03c2aa610bf...96e3d1fd1e34673481567a2182f1a9c50e67e5ab

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/ff10c6cf720d73cba5ac89efda22e03c2aa610bf...96e3d1fd1e34673481567a2182f1a9c50e67e5ab
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list