12#include "WidgetResizeHandler_p.h"
13#include "DragController_p.h"
18#include "DockRegistry_p.h"
20#include "kddockwidgets/core/DockRegistry.h"
21#include "kddockwidgets/core/MDILayout.h"
22#include "kddockwidgets/core/TitleBar.h"
23#include "kddockwidgets/core/FloatingWindow.h"
24#include "kddockwidgets/core/Platform.h"
25#include "core/ScopedValueRollback_p.h"
30#if defined(KDDW_FRONTEND_QTWIDGETS)
31#include "../qtcommon/Platform.h"
35#if defined(KDDW_FRONTEND_QT)
36#include "../qtcommon/Window_p.h"
37#include <QGuiApplication>
38#include <QtGui/private/qhighdpiscaling_p.h>
48#pragma comment(lib, "Dwmapi.lib")
49#pragma comment(lib, "User32.lib")
56#if defined(KDDW_FRONTEND_QTQUICK)
60bool WidgetResizeHandler::s_disableAllHandlers =
false;
61WidgetResizeHandler::WidgetResizeHandler(EventFilterMode filterMode, WindowMode windowMode,
63 : m_usesGlobalEventFilter(filterMode == EventFilterMode::Global)
64 , m_isTopLevelWindowResizer(windowMode == WindowMode::TopLevel)
69WidgetResizeHandler::~WidgetResizeHandler()
71 if (m_usesGlobalEventFilter) {
73 }
else if (mTargetGuard) {
74 mTarget->removeViewEventFilter(
this);
80void WidgetResizeHandler::setAllowedResizeSides(CursorPositions sides)
82 mAllowedResizeSides = sides;
85void WidgetResizeHandler::setResizeGap(
int gap)
90bool WidgetResizeHandler::isMDI()
const
93 return group && group->
isMDI();
96bool WidgetResizeHandler::isResizing()
const
98 return m_resizingInProgress;
101int WidgetResizeHandler::widgetResizeHandlerMargin()
106bool WidgetResizeHandler::onMouseEvent(
View *widget, MouseEvent *e)
108 if (s_disableAllHandlers || !widget || !mTargetGuard)
111 if (e->type() != Event::MouseButtonPress && e->type() != Event::MouseButtonRelease
112 && e->type() != Event::MouseMove)
115 auto me = mouseEvent(e);
119 if (m_isTopLevelWindowResizer) {
123 if (m_usesGlobalEventFilter) {
127 if (!m_resizingInProgress) {
128 const Point globalPos = Qt5Qt6Compat::eventGlobalPos(me);
129 updateCursor(cursorPosition(globalPos));
137 }
else if (isMDI()) {
149 auto f = widget->
d->firstParentOfType(ViewType::Frame);
156 if (group && !group->
view()->
equals(mTarget)) {
159 auto targetParent = mTarget->d->aboutToBeDestroyed() ? nullptr : mTarget->parentView();
160 const bool areSiblings = groupParent && groupParent->equals(targetParent);
167 case Event::MouseButtonPress: {
168 if (mTarget->isMaximized())
171 auto cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(e));
172 updateCursor(cursorPos);
176 const int m = widgetResizeHandlerMargin();
177 const Rect widgetRect = mTarget->rect().marginsAdded(Margins(m, m, m, m));
178 const Point cursorPoint = mTarget->mapFromGlobal(Qt5Qt6Compat::eventGlobalPos(e));
179 if (!widgetRect.contains(cursorPoint) || e->button() !=
Qt::LeftButton)
182 m_resizingInProgress =
true;
185 mNewPosition = Qt5Qt6Compat::eventGlobalPos(e);
186 mCursorPos = cursorPos;
190 case Event::MouseButtonRelease: {
191 m_resizingInProgress =
false;
197 auto group = mTarget->asGroupController();
201 auto cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(e));
202 updateCursor(cursorPos);
203 if (mTarget->isMaximized() || !m_resizingInProgress || e->button() !=
Qt::LeftButton)
206 mTarget->releaseMouse();
207 mTarget->releaseKeyboard();
212 case Event::MouseMove: {
213 if (mTarget->isMaximized())
218 const bool otherGroupBeingResized =
219 groupBeingResized && groupBeingResized->
view() != mTarget;
220 if (otherGroupBeingResized) {
226 m_resizingInProgress = m_resizingInProgress && (e->buttons() &
Qt::LeftButton);
227 const bool consumed = mouseMoveEvent(e);
236bool WidgetResizeHandler::mouseMoveEvent(MouseEvent *e)
238 const Point globalPos = Qt5Qt6Compat::eventGlobalPos(e);
239 if (!m_resizingInProgress) {
245 const Rect oldGeometry = mTarget->d->globalGeometry();
246 Rect newGeometry = oldGeometry;
249 if (!mTarget->isRootView()) {
250 auto parent = mTarget->parentView();
251 parentGeometry = parent->d->globalGeometry();
257 const int maxWidth = mTarget->maxSizeHint().width();
258 const int minWidth = mTarget->minSize().width();
260 switch (mCursorPos) {
264 parentGeometry = parentGeometry.adjusted(m_resizeGap, 0, 0, 0);
265 deltaWidth = oldGeometry.left() - globalPos.x();
266 newWidth =
bound(minWidth, mTarget->width() + deltaWidth, maxWidth);
267 deltaWidth = newWidth - mTarget->width();
268 if (deltaWidth != 0) {
269 newGeometry.setLeft(newGeometry.left() - deltaWidth);
278 parentGeometry = parentGeometry.adjusted(0, 0, -m_resizeGap, 0);
279 deltaWidth = globalPos.x() - newGeometry.right();
280 newWidth =
bound(minWidth, mTarget->width() + deltaWidth, maxWidth);
281 deltaWidth = newWidth - mTarget->width();
282 if (deltaWidth != 0) {
283 newGeometry.setRight(oldGeometry.right() + deltaWidth);
293 const int maxHeight = mTarget->maxSizeHint().height();
294 const int minHeight = mTarget->minSize().height();
297 switch (mCursorPos) {
301 parentGeometry = parentGeometry.adjusted(0, m_resizeGap, 0, 0);
302 deltaHeight = oldGeometry.top() - globalPos.y();
303 newHeight =
bound(minHeight, mTarget->height() + deltaHeight, maxHeight);
304 deltaHeight = newHeight - mTarget->height();
305 if (deltaHeight != 0) {
306 newGeometry.setTop(newGeometry.top() - deltaHeight);
315 parentGeometry = parentGeometry.adjusted(0, 0, 0, -m_resizeGap);
316 deltaHeight = globalPos.y() - newGeometry.bottom();
317 newHeight =
bound(minHeight, mTarget->height() + deltaHeight, maxHeight);
318 deltaHeight = newHeight - mTarget->height();
319 if (deltaHeight != 0) {
320 newGeometry.setBottom(oldGeometry.bottom() + deltaHeight);
329 if (newGeometry == mTarget->geometry()) {
334 if (!mTarget->isRootView()) {
337 newGeometry = newGeometry.intersected(parentGeometry);
340 newGeometry.moveTopLeft(mTarget->mapFromGlobal(newGeometry.topLeft()) + mTarget->pos());
343 mTarget->setGeometry(newGeometry);
347#ifdef KDDW_FRONTEND_QT_WINDOWS
352 Qt5Qt6Compat::qintptr *result)
354 if (eventType !=
"windows_generic_MSG")
357 auto msg =
static_cast<MSG *
>(message);
358 if (msg->message == WM_NCHITTEST) {
359 if (DragController::instance()->isInClientDrag()) {
365 const Rect htCaptionRect = fw->
dragRect();
366 const bool ret = handleWindowsNativeEvent(fw->
view()->
window(), msg, result, htCaptionRect);
368 fw->setLastHitTest(*result);
370 }
else if (msg->message == WM_NCLBUTTONDBLCLK) {
372 return handleWindowsNativeEvent(fw->
view()->
window(), msg, result, {});
376 if (titleBar->isVisible()) {
377 titleBar->onDoubleClicked();
385 return handleWindowsNativeEvent(fw->
view()->
window(), msg, result, {});
388bool WidgetResizeHandler::handleWindowsNativeEvent(Core::Window::Ptr w, MSG *msg,
389 Qt5Qt6Compat::qintptr *result,
390 const NativeFeatures &features)
392 if (msg->message == WM_NCCALCSIZE && features.hasShadow()) {
395 }
else if (msg->message == WM_NCHITTEST && (features.hasResize() || features.hasDrag())) {
396 const int borderWidth = 8;
397 const bool hasFixedWidth = w->minWidth() == w->maxWidth();
398 const bool hasFixedHeight = w->minHeight() == w->maxHeight();
399 const bool hasFixedWidthOrHeight = hasFixedWidth || hasFixedHeight;
402 const int xPos = GET_X_LPARAM(msg->lParam);
403 const int yPos = GET_Y_LPARAM(msg->lParam);
405 GetWindowRect(
reinterpret_cast<HWND
>(w->handle()), &rect);
407 if (!hasFixedWidthOrHeight && xPos >= rect.left && xPos <= rect.left + borderWidth && yPos <= rect.bottom
408 && yPos >= rect.bottom - borderWidth && features.hasResize()) {
409 *result = HTBOTTOMLEFT;
410 }
else if (!hasFixedWidthOrHeight && xPos < rect.right && xPos >= rect.right - borderWidth && yPos <= rect.bottom
411 && yPos >= rect.bottom - borderWidth && features.hasResize()) {
412 *result = HTBOTTOMRIGHT;
413 }
else if (!hasFixedWidthOrHeight && xPos >= rect.left && xPos <= rect.left + borderWidth && yPos >= rect.top
414 && yPos <= rect.top + borderWidth && features.hasResize()) {
416 }
else if (!hasFixedWidthOrHeight && xPos <= rect.right && xPos >= rect.right - borderWidth && yPos >= rect.top
417 && yPos < rect.top + borderWidth && features.hasResize()) {
418 *result = HTTOPRIGHT;
419 }
else if (!hasFixedWidth && xPos >= rect.left && xPos <= rect.left + borderWidth
420 && features.hasResize()) {
422 }
else if (!hasFixedHeight && yPos >= rect.top && yPos <= rect.top + borderWidth
423 && features.hasResize()) {
425 }
else if (!hasFixedHeight && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth
426 && features.hasResize()) {
428 }
else if (!hasFixedWidth && xPos <= rect.right && xPos >= rect.right - borderWidth
429 && features.hasResize()) {
431 }
else if (features.hasDrag()) {
432 const Point globalPosQt = w->fromNativePixels(Point(xPos, yPos));
434 const Rect htCaptionRect = features.htCaptionRect;
435 if (globalPosQt.y() >= htCaptionRect.top() && globalPosQt.y() <= htCaptionRect.bottom()
436 && globalPosQt.x() >= htCaptionRect.left()
437 && globalPosQt.x() <= htCaptionRect.right()) {
447 }
else if (msg->message == WM_NCLBUTTONDBLCLK && features.hasMaximize()) {
454 }
else if (msg->message == WM_GETMINMAXINFO) {
462 if (!screen || w->screen() != screen) {
466 DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
468 const Rect availableGeometry = screen->availableGeometry();
470 auto mmi =
reinterpret_cast<MINMAXINFO *
>(msg->lParam);
471 const double dpr = screen->devicePixelRatio();
473 mmi->ptMaxSize.y = int(availableGeometry.height() * dpr);
475 int(availableGeometry.width() * dpr) - 1;
476 mmi->ptMaxPosition.x = availableGeometry.x();
477 mmi->ptMaxPosition.y = availableGeometry.y();
479 mmi->ptMinTrackSize.x = int(w->minWidth() * dpr);
480 mmi->ptMinTrackSize.y = int(w->minHeight() * dpr);
491void WidgetResizeHandler::setTarget(
View *w)
497 if (m_usesGlobalEventFilter) {
500 mTarget->installViewEventFilter(
this);
503 KDDW_ERROR(
"Target widget is null!");
511 restoreMouseCursor();
517 const auto childViews = mTarget->childViews();
518 for (
const auto &child : childViews) {
542 restoreMouseCursor();
554 if (m_usesGlobalEventFilter) {
556 m_overrideCursorSet =
true;
557 }
else if (mTargetGuard) {
558 mTarget->setCursor(cursor);
562void WidgetResizeHandler::restoreMouseCursor()
564 if (m_usesGlobalEventFilter) {
565 if (m_overrideCursorSet) {
567 m_overrideCursorSet =
false;
569 }
else if (mTargetGuard) {
574CursorPosition WidgetResizeHandler::cursorPosition(Point globalPos)
const
579#ifdef KDDW_FRONTEND_QTQUICK
584 const QVariant v = qtview->viewProperty(
"cursorPosition");
590 Point pos = mTarget->mapFromGlobal(globalPos);
592 const int x = pos.x();
593 const int y = pos.y();
594 const int margin = widgetResizeHandlerMargin();
597 if (y >= -margin && y <= mTarget->height() + margin) {
598 if (std::abs(x) <= margin)
600 else if (std::abs(x - (mTarget->width() - margin)) <= margin)
604 if (x >= -margin && x <= mTarget->width() + margin) {
605 if (std::abs(y) <= margin)
607 else if (std::abs(y - (mTarget->height() - margin)) <= margin)
612 result = result & mAllowedResizeSides;
618void WidgetResizeHandler::setupWindow(Core::Window::Ptr window)
623#ifdef KDDW_FRONTEND_QT_WINDOWS
624 if (KDDockWidgets::usesAeroSnapWithCustomDecos()) {
625 const auto wid = HWND(window->handle());
626 window->onScreenChanged(
nullptr, [](
QObject *, Window::Ptr win) {
631 const auto winId = HWND(win->handle());
632 SetWindowPos(winId, 0, 0, 0, 0, 0,
633 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
636 const bool usesTransparentFloatingWindow =
638 if (!usesTransparentFloatingWindow) {
643 MARGINS margins = { 0, 0, 0, 1 };
644 DwmExtendFrameIntoClientArea(wid, &margins);
652#ifdef KDDW_FRONTEND_QT_WINDOWS
653bool WidgetResizeHandler::isInterestingNativeEvent(
unsigned int nativeEvent)
655 switch (nativeEvent) {
658 case WM_NCLBUTTONDBLCLK:
659 case WM_GETMINMAXINFO:
667#if defined(Q_OS_WIN) && defined(KDDW_FRONTEND_QTWIDGETS)
668bool NCHITTESTEventFilter::nativeEventFilter(
const QByteArray &eventType,
void *message,
669 Qt5Qt6Compat::qintptr *result)
672 if (eventType !=
"windows_generic_MSG" || !m_guard)
675 auto msg =
static_cast<MSG *
>(message);
676 if (msg->message != WM_NCHITTEST)
678 const WId wid =
WId(msg->hwnd);
682 if (!child || !m_floatingWindow->equals(child->rootView()))
684 const bool isThisWindow = m_floatingWindow->equals(child);
687 *result = HTTRANSPARENT;
Application-wide config to tune certain behaviours of the framework.
bool isValid() const const
int toInt(bool *ok) const const