Skip to content
Merged
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
27 changes: 24 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,39 @@ 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.
- The compositor controls the window stacking, so floating windows are not
forced to stay on top of the main window.
- Saved layouts restore the docked arrangement, the floating-window sizes and
their maximized/normal state, but **not** the on-screen position of floating
windows (the same applies to the application's own main window). Wayland does
not let a client position its top-level windows, so the compositor decides
where restored windows appear. Restoring positions requires compositor side
session management (the staging `xx-session-management-v1` protocol), which is
not yet available through Qt.

## Build

The Linux build requires private header files. Make sure that they are installed.
Expand Down
88 changes: 85 additions & 3 deletions src/DockAreaTitleBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ struct DockAreaTitleBarPrivate
*/
IFloatingWidget* makeAreaFloating(const QPoint& Offset, eDragState DragState);

/**
* Wayland hybrid drag: drive the in-window drag preview from reliable event
* coordinates and, when the cursor leaves the source top-level window,
* convert the in-window drag into a native compositor platform drag
* (mirrors CDockWidgetTab).
*/
void waylandPreviewMove(QMouseEvent* ev);

/**
* Helper function to create and initialize the menu entries for
* the "Auto Hide Group To..." menu
Expand Down Expand Up @@ -291,6 +299,10 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset,
{
QSize Size = DockArea->size();
this->DragState = DragState;
// Wayland hybrid drag: start an in-window drag preview (driven by event
// coordinates, confined to the source container) rather than an immediate
// platform drag. The drag converts to a native compositor platform drag
// only when the cursor leaves the source window (see waylandPreviewMove()).
bool CreateFloatingDockContainer = (DraggingFloatingWidget != DragState);
CFloatingDockContainer* FloatingDockContainer = nullptr;
IFloatingWidget* FloatingWidget;
Expand All @@ -309,10 +321,17 @@ IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset,
{
this->DragState = DraggingInactive;
});
if (internal::isWayland())
{
// Confine the in-window preview to the source container; its
// position and the drop overlays are driven by event coordinates
// delivered to the title bar (see waylandPreviewMove()).
w->setSourceContainer(DockArea->dockContainer());
}
FloatingWidget = w;
}

FloatingWidget->startFloating(Offset, Size, DragState, nullptr);
FloatingWidget->startFloating(Offset, Size, DragState, nullptr);
if (FloatingDockContainer)
{
auto TopLevelDockWidget = FloatingDockContainer->topLevelDockWidget();
Expand All @@ -334,10 +353,49 @@ void DockAreaTitleBarPrivate::startFloating(const QPoint& Offset)
DockArea->autoHideDockContainer()->hide();
}
FloatingWidget = makeAreaFloating(Offset, DraggingFloatingWidget);
// On Wayland the hybrid drag now starts an in-window preview (it no longer
// blocks on a platform drag here), so the drag-start event is reported like
// on every other platform.
qApp->postEvent(DockArea, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent));
}


//============================================================================
void DockAreaTitleBarPrivate::waylandPreviewMove(QMouseEvent* ev)
{
const QPoint GlobalPos = internal::globalPositionOf(ev);

// FloatingWidget is a CFloatingDragPreview during the in-window phase
// (DraggingFloatingWidget state on Wayland; see makeAreaFloating()). While
// the cursor stays inside the source window the preview just follows it.
auto Preview = static_cast<CFloatingDragPreview*>(FloatingWidget);
if (CFloatingDockContainer::waylandMoveOrLeaveInWindowPreview(
Preview, _this->window(), GlobalPos))
{
return;
}

// Boundary cross: the preview was torn down, convert the gesture into a
// native compositor platform drag of a freshly created floating widget.
FloatingWidget = nullptr;
DragState = DraggingInactive;

if (DockArea->autoHideDockContainer())
{
DockArea->autoHideDockContainer()->cleanupAndDelete();
}
auto FloatingDockContainer = new CFloatingDockContainer(DockArea);
auto TopLevelDockWidget = FloatingDockContainer->topLevelDockWidget();
if (TopLevelDockWidget)
{
TopLevelDockWidget->emitTopLevelChanged(true);
}
CFloatingDockContainer::startPlatformDragForFloatingWidget(
FloatingDockContainer, DragStartMousePos, DockArea->size(),
_this->mapToGlobal(DragStartMousePos), _this);
}


//============================================================================
CDockAreaTitleBar::CDockAreaTitleBar(CDockAreaWidget* parent) :
QFrame(parent),
Expand Down Expand Up @@ -691,17 +749,41 @@ void CDockAreaTitleBar::mouseMoveEvent(QMouseEvent* ev)
// move floating window
if (d->isDraggingState(DraggingFloatingWidget))
{
d->FloatingWidget->moveFloating();
if (internal::isWayland())
{
// Wayland hybrid drag: in-window preview until the cursor leaves
// the source window, then convert to a native platform drag.
d->waylandPreviewMove(ev);
}
else
{
d->FloatingWidget->moveFloating();
}
return;
}

// If this is the last dock area in a floating dock container it does not make
// 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
Loading