feat(qt): custom title bar on Windows, with a native hit test
Three requirements -- no title bar, working clicks, and window tiling -- and no combination of public Qt flags delivers all three. Each attempt was measured on the box rather than reasoned about, and each one taught something the plan had wrong. FramelessWindowHint, the previous arm, removes WS_THICKFRAME. THAT is what the shell keys tiling on -- not WS_CAPTION, which this window never has in any arrangement -- so Win+Arrow and Aero Snap were dead, along with the DWM shadow and the Win11 rounded corners. "Hide title bar" was not making the app refuse the shortcuts; it had stopped being a window the shell could tile. Adding CustomizeWindowHint restored tiling and stopped Qt painting its own minimise/maximise/close cluster over the QBZ header. It also made Qt answer WM_NCHITTEST with HTNOWHERE for EVERY pixel -- centre, header, player bar and sidebar all returned 0 -- and Windows delivers no mouse input to HTNOWHERE, so the whole app became unclickable while still running: the tray menu still drove playback. Dropping the hint fixed clicks and brought the drawn buttons back, duplicated on top of the ones QBZ already draws itself. So the hint stays and cxx/win_shell.cpp answers the hit test instead. The filter defers to DefWindowProcW, which is what an ordinary sizable caption-less window would return: the eight edge codes near the frame, HTCLIENT everywhere else. It invents no geometry. Its guard is narrow on purpose -- sizable AND caption-less, which is exactly the custom-chrome main window -- so menus, tooltips, popups and any window showing the system title bar keep Qt's own answer. Measured after: HTCLIENT at the centre, in the title band, at the edges and in the corner; Win+Left resized the window to 1720 px, exactly half of a 3440 px display. Dragging is unaffected and stays with QML's startSystemMove(): with no caption there is no HTCAPTION band to inherit, which is the arrangement HeaderBar was already written for. NOT restored, and not restorable from flags: the Win11 Snap Layouts hover flyout. Windows asks for it by hit-testing the maximise button as HTMAXBUTTON, and QBZ's is a QML item inside the client area. Reporting HTMAXBUTTON over that one rect from this filter would do it -- named as future work rather than claimed. cc
This commit is contained in:
4 files changed
+157
-1
No files matched your search
@@ -140,11 +140,66 @@ ApplicationWindow {
|
||||
// the frame, and the reference never draws its own there
|
||||
// ("Linux only — macOS keeps the native traffic lights",
|
||||
// WindowControls.slint:1-2).
|
||||
// WINDOWS takes the macOS shape, not the Linux one, and the difference is
|
||||
// the whole feature (W27).
|
||||
//
|
||||
// `Qt.FramelessWindowHint` removes WS_THICKFRAME, and THAT is what the
|
||||
// shell keys tiling on: Win+Arrow and Aero Snap by dragging to an edge both
|
||||
// stop, along with the DWM drop shadow and the Win11 rounded corners. So
|
||||
// "hide title bar" was not making the app refuse the shortcuts -- it had
|
||||
// stopped being a window the shell could tile.
|
||||
//
|
||||
// NOT restored, and it cannot be from flags alone: the Windows 11 Snap
|
||||
// Layouts flyout that appears when hovering a maximise button. Windows asks
|
||||
// for it by hit-testing that pixel as HTMAXBUTTON, and QBZ's maximise
|
||||
// button is a QML item inside the client area, so it answers HTCLIENT.
|
||||
// Wiring it would mean reporting HTMAXBUTTON over that one rect from the
|
||||
// native filter -- deliberate future work, not something this arm does.
|
||||
//
|
||||
// Qt 6.9's Windows plugin already implements WM_NCCALCSIZE -> 0,
|
||||
// DwmExtendFrameIntoClientArea, the eight resize edges and
|
||||
// safeAreaMargins() for `ExpandedClientAreaHint` -- the same flags the
|
||||
// macOS arm uses. `CustomizeWindowHint` with no button hints then
|
||||
// suppresses Qt's own painted title cluster
|
||||
// (qwindowswindow.cpp :3561/:3600/:3624/:3645) while the window KEEPS
|
||||
// WS_CAPTION, so the shell still treats it as tileable.
|
||||
//
|
||||
// It also provides no HTCAPTION drag band of its own (:3332-3338: with
|
||||
// both isDefaultTitleBar and isCustomized false the whole title block is
|
||||
// skipped), which is why HeaderBar's QML `startSystemMove()` stays: it
|
||||
// issues SC_MOVE, and SC_MOVE drags DO snap. The eight QML resize grips
|
||||
// stay enabled too and do not fight the native edges -- Qt answers
|
||||
// WM_NCHITTEST first, so a press on the border never reaches QML.
|
||||
flags: QbzShell.systemTitleBar
|
||||
? Qt.Window
|
||||
: (QbzShell.isMacos
|
||||
? (Qt.Window | Qt.ExpandedClientAreaHint | Qt.NoTitleBarBackgroundHint)
|
||||
: (Qt.Window | Qt.FramelessWindowHint))
|
||||
: (QbzShell.isWindows
|
||||
// Both halves were MEASURED on the box, and neither is
|
||||
// optional:
|
||||
//
|
||||
// WITHOUT CustomizeWindowHint, Qt populates the default title
|
||||
// hints and its customized-title path DRAWS minimise/maximise/
|
||||
// close over the QBZ header.
|
||||
//
|
||||
// WITH it, Qt answers WM_NCHITTEST with HTNOWHERE for every
|
||||
// pixel -- centre, header, player bar and sidebar all returned
|
||||
// 0 -- and Windows sends no mouse input to HTNOWHERE, so the
|
||||
// whole app becomes unclickable while still running.
|
||||
//
|
||||
// No public flag combination gives no-buttons + clicks +
|
||||
// the WS_THICKFRAME that tiling needs, so the hint stays and
|
||||
// `win_shell::install_hittest_filter()` answers the hit test
|
||||
// instead. What this arm must NOT do is drop the hint: that
|
||||
// trades the buttons back in.
|
||||
//
|
||||
// Frameless is not the answer either: it removes
|
||||
// WS_THICKFRAME, and THAT is what Win+Arrow and Aero Snap
|
||||
// actually key on -- not WS_CAPTION, which this window does
|
||||
// not have in either arrangement.
|
||||
? (Qt.Window | Qt.ExpandedClientAreaHint | Qt.NoTitleBarBackgroundHint
|
||||
| Qt.CustomizeWindowHint)
|
||||
: (Qt.Window | Qt.FramelessWindowHint)))
|
||||
|
||||
// THE OTHER HALF OF ExpandedClientAreaHint, and the piece that made the
|
||||
// flag look broken when it was not.
|
||||
|
||||
Reference in new issue
Block a user