[vlc-devel] [PATCH 5/7] qml: add support for client side decoration

Pierre Lamot pierre at videolabs.io
Wed Sep 30 18:21:57 CEST 2020


---
 modules/gui/qt/Makefile.am                    |  3 +
 .../gui/qt/maininterface/compositor_dcomp.cpp |  8 ++-
 .../gui/qt/maininterface/compositor_dummy.cpp | 10 ++-
 .../gui/qt/maininterface/compositor_win7.cpp  |  8 ++-
 .../interface_window_handler.cpp              | 67 +++++++++++++++++++
 .../interface_window_handler.hpp              |  8 +++
 .../gui/qt/maininterface/main_interface.cpp   | 12 ++++
 .../gui/qt/maininterface/main_interface.hpp   | 10 ++-
 .../qt/maininterface/main_interface_win32.cpp | 13 ++++
 modules/gui/qt/maininterface/mainui.cpp       |  6 +-
 modules/gui/qt/maininterface/mainui.hpp       |  3 +-
 .../qt/maininterface/qml/BannerSources.qml    | 19 ++++++
 modules/gui/qt/player/qml/ResumeDialog.qml    | 21 +++++-
 modules/gui/qt/player/qml/TopBar.qml          | 20 ++++++
 modules/gui/qt/qt.cpp                         | 14 ++++
 modules/gui/qt/style/VLCColors.qml            |  3 +
 modules/gui/qt/style/VLCStyle.qml             |  4 +-
 modules/gui/qt/vlc.qrc                        |  3 +
 .../qml/CSDTitlebarTapNDrapHandler.qml        | 46 +++++++++++++
 .../gui/qt/widgets/qml/CSDWindowButton.qml    | 59 ++++++++++++++++
 .../gui/qt/widgets/qml/CSDWindowButtonSet.qml | 62 +++++++++++++++++
 21 files changed, 389 insertions(+), 10 deletions(-)
 create mode 100644 modules/gui/qt/widgets/qml/CSDTitlebarTapNDrapHandler.qml
 create mode 100644 modules/gui/qt/widgets/qml/CSDWindowButton.qml
 create mode 100644 modules/gui/qt/widgets/qml/CSDWindowButtonSet.qml

diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index cfd24af76e..f0e3593369 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -683,6 +683,9 @@ libqt_plugin_la_QML = \
 	gui/qt/widgets/qml/CaptionLabel.qml \
 	gui/qt/widgets/qml/ComboBoxExt.qml \
 	gui/qt/widgets/qml/ContextButton.qml \
+	gui/qt/widgets/qml/CSDWindowButton.qml \
+	gui/qt/widgets/qml/CSDWindowButtonSet.qml \
+	gui/qt/widgets/qml/CSDTitlebarTapNDrapHandler.qml \
 	gui/qt/widgets/qml/DNDLabel.qml \
 	gui/qt/widgets/qml/DrawerExt.qml \
 	gui/qt/widgets/qml/ExpandGridView.qml \
diff --git a/modules/gui/qt/maininterface/compositor_dcomp.cpp b/modules/gui/qt/maininterface/compositor_dcomp.cpp
index f459480447..361746027d 100644
--- a/modules/gui/qt/maininterface/compositor_dcomp.cpp
+++ b/modules/gui/qt/maininterface/compositor_dcomp.cpp
@@ -198,6 +198,7 @@ MainInterface* CompositorDirectComposition::makeMainInterface()
         m_rootWindow->setAttribute(Qt::WA_NativeWindow);
         m_rootWindow->setAttribute(Qt::WA_DontCreateNativeAncestors);
         m_rootWindow->setAttribute(Qt::WA_TranslucentBackground);
+
         m_rootWindow->winId();
         m_rootWindow->show();
 
@@ -234,7 +235,12 @@ MainInterface* CompositorDirectComposition::makeMainInterface()
         connect(m_qmlVideoSurfaceProvider.get(), &VideoSurfaceProvider::hasVideoChanged,
                 m_interfaceWindowHandler, &InterfaceWindowHandlerWin32::onVideoEmbedChanged);
 
-        m_ui = std::make_unique<MainUI>(m_intf, m_rootWindow);
+        connect(m_rootWindow, &MainInterface::requestInterfaceMaximized,
+                m_rootWindow, &MainInterface::showMaximized);
+        connect(m_rootWindow, &MainInterface::requestInterfaceNormal,
+                m_rootWindow, &MainInterface::showNormal);
+
+        m_ui = std::make_unique<MainUI>(m_intf, m_rootWindow, m_rootWindow->windowHandle());
         ret = m_ui->setup(m_uiSurface->engine());
         if (! ret)
         {
diff --git a/modules/gui/qt/maininterface/compositor_dummy.cpp b/modules/gui/qt/maininterface/compositor_dummy.cpp
index 81e5d3f867..f972ac4790 100644
--- a/modules/gui/qt/maininterface/compositor_dummy.cpp
+++ b/modules/gui/qt/maininterface/compositor_dummy.cpp
@@ -32,17 +32,25 @@ CompositorDummy::CompositorDummy(intf_thread_t *p_intf, QObject* parent)
 MainInterface* CompositorDummy::makeMainInterface()
 {
     m_rootWindow = new MainInterface(m_intf);
+    if (m_rootWindow->useClientSideDecoration())
+        m_rootWindow->setWindowFlag(Qt::FramelessWindowHint);
     m_rootWindow->show();
     QQuickWidget* centralWidget = new QQuickWidget(m_rootWindow);
     centralWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
 
     new InterfaceWindowHandler(m_intf, m_rootWindow, m_rootWindow->windowHandle(), m_rootWindow);
 
-    MainUI* m_ui = new MainUI(m_intf, m_rootWindow, this);
+    MainUI* m_ui = new MainUI(m_intf, m_rootWindow, m_rootWindow->windowHandle(), this);
     m_ui->setup(centralWidget->engine());
     centralWidget->setContent(QUrl(), m_ui->getComponent(), m_ui->createRootItem());
 
     m_rootWindow->setCentralWidget(centralWidget);
+
+    connect(m_rootWindow, &MainInterface::requestInterfaceMaximized,
+            m_rootWindow, &MainInterface::showMaximized);
+    connect(m_rootWindow, &MainInterface::requestInterfaceNormal,
+            m_rootWindow, &MainInterface::showNormal);
+
     return m_rootWindow;
 }
 
diff --git a/modules/gui/qt/maininterface/compositor_win7.cpp b/modules/gui/qt/maininterface/compositor_win7.cpp
index dab17a6997..7349f383b9 100644
--- a/modules/gui/qt/maininterface/compositor_win7.cpp
+++ b/modules/gui/qt/maininterface/compositor_win7.cpp
@@ -174,6 +174,8 @@ MainInterface* CompositorWin7::makeMainInterface()
     m_rootWindow->setVideoSurfaceProvider(m_qmlVideoSurfaceProvider.get());
 
     m_qmlView = std::make_unique<QQuickView>();
+    if (m_rootWindow->useClientSideDecoration())
+        m_qmlView->setFlag(Qt::FramelessWindowHint);
     m_qmlView->setResizeMode(QQuickView::SizeRootObjectToView);
     m_qmlView->setClearBeforeRendering(true);
     m_qmlView->setColor(QColor(Qt::transparent));
@@ -198,7 +200,7 @@ MainInterface* CompositorWin7::makeMainInterface()
     m_taskbarWidget = new WinTaskbarWidget(m_intf, m_qmlView.get(), this);
     qApp->installNativeEventFilter(m_taskbarWidget);
 
-    MainUI* m_ui = new MainUI(m_intf, m_rootWindow, this);
+    MainUI* m_ui = new MainUI(m_intf, m_rootWindow, m_qmlView.get(), this);
     m_ui->setup(m_qmlView->engine());
 
 
@@ -208,6 +210,10 @@ MainInterface* CompositorWin7::makeMainInterface()
             m_qmlView.get(), &QQuickView::setTitle);
     connect(m_rootWindow, &MainInterface::windowIconChanged,
             m_qmlView.get(), &QQuickView::setIcon);
+    connect(m_rootWindow, &MainInterface::requestInterfaceMaximized,
+            m_qmlView.get(), &QWindow::showMaximized);
+    connect(m_rootWindow, &MainInterface::requestInterfaceNormal,
+            m_qmlView.get(), &QWindow::showNormal);
 
     return m_rootWindow;
 }
diff --git a/modules/gui/qt/maininterface/interface_window_handler.cpp b/modules/gui/qt/maininterface/interface_window_handler.cpp
index 084d0b3a45..c1a2ffc3f5 100644
--- a/modules/gui/qt/maininterface/interface_window_handler.cpp
+++ b/modules/gui/qt/maininterface/interface_window_handler.cpp
@@ -85,6 +85,60 @@ InterfaceWindowHandler::~InterfaceWindowHandler()
     WindowStateHolder::holdFullscreen( m_window,  WindowStateHolder::INTERFACE, false );
 }
 
+#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
+bool InterfaceWindowHandler::CSDSetCursor(QMouseEvent* mouseEvent)
+{
+    if (!m_mainInterface->useClientSideDecoration())
+        return false;
+    if ((m_window->visibility() & QWindow::Maximized) != 0)
+        return false;
+    Qt::CursorShape shape;
+    const int x = mouseEvent->x();
+    const int y = mouseEvent->y();
+    const int winHeight = m_window->height();
+    const int winWidth = m_window->width();
+    const int b = 5 * m_mainInterface->getIntfScaleFactor();
+
+    if (x < b && y < b) shape = Qt::SizeFDiagCursor;
+    else if (x >= winWidth - b && y >= winHeight - b) shape = Qt::SizeFDiagCursor;
+    else if (x >= winWidth - b && y < b) shape = Qt::SizeBDiagCursor;
+    else if (x < b && y >= winHeight - b) shape = Qt::SizeBDiagCursor;
+    else if (x < b || x >= winWidth - b) shape = Qt::SizeHorCursor;
+    else if (y < b || y >= winHeight - b) shape = Qt::SizeVerCursor;
+    else if (m_hasResizeCursor) {
+        m_window->unsetCursor();
+        m_hasResizeCursor = false;
+        return false;
+    } else {
+        return false;
+    }
+    m_hasResizeCursor = true;
+    m_window->setCursor(shape);
+    return false;
+}
+
+bool InterfaceWindowHandler::CSDHandleClick(QMouseEvent* mouseEvent)
+{
+    if (!m_mainInterface->useClientSideDecoration())
+        return false;
+    const int b = 5 * m_mainInterface->getIntfScaleFactor();
+    if( mouseEvent->buttons() != Qt::LeftButton)
+        return false;
+    if ((m_window->visibility() & QWindow::Maximized) != 0)
+        return false;
+    Qt::Edges edge;
+    if (mouseEvent->x() < b) { edge |= Qt::LeftEdge; }
+    if (mouseEvent->x() > m_window->width() - b) { edge |= Qt::RightEdge; }
+    if (mouseEvent->y() < b) { edge |= Qt::TopEdge; }
+    if (mouseEvent->y() > m_window->height() - b) { edge |= Qt::BottomEdge; }
+    if (edge != 0) {
+        m_window->startSystemResize(edge);
+        return true;
+    }
+    return false;
+}
+#endif
+
 bool InterfaceWindowHandler::eventFilter(QObject*, QEvent* event)
 {
     switch ( event->type() )
@@ -148,6 +202,19 @@ bool InterfaceWindowHandler::eventFilter(QObject*, QEvent* event)
         }
         break;
     }
+#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
+    //Handle CSD edge behaviors
+    case QEvent::MouseMove:
+    {
+        QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
+        return CSDSetCursor(mouseEvent);
+    }
+    case QEvent::MouseButtonPress:
+    {
+        QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
+        return CSDHandleClick(mouseEvent);
+    }
+#endif
     default:
         break;
     }
diff --git a/modules/gui/qt/maininterface/interface_window_handler.hpp b/modules/gui/qt/maininterface/interface_window_handler.hpp
index e3f0a0255a..eb5724a9f8 100644
--- a/modules/gui/qt/maininterface/interface_window_handler.hpp
+++ b/modules/gui/qt/maininterface/interface_window_handler.hpp
@@ -54,6 +54,12 @@ signals:
     void interfaceFullScreenChanged(bool);
     void incrementIntfUserScaleFactor(bool increment);
 
+private:
+#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
+    bool CSDSetCursor(QMouseEvent* mouseEvent);
+    bool CSDHandleClick(QMouseEvent* mouseEvent);
+#endif
+
 protected:
     intf_thread_t* p_intf = nullptr;
     QWindow* m_window = nullptr;
@@ -67,6 +73,8 @@ protected:
     bool m_maximizedView = false;
     bool m_hideAfterCreation  = false; // --qt-start-minimized
 
+    bool m_hasResizeCursor = false;
+
     QRect m_interfaceGeometry;
 };
 
diff --git a/modules/gui/qt/maininterface/main_interface.cpp b/modules/gui/qt/maininterface/main_interface.cpp
index 4472b6754d..b6f394fc2f 100644
--- a/modules/gui/qt/maininterface/main_interface.cpp
+++ b/modules/gui/qt/maininterface/main_interface.cpp
@@ -158,6 +158,10 @@ MainInterface::MainInterface(intf_thread_t *_p_intf , QWidget* parent, Qt::Windo
     /* Should the UI stays on top of other windows */
     b_interfaceOnTop = var_InheritBool( p_intf, "video-on-top" );
 
+#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
+    m_clientSideDecoration = ! var_InheritBool( p_intf, "qt-titlebar" );
+#endif
+
     QString platformName = QGuiApplication::platformName();
 
 #ifdef QT5_HAS_WAYLAND
@@ -195,6 +199,8 @@ MainInterface::MainInterface(intf_thread_t *_p_intf , QWidget* parent, Qt::Windo
     /* VideoWidget connects for asynchronous calls */
     connect( this, &MainInterface::askToQuit, THEDP, &DialogsProvider::quit, Qt::QueuedConnection  );
 
+    connect(this, &MainInterface::interfaceFullScreenChanged, this, &MainInterface::useClientSideDecorationChanged);
+
     connect( THEDP, &DialogsProvider::toolBarConfUpdated, this, &MainInterface::toolBarConfUpdated );
 
     /** END of CONNECTS**/
@@ -257,6 +263,12 @@ MainInterface::~MainInterface()
     p_intf->p_sys->p_mi = NULL;
 }
 
+bool MainInterface::useClientSideDecoration() const
+{
+    //don't show CSD when interface is fullscreen
+    return m_clientSideDecoration && !b_interfaceFullScreen;
+}
+
 void MainInterface::computeMinimumSize()
 {
     int minWidth = 450;
diff --git a/modules/gui/qt/maininterface/main_interface.hpp b/modules/gui/qt/maininterface/main_interface.hpp
index 43acc95233..8ac0e435e7 100644
--- a/modules/gui/qt/maininterface/main_interface.hpp
+++ b/modules/gui/qt/maininterface/main_interface.hpp
@@ -152,6 +152,7 @@ class MainInterface : public QVLCMW
     Q_PROPERTY(bool mediaLibraryAvailable READ hasMediaLibrary CONSTANT)
     Q_PROPERTY(bool gridView READ hasGridView WRITE setGridView NOTIFY gridViewChanged)
     Q_PROPERTY(ColorSchemeModel* colorScheme READ getColorScheme CONSTANT)
+    Q_PROPERTY(bool clientSideDecoration READ useClientSideDecoration NOTIFY useClientSideDecorationChanged)
 
 public:
     /* tors */
@@ -188,7 +189,7 @@ public:
     inline bool hasMediaLibrary() const { return b_hasMedialibrary; }
     inline bool hasGridView() const { return m_gridView; }
     inline ColorSchemeModel* getColorScheme() const { return m_colorScheme; }
-
+    bool useClientSideDecoration() const;
 
     bool hasEmbededVideo() const;
     VideoSurfaceProvider* getVideoSurfaceProvider() const;
@@ -252,6 +253,7 @@ protected:
     bool                 b_hasMedialibrary;
     bool                 m_gridView;
     ColorSchemeModel*    m_colorScheme;
+    bool                 m_clientSideDecoration = false;
 
     /* States */
     bool                 playlistVisible;       ///< Is the playlist visible ?
@@ -316,6 +318,12 @@ signals:
     void showRemainingTimeChanged(bool);
     void gridViewChanged( bool );
     void colorSchemeChanged( QString );
+    void useClientSideDecorationChanged();
+
+    /// forward window maximise query to the actual window or widget
+    void requestInterfaceMaximized();
+    /// forward window normal query to the actual window or widget
+    void requestInterfaceNormal();
 
     void intfScaleFactorChanged();
 };
diff --git a/modules/gui/qt/maininterface/main_interface_win32.cpp b/modules/gui/qt/maininterface/main_interface_win32.cpp
index a4c47cb9cc..edfe98243d 100644
--- a/modules/gui/qt/maininterface/main_interface_win32.cpp
+++ b/modules/gui/qt/maininterface/main_interface_win32.cpp
@@ -340,6 +340,19 @@ bool MainInterfaceWin32::nativeEvent(const QByteArray &eventType, void *message,
     short cmd;
     switch( msg->message )
     {
+#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
+        case WM_NCCALCSIZE:
+        {
+            /* This is used to remove the decoration instead of using FramelessWindowHint because
+             * frameless window don't support areo snapping
+             */
+            if (useClientSideDecoration()) {
+                *result = 0;
+                return true;
+            }
+            break;
+        }
+#endif
         case WM_APPCOMMAND:
             cmd = GET_APPCOMMAND_LPARAM(msg->lParam);
 
diff --git a/modules/gui/qt/maininterface/mainui.cpp b/modules/gui/qt/maininterface/mainui.cpp
index c2d5460126..20d5d5a310 100644
--- a/modules/gui/qt/maininterface/mainui.cpp
+++ b/modules/gui/qt/maininterface/mainui.cpp
@@ -64,13 +64,15 @@ void registerAnonymousType( const char *uri, int versionMajor )
 } // anonymous namespace
 
 
-MainUI::MainUI(intf_thread_t *p_intf, MainInterface *mainInterface,  QObject *parent)
+MainUI::MainUI(intf_thread_t *p_intf, MainInterface *mainInterface, QWindow* interfaceWindow,  QObject *parent)
     : QObject(parent)
     , m_intf(p_intf)
     , m_mainInterface(mainInterface)
+    , m_interfaceWindow(interfaceWindow)
 {
     assert(m_intf);
     assert(m_mainInterface);
+    assert(m_interfaceWindow);
 
     registerQMLTypes();
 }
@@ -92,7 +94,7 @@ bool MainUI::setup(QQmlEngine* engine)
     rootCtx->setContextProperty( "i18n", new I18n(this) );
     rootCtx->setContextProperty( "mainctx", new QmlMainContext(m_intf, m_mainInterface, this));
     rootCtx->setContextProperty( "mainInterface", m_mainInterface);
-    rootCtx->setContextProperty( "topWindow", m_mainInterface->windowHandle());
+    rootCtx->setContextProperty( "topWindow", m_interfaceWindow);
     rootCtx->setContextProperty( "dialogProvider", DialogsProvider::getInstance());
     rootCtx->setContextProperty( "recentsMedias",  new VLCRecentMediaModel( m_intf, this ));
     rootCtx->setContextProperty( "systemPalette", new SystemPalette(this));
diff --git a/modules/gui/qt/maininterface/mainui.hpp b/modules/gui/qt/maininterface/mainui.hpp
index 08807d6550..300faaf551 100644
--- a/modules/gui/qt/maininterface/mainui.hpp
+++ b/modules/gui/qt/maininterface/mainui.hpp
@@ -19,7 +19,7 @@ class MainUI : public QObject
     Q_OBJECT
 
 public:
-    explicit MainUI(intf_thread_t *_p_intf, MainInterface* mainInterface, QObject *parent = nullptr);
+    explicit MainUI(intf_thread_t *_p_intf, MainInterface* mainInterface, QWindow* interfaceWindow, QObject *parent = nullptr);
     ~MainUI();
 
     bool setup(QQmlEngine* engine);
@@ -36,6 +36,7 @@ private:
 
     intf_thread_t* m_intf = nullptr;
     MainInterface* m_mainInterface = nullptr;
+    QWindow*       m_interfaceWindow = nullptr;
 
     QQmlComponent* m_component = nullptr;
     QQuickItem* m_rootItem = nullptr;
diff --git a/modules/gui/qt/maininterface/qml/BannerSources.qml b/modules/gui/qt/maininterface/qml/BannerSources.qml
index 95855a782a..f5c7fe3ae0 100644
--- a/modules/gui/qt/maininterface/qml/BannerSources.qml
+++ b/modules/gui/qt/maininterface/qml/BannerSources.qml
@@ -105,6 +105,13 @@ Widgets.NavigableFocusScope {
                 width: parent.width
                 height: VLCStyle.globalToolbar_height
 
+                //drag and dbl click the titlebar in CSD mode
+                Loader {
+                    anchors.fill: parent
+                    active: mainInterface.clientSideDecoration
+                    source: "qrc:///widgets/CSDTitlebarTapNDrapHandler.qml"
+                }
+
                 RowLayout {
                     anchors.verticalCenter: parent.verticalCenter
                     anchors.left: parent.left
@@ -153,6 +160,18 @@ Widgets.NavigableFocusScope {
                         height: globalMenuGroup.height
                     }
                 }
+
+                Loader {
+                    id: globalToolbarRight
+                    anchors {
+                        top: parent.top
+                        bottom: parent.bottom
+                        right: parent.right
+                        rightMargin: VLCStyle.applicationHorizontalMargin
+                    }
+                    active: mainInterface.clientSideDecoration
+                    source: "qrc:///widgets/CSDWindowButtonSet.qml"
+                }
             }
 
             Rectangle {
diff --git a/modules/gui/qt/player/qml/ResumeDialog.qml b/modules/gui/qt/player/qml/ResumeDialog.qml
index 52ee217d82..887a20da2c 100644
--- a/modules/gui/qt/player/qml/ResumeDialog.qml
+++ b/modules/gui/qt/player/qml/ResumeDialog.qml
@@ -85,12 +85,19 @@ Widgets.NavigableFocusScope {
         anchors.fill: parent
         color: VLCStyle.colors.setColorAlpha(VLCStyle.colors.playerBg, 0.8)
 
+        //drag and dbl click the titlebar in CSD mode
+        Loader {
+            anchors.fill: parent
+            active: mainInterface.clientSideDecoration
+            source: "qrc:///widgets/CSDTitlebarTapNDrapHandler.qml"
+        }
+
         RowLayout {
             id: layout
             anchors.fill: parent
             anchors.topMargin: VLCStyle.applicationVerticalMargin
             anchors.leftMargin: VLCStyle.applicationHorizontalMargin + VLCStyle.margin_small
-            anchors.rightMargin: VLCStyle.applicationHorizontalMargin + VLCStyle.margin_small
+            anchors.rightMargin: VLCStyle.applicationHorizontalMargin
 
             spacing: VLCStyle.margin_small
 
@@ -134,6 +141,18 @@ Widgets.NavigableFocusScope {
             Item {
                 Layout.fillWidth: true
             }
+
+            Loader {
+                Layout.alignment: Qt.AlignRight | Qt.AlignTop
+                height: VLCStyle.icon_normal
+                active: mainInterface.clientSideDecoration
+                enabled: mainInterface.clientSideDecoration
+                source: "qrc:///widgets/CSDWindowButtonSet.qml"
+                onLoaded: {
+                    item.color = VLCStyle.colors.playerFg
+                    item.hoverColor = VLCStyle.colors.windowCSDButtonDarkBg
+                }
+            }
         }
     }
 }
diff --git a/modules/gui/qt/player/qml/TopBar.qml b/modules/gui/qt/player/qml/TopBar.qml
index 8da792d0df..f7802ff832 100644
--- a/modules/gui/qt/player/qml/TopBar.qml
+++ b/modules/gui/qt/player/qml/TopBar.qml
@@ -65,6 +65,13 @@ Widgets.NavigableFocusScope{
             anchors.rightMargin: VLCStyle.applicationHorizontalMargin
             implicitHeight: rowLayout.implicitHeight
 
+            //drag and dbl click the titlebar in CSD mode
+            Loader {
+                anchors.fill: parent
+                active: mainInterface.clientSideDecoration
+                source: "qrc:///widgets/CSDTitlebarTapNDrapHandler.qml"
+            }
+
             RowLayout {
                 id: rowLayout
                 anchors.fill: parent
@@ -132,6 +139,19 @@ Widgets.NavigableFocusScope{
 
                     spacing: VLCStyle.margin_xsmall
 
+                    Loader {
+                        //Layout.alignment: Qt.AlignRight | Qt.AlignTop
+                        anchors.right: parent.right
+                        height: VLCStyle.icon_normal
+                        active: mainInterface.clientSideDecoration
+                        enabled: mainInterface.clientSideDecoration
+                        source: "qrc:///widgets/CSDWindowButtonSet.qml"
+                        onLoaded: {
+                            item.color = VLCStyle.colors.playerFg
+                            item.hoverColor = VLCStyle.colors.windowCSDButtonDarkBg
+                        }
+                    }
+
                     Row {
                         //Layout.alignment: Qt.AlignRight | Qt.AlignTop
                         anchors.right: parent.right
diff --git a/modules/gui/qt/qt.cpp b/modules/gui/qt/qt.cpp
index c05df6e2fa..f51f11d1ae 100644
--- a/modules/gui/qt/qt.cpp
+++ b/modules/gui/qt/qt.cpp
@@ -234,6 +234,10 @@ static void ShowDialog   ( intf_thread_t *, int, int, intf_dialog_args_t * );
 #define AUTORAISE_ON_PLAYBACK_LONGTEXT N_( "This option allows the interface to be raised automatically " \
     "when a video/audio playback starts, or never." )
 
+#define QT_CLIENT_SIDE_DECORATION_TEXT N_( "Enable window titlebar" )
+#define QT_CLIENT_SIDE_DECORATION_LONGTEXT N_( "This option enables the title bar. Disabling it will remove " \
+    "the titlebar and move window buttons within the interface (Client Side Decoration)" )
+
 #define FULLSCREEN_CONTROL_PIXELS N_( "Fullscreen controller mouse sensitivity" )
 
 #define CONTINUE_PLAYBACK_TEXT N_("Continue playback?")
@@ -328,6 +332,16 @@ vlc_module_begin ()
               false                                /* advanced mode only */)
 #endif
 
+#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
+    add_bool( "qt-titlebar",
+#ifdef _WIN32
+              false                              /* use CSD by default on windows */,
+#else
+              true                               /* but not on linux */,
+#endif
+              QT_CLIENT_SIDE_DECORATION_TEXT, QT_CLIENT_SIDE_DECORATION_LONGTEXT, false )
+#endif
+
     add_bool( "qt-embedded-open", false, QT_NATIVEOPEN_TEXT,
                QT_NATIVEOPEN_TEXT, false )
 
diff --git a/modules/gui/qt/style/VLCColors.qml b/modules/gui/qt/style/VLCColors.qml
index efaa276fe4..19094ad519 100644
--- a/modules/gui/qt/style/VLCColors.qml
+++ b/modules/gui/qt/style/VLCColors.qml
@@ -112,6 +112,9 @@ Item {
         mainInterface.colorScheme.setAvailableColorSchemes(["system", "day", "night"])
     }
 
+    property color windowCSDButtonDarkBg:  "#80484848"
+    property color windowCSDButtonLightBg: "#80DADADA"
+    property color windowCSDButtonBg: isThemeDark ? windowCSDButtonDarkBg : windowCSDButtonLightBg
 
     state: mainInterface.colorScheme.current
     states: [
diff --git a/modules/gui/qt/style/VLCStyle.qml b/modules/gui/qt/style/VLCStyle.qml
index 590bba9d68..df3a3c072e 100644
--- a/modules/gui/qt/style/VLCStyle.qml
+++ b/modules/gui/qt/style/VLCStyle.qml
@@ -180,8 +180,8 @@ Item {
     property int appHeight: 0
 
     //global application margin "safe area"
-    property int applicationHorizontalMargin: 0
-    property int applicationVerticalMargin: 0
+    property int applicationHorizontalMargin: mainInterface.clientSideDecoration ? dp(5, scale) : 0
+    property int applicationVerticalMargin: mainInterface.clientSideDecoration ? dp(5, scale) : 0
 
     property int globalToolbar_height: dp(32, scale)
     property int localToolbar_height: dp(40, scale)
diff --git a/modules/gui/qt/vlc.qrc b/modules/gui/qt/vlc.qrc
index 8c495e9c55..fcf9c86592 100644
--- a/modules/gui/qt/vlc.qrc
+++ b/modules/gui/qt/vlc.qrc
@@ -180,6 +180,9 @@
     <qresource prefix="/widgets">
         <file alias="BannerTabButton.qml">widgets/qml/BannerTabButton.qml</file>
         <file alias="BusyIndicatorExt.qml">widgets/qml/BusyIndicatorExt.qml</file>
+        <file alias="CSDWindowButton.qml">widgets/qml/CSDWindowButton.qml</file>
+        <file alias="CSDWindowButtonSet.qml">widgets/qml/CSDWindowButtonSet.qml</file>
+        <file alias="CSDTitlebarTapNDrapHandler.qml">widgets/qml/CSDTitlebarTapNDrapHandler.qml</file>
         <file alias="GridItem.qml">widgets/qml/GridItem.qml</file>
         <file alias="ListItem.qml">widgets/qml/ListItem.qml</file>
         <file alias="DrawerExt.qml">widgets/qml/DrawerExt.qml</file>
diff --git a/modules/gui/qt/widgets/qml/CSDTitlebarTapNDrapHandler.qml b/modules/gui/qt/widgets/qml/CSDTitlebarTapNDrapHandler.qml
new file mode 100644
index 0000000000..392d7619e9
--- /dev/null
+++ b/modules/gui/qt/widgets/qml/CSDTitlebarTapNDrapHandler.qml
@@ -0,0 +1,46 @@
+/*****************************************************************************
+ * Copyright (C) 2020 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.
+ *****************************************************************************/
+
+//CSD is only supported on Qt 5.15
+import QtQuick 2.15
+import QtQuick.Window 2.15
+
+Item {
+    TapHandler {
+        onTapped: {
+            if (tapCount === 2) {
+                if ((topWindow.visibility & Window.Maximized) !== 0) {
+                    mainInterface.requestInterfaceNormal()
+                } else {
+                    mainInterface.requestInterfaceMaximized()()
+                }
+            }
+        }
+        gesturePolicy: TapHandler.DragThreshold
+    }
+    DragHandler {
+        target: null
+        grabPermissions: TapHandler.CanTakeOverFromAnything
+        onActiveChanged: {
+            if (active) {
+                topWindow.startSystemMove();
+            }
+        }
+    }
+
+}
diff --git a/modules/gui/qt/widgets/qml/CSDWindowButton.qml b/modules/gui/qt/widgets/qml/CSDWindowButton.qml
new file mode 100644
index 0000000000..93fd287839
--- /dev/null
+++ b/modules/gui/qt/widgets/qml/CSDWindowButton.qml
@@ -0,0 +1,59 @@
+/*****************************************************************************
+ * Copyright (C) 2020 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 2.11
+import QtQuick.Controls 2.4
+import QtQuick.Templates 2.4 as T
+import QtQuick.Layouts 1.11
+
+import "qrc:///style/"
+
+
+T.TabButton {
+    id: control
+
+    property color color: VLCStyle.colors.text
+    property color hoverColor: VLCStyle.colors.windowCSDButtonBg
+    property string iconTxt: ""
+
+    text: model.displayText
+    padding: 0
+    width: VLCStyle.dp(40, VLCStyle.scale)
+    implicitWidth: contentItem.implicitWidth
+    implicitHeight: contentItem.implicitHeight
+
+
+    background: Rectangle {
+        height: control.height
+        width: control.width
+        color: !control.hovered ? "transparent"
+               : control.pressed ? (VLCStyle.isThemeDark ? Qt.lighter(control.hoverColor, 1.2)
+                                      : Qt.darker(control.hoverColor, 1.2)
+                                    )
+               : control.hoverColor
+    }
+
+    contentItem: Item {
+        IconLabel {
+            id: icon
+            anchors.centerIn: parent
+            text: control.iconTxt
+            font.pixelSize: VLCIcons.pixelSize(VLCStyle.dp(20, VLCStyle.scale))
+            color: control.color
+        }
+    }
+}
diff --git a/modules/gui/qt/widgets/qml/CSDWindowButtonSet.qml b/modules/gui/qt/widgets/qml/CSDWindowButtonSet.qml
new file mode 100644
index 0000000000..ee15576821
--- /dev/null
+++ b/modules/gui/qt/widgets/qml/CSDWindowButtonSet.qml
@@ -0,0 +1,62 @@
+/*****************************************************************************
+ * Copyright (C) 2020 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 2.11
+import QtQuick.Window 2.11
+
+import "qrc:///style/"
+
+Row {
+    id: windowButtonGroup
+
+    spacing: 0
+    padding: 0
+
+    property color color: VLCStyle.colors.text
+    property color hoverColor: VLCStyle.colors.windowCSDButtonBg
+
+    CSDWindowButton {
+        iconTxt: VLCIcons.window_minimize
+        onClicked: topWindow.showMinimized()
+        height: windowButtonGroup.height
+        color: windowButtonGroup.color
+        hoverColor: windowButtonGroup.hoverColor
+    }
+
+    CSDWindowButton {
+        iconTxt: (topWindow.visibility & Window.Maximized)  ? VLCIcons.window_restore :VLCIcons.window_maximize
+        onClicked: {
+            if (topWindow.visibility & Window.Maximized) {
+                mainInterface.requestInterfaceNormal()
+            } else {
+                mainInterface.requestInterfaceMaximized()
+            }
+        }
+        height: windowButtonGroup.height
+        color: windowButtonGroup.color
+        hoverColor: windowButtonGroup.hoverColor
+    }
+
+    CSDWindowButton {
+        id: closeButton
+        iconTxt: VLCIcons.window_close
+        onClicked: topWindow.close()
+        height: windowButtonGroup.height
+        color: closeButton.hovered ? "white" : windowButtonGroup.color
+        hoverColor: "red"
+    }
+}
-- 
2.25.1



More information about the vlc-devel mailing list