Commit e3ead9cb authored by Pierre Lamot's avatar Pierre Lamot
Browse files

qml: add support for client side decoration

parent 45d2df84
......@@ -686,6 +686,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 \
......
......@@ -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)
{
......
......@@ -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;
}
......
......@@ -198,7 +198,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 +208,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;
}
......
......@@ -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;
}
......
......@@ -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;
};
......
......@@ -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;
......
......@@ -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();
};
......
......@@ -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);
......
......@@ -65,13 +65,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();
}
......@@ -93,7 +95,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));
......
......@@ -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;
......
......@@ -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 {
......
......@@ -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
}
}
}
}
}
......
......@@ -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
......
......@@ -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 )
......
......@@ -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: [
......
......@@ -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)
......
......@@ -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>
......
/*****************************************************************************
* 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();
}
}
}
}
/*****************************************************************************
* 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: ""
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
}
}
}
Markdown is supported