Custom client-side window decorations in Qt 5.15
February 28, 2020 by Johan Helsing | Comments
This is just a quick update about a new feature in Qt 5.15 that I'm really excited
about.
Traditionally, window decorations have been a pretty boring thing. Title bar,
border, minimize, maximize, resize and quit... and that's it.
In recent times, however, applications more and more tend to include
application specific UI and theming in their decorations. Just a couple of screenshots to
explain what I'm talking about:
MacOS has been doing this for quite a while.
...and so has Chrome, and about every other web browser.
Embedding menus in the decorations can save a lot of screen space.
...or it can be important for branding or design purposes.
Unfortunately, these kinds of things were not previously possible with Qt.
It was, however, possible to remove the decorations on a window i.e.:
Window {
flags: Qt.FramelessWindowHint
}
But that just left you with a window with no decorations. So it couldn't be moved or resized. If you then were to try implementing window movement or resizing yourself by grabbing the mouse and manually setting the window size and position, you would quickly discover that it didn't really feel good. Window managers generally tend to have very specific behavior for how windows are moved or resized. Common conventions are dragging to the top to maximize, dragging to the left/right to tile, snapping to other windows or the task bar, resizing two windows simultaneously if they are tiled next to each other and so on.
In fairness, we did provide one helper for this previously: QSizeGrip. It lets you resize any given corner of a window, but it only works on corners, not window edges, and it's only available for widget applications.
In Qt 5.15, we've added two new methods to QWindow: startSystemMove and startSystemResize. These methods ask the window manager to take over and start a native resize or move operation. This means snapping, tiling, etc. should just magically work and implementing a titlebar in QML becomes almost a one-liner:
DragHandler {
onActiveChanged: if (active) window.startSystemMove();
target: null
}
Putting this this piece of code inside a QtQuick Item, will make any drag operations trigger a native window move operation.
startSystemResize works similarly, except it takes a Qt::Edges argument, which is a bit field of the window edges you grabbed. i.e. for the bottom, right corner, you would call
startSystemResize(Qt.RightEdge | Qt.BottomEdge)
This is also really convenient as you can easily have one handler for all four window edges and just build up the edges argument like this:
DragHandler {
id: resizeHandler
grabPermissions: TapHandler.TakeOverForbidden
target: null
onActiveChanged: if (active) {
const p = resizeHandler.centroid.position;
let e = 0;
if (p.x < border) e |= Qt.LeftEdge;
if (p.x >= width - border) e |= Qt.RightEdge;
if (p.y < border) e |= Qt.TopEdge;
if (p.y >= height - border) e |= Qt.BottomEdge;
window.startSystemResize(e);
}
}
If you want a full example of how this can be used, I made a mockup of a web browser using the new API.
Note that although this is a cross-platform API, not all platforms support it. startSystemMove is currently supported on Wayland, X11, macOS and Windows, while startSystemResize, on the other hand, is supported on Wayland, X11 and Windows, but not on macOS.
In order to deal with this, both methods return a boolean which indicates whether the operation was supported or not. This means that if you want to implement resizing on macOS as well, you will have to check the return value of startSystemResize and try your best to implement a fallback in case it fails, i.e.
if (!window.startSystemResize(edges)) {
// your fallback code for setting window.width/height manually
}
Further work in Qt would be to provide fallbacks or abstractions on top that would do this work for you, but at least nothing should stop you from doing it yourself.
Another area of improvement is the negotiations with the window manager on whether client-side or server-side window decorations should be used. Some applications might want to support both modes and let the window manager decide, but this is not currently possible. Once the FramelessWindowHint is set, there will be no server-side decorations.
A third area is the window shadow. At least on Wayland, the shadow should be drawn as part of the window decoration. And while we could definitely draw shadows with QtQuick, there is currently no way to tell the QPA plugin which part of the surface is the shadow and which part is the window frame, which means if you try to draw shadows, the window manager will currently consider the shadow part of the window and this will mess up tiling and snapping with other windows. On other platforms, shadows are generally drawn by the window manager, even for client-side decorated windows, so this is a tricky issue to solve.
That's it. A big thanks to everyone that helped test the API on various platforms! I really hope people will build something cool with it.
Blog Topics:
Comments
Subscribe to our newsletter
Subscribe Newsletter
Try Qt 6.9 Now!
Download the latest release here: www.qt.io/download.
Qt 6.9 is now available, with new features and improvements for application developers and device creators.
We're Hiring
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.
Commenting for this post has ended.
Could someone also make an example for Qt Widgets?
Not using widgets, but there is a lower level c++ manual test in qtbase that might help you get started: https://github.com/qt/qtbase/blob/dev/tests/manual/startsystemmove/main.cpp
Well, there is the CustomTitlebar project which allows you to have a customized window decoration by inheriting from NMainWindow (which inherits QMainWindow). There are some screenshots and project samples (and, should your project be for Windows, there are the DLLs available for download as well, otherwise you can just download the "statically-linked version").
I took a quick look at that project, and it looks like it doesn't use the new API (
startSystemMove
andstartSystemResize
), which means snapping, tiling etc. won't work, and it won't work on Wayland. It'd probably easy to fix it, though... Releavant part of their code: https://github.com/Nintersoft/CustomTitlebar/blob/master/src/CustomTitlebar-Dynamic/titlebar.cpp#L92Thank you for your feedback! In fact, this project uses an older version of the API, since the last compilation provided was only available for "Qt 5.12.6" (startSystemMove + startSystemResize were introduced in Qt 5.15). I'll have a look at it and make the necessary adaptations as soon as I can.
[EDIT]
I've just noticed that this new API is only available for QWindow, which is not part of Qt Widgets package.Unfortunately, I won't be able to upgrade the project until this API is also made available for QMainWindow as well. I have not tested CustomTitlebar on Wayland yet, but it seems to work pretty well in other platforms (tested on Windows and Gnome over X.org).
Every QWidget has the function windowHandle(). This will return you a QWindow, which you can call startSystemMove and startSystemResize on.
Example:
QWidget* widget = new QWidget();
widget->windowHandle()->startSystemMove();
Keep this project going. This is much needed functionality on all desktop platforms.
Once get rid of the window frame, the frame shadow is gone on Windows and it's a headache to bring it back. What's worse, the frameless window can no longer react to the window manager.
Thanks very much ! This is the feature I have been longing for a long time !!!
Good stuff! Thanks I will definitely use this feature for ScreenPlay!
Nice feature, but it is still buggy in case of moving the window between screens with different scaling factors. (for example 125% and 100%) In this moment it resizes the window not correctly. (It does not resize the window at all.) Because of that, this it is not really useful at the moment and people have to implement still their own logic, maybe with this new methods on top. But what would help to allow the access to the setPosition() method inside of qml. Why we dont have access to this method?
Window resizing when moving windows between screens is a different issue, and shouldn't be affected by this change. I.e. if it is a problem for you with this change, it was probably already a bug with regular server-side window moving. Please report it if you think so.
I don't recall having any issues with resizing when moving windows between screens on Wayland (which is the platform I worked on).
As for your question: IIRC you can set window position from qml by setting the
x
andy
properties of theWindow
, however, as explained in the blog post, this comes with it's own set of problems, and it's also not going to work on Wayland if you care about that (since applications are not allowed to know their position on the screen (this is by design in the protocol and out of our control)). I believe it should work on Windows, macOS and X11, though.This has always been pretty do-able in QML land...
The problem has been snapping to other windows and screen edges, maximizing when dragging to the top. You don't want to implement that yourself for every platform, you're just bound to get it wrong, and not feel native.
Good job!
Overloading the Title bar purpose has become unfortunately very popular. It's disconcerting that also some major players like Microsoft and Google are doing it, even against their own UI language guidelines.
I get it, that designers like to see their handwriting and it's also a way of distinguishing your program from the competition. But this severely breaks the desktop metaphor and control meta-language and overal usability. How about letting the OS/window manager handle window title and window sizing in an uniform way - as it does and should do, and this way empower the user with predictability, confidence and fluency, instead of confusing them with every other app looking and behaving differently.
If you are a UI designer or programmer wanting this in your project, I encourage you to consider the other side - usability, and research about it. A good, albeit opinonated article is here: https://datagubbe.se/decusab/ and the associated HN discussion has also some good points: https://news.ycombinator.com/item?id=22901541
I think it's not as much about design s it is about conserving screen space. We've been asked a couple times (by people with laptops) to get rid of the title bar to get an extra dozen of pixels of useful window height.
If you have issues with resizing(especially reducing size) with small border width set in drag handler, I recommend to use
resizeHandler.centroid.scenePressPosition
instead ofroot.centroid.position
.Thanks! That worked for me :)
A sample Qt Widget implementation using the same API:
https://gitlab.com/slbtty/Qt6_CSD_DEMO
demo: https://moe.cat/@shenlebantongying/108377968998176242
Why is it still so difficult to implement a frameless window? This seems to solve it 4 years ago. BUT: - Doesn't work on macOS. So the solution is not good enough for a multiplatform app... - No example in c++ anywhere to be found. A demo app in cpp and one in python would be good.
Alternatively I found the project : https://github.com/antonypro/QGoodWindow which seems to work on all plateform. But I've not sucessfully used it yet.
Honestly one would expect native support in Qt for this without having to jump through hoops...
Why those new function are ONLY QWindow and not QMainWindow ??? Just why is it so hard to make a frameless application in QT? This is ridiculous...