Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ know it from Visual Studio.
- [Windows](#windows)
- [macOS](#macos)
- [Linux](#linux)
- [Wayland](#wayland)
- [Build](#build)
- [Qt5 on Ubuntu 18.04 or 20.04](#qt5-on-ubuntu-1804-or-2004)
- [Qt5 on Ubuntu 22.04](#qt5-on-ubuntu-2204)
Expand Down Expand Up @@ -408,19 +409,35 @@ the library switches to `QWidget` based title bars.

- **Kubuntu 18.04 and 19.10** - uses KWin - no native title bars
- **Ubuntu 18.04, 19.10 and 20.04** - native title bars are supported
- **Ubuntu 22.04** - uses Wayland -> no native title bars
- **Ubuntu 22.04 and later** - uses Wayland -> native title bars (see [Wayland](#wayland) below)

There are some requirements for the Linux distribution that have to be met:

- an X server that supports ARGB visuals and a compositing window manager. This is required to display the translucent dock overlays ([https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows](https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows)). If your Linux distribution does not support this, or if you disable this feature, you will very likely see issue [#95](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/95).
- Wayland is not properly supported by Qt yet. If you use Wayland, then you should set the session type to x11: `XDG_SESSION_TYPE=x11 ./AdvancedDockingSystemDemo`. You will find more details about this in issue [#288](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/288).
- an X server that supports ARGB visuals and a compositing window manager. This is required to display the translucent dock overlays ([https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows](https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows)). If your Linux distribution does not support this, or if you disable this feature, you will very likely see issue [#95](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/95). On Wayland the dock overlays are rendered as child widgets and this requirement does not apply.

Screenshot Kubuntu:
![Advanced Docking on Kubuntu Linux](doc/linux_kubuntu_1804.png)

Screenshot Ubuntu:
![Advanced Docking on Ubuntu Linux](doc/linux_ubuntu_1910.png)

#### Wayland

Since Qt 6.6.3 docking is supported on Wayland. Earlier Qt versions do not implement the `xdg_toplevel_drag_v1` protocol that is required to drag a floating window with the cursor, so on those versions you should still set the session type to X11 (XWayland): `XDG_SESSION_TYPE=x11 ./AdvancedDockingSystemDemo`. You will find more details in issues [#288](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/288) and [#714](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/714).

Wayland does not allow a client to move its own top level windows in screen coordinates or to query the global cursor position, so docking is implemented differently than on the other platforms. This results in a few behavioral differences on Wayland:

- Floating dock containers always use native window decorations (the custom
`QWidget` title bar is not available), because the custom title bar cannot
move the window.
- Undocking and re-docking use a compositor driven drag (the floating window
itself follows the cursor) instead of the translucent drag preview.
- A floating window's stylesheet is taken from the dock manager when the window
is created. Changing the application stylesheet while a window is already
floating does not update that window.
- The compositor controls the window stacking, so floating windows are not
forced to stay on top of the main window.

## Build

The Linux build requires private header files. Make sure that they are installed.
Expand Down
44 changes: 40 additions & 4 deletions src/DockAreaTitleBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,13 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset,
{
QSize Size = DockArea->size();
this->DragState = DragState;
bool CreateFloatingDockContainer = (DraggingFloatingWidget != DragState);
// On Wayland, the mouse tracked drag preview cannot work because the
// global cursor position is not available. Instead, we always create a
// real floating widget and hand it to a compositor driven platform drag
bool PlatformDrag = internal::isWayland()
&& (DraggingFloatingWidget == DragState);
bool CreateFloatingDockContainer = (DraggingFloatingWidget != DragState)
|| PlatformDrag;
CFloatingDockContainer* FloatingDockContainer = nullptr;
IFloatingWidget* FloatingWidget;
if (CreateFloatingDockContainer)
Expand All @@ -312,7 +318,8 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset,
FloatingWidget = w;
}

FloatingWidget->startFloating(Offset, Size, DragState, nullptr);
FloatingWidget->startFloating(Offset, Size,
PlatformDrag ? DraggingInactive : DragState, nullptr);
if (FloatingDockContainer)
{
auto TopLevelDockWidget = FloatingDockContainer->topLevelDockWidget();
Expand All @@ -322,6 +329,15 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset,
}
}

// The platform drag blocks until the drag finishes and the floating
// widget is deleted, if it was docked into a drop area
if (PlatformDrag)
{
this->DragState = DraggingInactive;
CFloatingDockContainer::startPlatformDrag(FloatingDockContainer,
_this->mapToGlobal(Offset), _this);
}

return FloatingWidget;
}

Expand All @@ -334,7 +350,12 @@ void DockAreaTitleBarPrivate::startFloating(const QPoint& Offset)
DockArea->autoHideDockContainer()->hide();
}
FloatingWidget = makeAreaFloating(Offset, DraggingFloatingWidget);
qApp->postEvent(DockArea, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent));
// On Wayland, makeAreaFloating() blocks until the platform drag has
// finished, so there is no drag to report anymore
if (!internal::isWayland())
{
qApp->postEvent(DockArea, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent));
}
}


Expand Down Expand Up @@ -699,9 +720,24 @@ void CDockAreaTitleBar::mouseMoveEvent(QMouseEvent* ev)
// sense to move it to a new floating widget and leave this one
// empty
if (d->DockArea->dockContainer()->isFloating()
&& d->DockArea->dockContainer()->visibleDockAreaCount() == 1
&& d->DockArea->dockContainer()->visibleDockAreaCount() == 1
&& !d->DockArea->isAutoHide())
{
// On Wayland, dragging the title bar of the last dock area of a
// floating widget drags the existing floating widget, so the user
// can dock it into another container
int DragDistanceWayland = (d->DragStartMousePos - ev->pos()).manhattanLength();
if (internal::isWayland()
&& DragDistanceWayland >= CDockManager::startDragDistance())
{
auto FloatingContainer = d->DockArea->dockContainer()->floatingWidget();
if (FloatingContainer)
{
d->DragState = DraggingInactive;
CFloatingDockContainer::startPlatformDrag(FloatingContainer,
mapToGlobal(d->DragStartMousePos), this);
}
}
return;
}

Expand Down
7 changes: 6 additions & 1 deletion src/DockAreaWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,12 @@ void CDockAreaWidget::updateTitleBarVisibility()
if (!CDockManager::testConfigFlag(CDockManager::AlwaysShowTabs))
{
bool Hidden = false;
if (!IsAutoHide) // Titlebar must always be visible when auto hidden so it can be dragged
// Wayland: the title bar of a floating container must always stay
// visible because it is the only handle that can drag the dock
// widget back into a dock container. The window decoration is owned
// by the compositor and only moves the window
bool IsWaylandFloating = internal::isWayland() && Container->isFloating();
if (!IsAutoHide && !IsWaylandFloating) // Titlebar must always be visible when auto hidden so it can be dragged
{
if (Container->isFloating() || CDockManager::testConfigFlag(CDockManager::HideSingleCentralWidgetTitleBar))
{
Expand Down
193 changes: 191 additions & 2 deletions src/DockContainerWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <QTimer>
#include <QMetaObject>
#include <QMetaType>
#include <QMimeData>
#include <QApplication>

#include "DockManager.h"
Expand Down Expand Up @@ -186,6 +187,13 @@ class DockContainerWidgetPrivate
*/
void dropIntoAutoHideSideBar(CFloatingDockContainer* FloatingWidget, DockWidgetArea area);

/**
* Shows the drop overlays for the given floating widget that is dragged
* over this container by a drag and drop operation (Wayland platform
* drag)
*/
void updateDropOverlays(const QPoint& GlobalPos, CFloatingDockContainer* FloatingWidget);

/**
* Creates a new tab for a widget dropped into the center of a section
*/
Expand Down Expand Up @@ -521,6 +529,80 @@ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* Float
}


//============================================================================
void DockContainerWidgetPrivate::updateDropOverlays(const QPoint& GlobalPos,
CFloatingDockContainer* FloatingWidget)
{
if (!DockManager)
{
return;
}

// This container received the drag move event, so it is the drop target -
// unlike the mouse tracked dragging, there is no need to search the
// container under the cursor
bool ContentPinnable = FloatingWidget->dockContainer()->features().testFlag(
CDockWidget::DockWidgetPinnable);
CDockContainerWidget::showDropOverlays(DockManager, _this, GlobalPos, ContentPinnable);
}


//============================================================================
void CDockContainerWidget::showDropOverlays(CDockManager* DockManager,
CDockContainerWidget* TopContainer, const QPoint& GlobalPos,
bool ContentPinnable)
{
auto ContainerOverlay = DockManager->containerOverlay();
auto DockAreaOverlay = DockManager->dockAreaOverlay();
int VisibleDockAreas = TopContainer->visibleDockAreaCount();
DockWidgetAreas AllowedContainerAreas = (VisibleDockAreas > 1) ? OuterDockAreas : AllDockAreas;
auto DockArea = TopContainer->dockAreaAt(GlobalPos);
// If the dock container contains only one single DockArea, then we need
// to respect the allowed areas - only the center area is relevant here because
// all other allowed areas are from the container
if (VisibleDockAreas == 1 && DockArea)
{
AllowedContainerAreas.setFlag(CenterDockWidgetArea, DockArea->allowedAreas().testFlag(CenterDockWidgetArea));
}

if (ContentPinnable)
{
AllowedContainerAreas |= AutoHideDockAreas;
}

ContainerOverlay->setAllowedAreas(AllowedContainerAreas);

DockWidgetArea ContainerArea = ContainerOverlay->showOverlay(TopContainer, GlobalPos);
ContainerOverlay->enableDropPreview(ContainerArea != InvalidDockWidgetArea);
if (DockArea && DockArea->isVisible() && VisibleDockAreas > 0)
{
DockAreaOverlay->enableDropPreview(true);
DockAreaOverlay->setAllowedAreas(
(VisibleDockAreas == 1) ? NoDockWidgetArea : DockArea->allowedAreas());
DockWidgetArea Area = DockAreaOverlay->showOverlay(DockArea, GlobalPos);

// A CenterDockWidgetArea for the dockAreaOverlay() indicates that
// the mouse is in the title bar. If the ContainerArea is valid
// then we ignore the dock area of the dockAreaOverlay() and disable
// the drop preview
if ((Area == CenterDockWidgetArea)
&& (ContainerArea != InvalidDockWidgetArea))
{
DockAreaOverlay->enableDropPreview(false);
ContainerOverlay->enableDropPreview(true);
}
else
{
ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area);
}
}
else
{
DockAreaOverlay->hideOverlay();
}
}


//============================================================================
void DockContainerWidgetPrivate::dropIntoAutoHideSideBar(CFloatingDockContainer* FloatingWidget, DockWidgetArea area)
{
Expand Down Expand Up @@ -1425,6 +1507,14 @@ CDockContainerWidget::CDockContainerWidget(CDockManager* DockManager, QWidget *p
createRootSplitter();
createSideTabBarWidgets();
}

// On Wayland, floating widgets are docked via drag and drop events
// because the mouse tracked dragging that is used on the other platforms
// requires the global cursor position, which Wayland does not provide
if (internal::isWayland())
{
setAcceptDrops(true);
}
}


Expand Down Expand Up @@ -1537,6 +1627,105 @@ bool CDockContainerWidget::event(QEvent *e)
}


//============================================================================
/**
* Returns the drop position of the given drop event in global coordinates
*/
static QPoint dropEventGlobalPos(QDropEvent* e, QWidget* Widget)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return Widget->mapToGlobal(e->position().toPoint());
#else
return Widget->mapToGlobal(e->pos());
#endif
}


//============================================================================
CFloatingDockContainer* CDockContainerWidget::floatingWidgetFromDropEvent(
QDropEvent* e, CDockManager* DockManager)
{
auto FloatingWidget = CFloatingDockContainer::floatingWidgetFromMimeData(e->mimeData());
if (!FloatingWidget || !DockManager
|| !DockManager->floatingWidgets().contains(FloatingWidget))
{
return nullptr;
}

return FloatingWidget;
}


//============================================================================
void CDockContainerWidget::dragEnterEvent(QDragEnterEvent* e)
{
auto FloatingWidget = floatingWidgetFromDropEvent(e, d->DockManager);
if (!FloatingWidget || FloatingWidget->dockContainer() == this)
{
QFrame::dragEnterEvent(e);
return;
}

e->acceptProposedAction();
}


//============================================================================
void CDockContainerWidget::dragMoveEvent(QDragMoveEvent* e)
{
auto FloatingWidget = floatingWidgetFromDropEvent(e, d->DockManager);
if (!FloatingWidget || FloatingWidget->dockContainer() == this)
{
QFrame::dragMoveEvent(e);
return;
}

e->acceptProposedAction();
const QPoint GlobalPos = dropEventGlobalPos(e, this);
d->updateDropOverlays(GlobalPos, FloatingWidget);

// Record the drop candidate so the drag source can dock the floating
// widget if the compositor does not deliver a drop event (see
// CFloatingDockContainer::startPlatformDrag)
bool ValidDropArea =
(d->DockManager->containerOverlay()->visibleDropAreaUnderCursor(GlobalPos) != InvalidDockWidgetArea)
|| (d->DockManager->dockAreaOverlay()->visibleDropAreaUnderCursor(GlobalPos) != InvalidDockWidgetArea);
CFloatingDockContainer::platformDragUpdateDropCandidate(this, GlobalPos, ValidDropArea);
}


//============================================================================
void CDockContainerWidget::dragLeaveEvent(QDragLeaveEvent* e)
{
Q_UNUSED(e)
if (!d->DockManager)
{
return;
}

d->DockManager->containerOverlay()->hideOverlay();
d->DockManager->dockAreaOverlay()->hideOverlay();
}


//============================================================================
void CDockContainerWidget::dropEvent(QDropEvent* e)
{
auto FloatingWidget = floatingWidgetFromDropEvent(e, d->DockManager);
if (!FloatingWidget || FloatingWidget->dockContainer() == this)
{
QFrame::dropEvent(e);
return;
}

CFloatingDockContainer::platformDragNotifyDropHandled();
dropFloatingWidget(FloatingWidget, dropEventGlobalPos(e, this));
d->DockManager->containerOverlay()->hideOverlay();
d->DockManager->dockAreaOverlay()->hideOverlay();
e->acceptProposedAction();
}


//============================================================================
QList<CAutoHideDockContainer*> CDockContainerWidget::autoHideWidgets() const
{
Expand Down Expand Up @@ -1726,7 +1915,7 @@ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWi
CDockWidget* SingleDroppedDockWidget = FloatingWidget->topLevelDockWidget();
CDockWidget* SingleDockWidget = topLevelDockWidget();
auto dropArea = InvalidDockWidgetArea;
auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor();
auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor(TargetPos);
bool Dropped = false;

CDockAreaWidget* DockArea = dockAreaAt(TargetPos);
Expand All @@ -1735,7 +1924,7 @@ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWi
{
auto dropOverlay = d->DockManager->dockAreaOverlay();
dropOverlay->setAllowedAreas(DockArea->allowedAreas());
dropArea = dropOverlay->showOverlay(DockArea);
dropArea = dropOverlay->showOverlay(DockArea, TargetPos);
if (ContainerDropArea != InvalidDockWidgetArea &&
ContainerDropArea != dropArea)
{
Expand Down
Loading