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::Group);
156 if (group && !group->
view()->
equals(mTarget)) {
159 auto targetParent = mTarget->d->aboutToBeDestroyed() ? nullptr : mTarget->parentView();
160 const bool areSiblings = groupParent && groupParent->equals(targetParent);
163 restoreMouseCursor();
170 case Event::MouseButtonPress: {
171 if (mTarget->isMaximized())
174 CursorPosition cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(e));
175 updateCursor(cursorPos);
179 const int m = widgetResizeHandlerMargin();
180 const Rect widgetRect = mTarget->rect().marginsAdded(Margins(m, m, m, m));
181 const Point cursorPoint = mTarget->mapFromGlobal(Qt5Qt6Compat::eventGlobalPos(e));
182 if (!widgetRect.contains(cursorPoint) || e->button() !=
Qt::LeftButton)
185 m_resizingInProgress =
true;
188 mNewPosition = Qt5Qt6Compat::eventGlobalPos(e);
189 mCursorPos = cursorPos;
193 case Event::MouseButtonRelease: {
194 m_resizingInProgress =
false;
200 auto group = mTarget->asGroupController();
204 auto cursorPos = cursorPosition(Qt5Qt6Compat::eventGlobalPos(e));
205 updateCursor(cursorPos);
206 if (mTarget->isMaximized() || !m_resizingInProgress || e->button() !=
Qt::LeftButton)
209 mTarget->releaseMouse();
210 mTarget->releaseKeyboard();
211 if (m_eventFilteringStartsManually)
218 case Event::MouseMove: {
219 if (mTarget->isMaximized())
224 const bool otherGroupBeingResized =
225 groupBeingResized && groupBeingResized->
view() != mTarget;
226 if (otherGroupBeingResized) {
232 m_resizingInProgress = m_resizingInProgress && (e->buttons() &
Qt::LeftButton);
233 const bool consumed = mouseMoveEvent(e);
242bool WidgetResizeHandler::mouseMoveEvent(MouseEvent *e)
244 const Point globalPos = Qt5Qt6Compat::eventGlobalPos(e);
245 if (!m_resizingInProgress) {
251 const Rect oldGeometry = mTarget->d->globalGeometry();
252 Rect newGeometry = oldGeometry;
255 if (!mTarget->isRootView()) {
256 auto parent = mTarget->parentView();
257 parentGeometry = parent->d->globalGeometry();
263 const int maxWidth = mTarget->maxSizeHint().width();
264 const int minWidth = mTarget->minSize().width();
266 switch (mCursorPos) {
270 parentGeometry = parentGeometry.adjusted(m_resizeGap, 0, 0, 0);
271 deltaWidth = oldGeometry.left() - globalPos.x();
272 newWidth =
bound(minWidth, mTarget->width() + deltaWidth, maxWidth);
273 deltaWidth = newWidth - mTarget->width();
274 if (deltaWidth != 0) {
275 newGeometry.setLeft(newGeometry.left() - deltaWidth);
284 parentGeometry = parentGeometry.adjusted(0, 0, -m_resizeGap, 0);
285 deltaWidth = globalPos.x() - newGeometry.right();
286 newWidth =
bound(minWidth, mTarget->width() + deltaWidth, maxWidth);
287 deltaWidth = newWidth - mTarget->width();
288 if (deltaWidth != 0) {
289 newGeometry.setRight(oldGeometry.right() + deltaWidth);
299 const int maxHeight = mTarget->maxSizeHint().height();
300 const int minHeight = mTarget->minSize().height();
303 switch (mCursorPos) {
307 parentGeometry = parentGeometry.adjusted(0, m_resizeGap, 0, 0);
308 deltaHeight = oldGeometry.top() - globalPos.y();
309 newHeight =
bound(minHeight, mTarget->height() + deltaHeight, maxHeight);
310 deltaHeight = newHeight - mTarget->height();
311 if (deltaHeight != 0) {
312 newGeometry.setTop(newGeometry.top() - deltaHeight);
321 parentGeometry = parentGeometry.adjusted(0, 0, 0, -m_resizeGap);
322 deltaHeight = globalPos.y() - newGeometry.bottom();
323 newHeight =
bound(minHeight, mTarget->height() + deltaHeight, maxHeight);
324 deltaHeight = newHeight - mTarget->height();
325 if (deltaHeight != 0) {
326 newGeometry.setBottom(oldGeometry.bottom() + deltaHeight);
335 if (newGeometry == mTarget->geometry()) {
340 if (!mTarget->isRootView()) {
343 newGeometry = newGeometry.intersected(parentGeometry);
346 newGeometry.moveTopLeft(mTarget->mapFromGlobal(newGeometry.topLeft()) + mTarget->pos());
349 mTarget->setGeometry(newGeometry);
353#ifdef KDDW_FRONTEND_QT_WINDOWS
355void WidgetResizeHandler::requestNCCALCSIZE(HWND winId)
357 SetWindowPos(winId, 0, 0, 0, 0, 0,
358 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
364 Qt5Qt6Compat::qintptr *result)
366 if (eventType !=
"windows_generic_MSG")
369 auto msg =
static_cast<MSG *
>(message);
370 if (msg->message == WM_NCHITTEST) {
371 if (DragController::instance()->isInClientDrag()) {
377 const Rect htCaptionRect = fw->
dragRect();
378 const bool ret = handleWindowsNativeEvent(fw->
view()->
window(), msg, result, htCaptionRect);
380 fw->setLastHitTest(*result);
382 }
else if (msg->message == WM_NCLBUTTONDBLCLK) {
384 return handleWindowsNativeEvent(fw->
view()->
window(), msg, result, {});
388 if (titleBar->isVisible()) {
389 titleBar->onDoubleClicked();
397 return handleWindowsNativeEvent(fw->
view()->
window(), msg, result, {});
400bool WidgetResizeHandler::handleWindowsNativeEvent(Core::Window::Ptr w, MSG *msg,
401 Qt5Qt6Compat::qintptr *result,
402 const NativeFeatures &features)
404 if (msg->message == WM_NCCALCSIZE && features.hasShadow()) {
405 if (w->windowState() == WindowState::Minimized && w->hasBeenMinimizedDirectlyFromRestore()) {
413 }
else if (msg->message == WM_NCHITTEST && (features.hasResize() || features.hasDrag())) {
414 const int borderWidth = 8;
415 const bool hasFixedWidth = w->minWidth() == w->maxWidth();
416 const bool hasFixedHeight = w->minHeight() == w->maxHeight();
417 const bool hasFixedWidthOrHeight = hasFixedWidth || hasFixedHeight;
420 const int xPos = GET_X_LPARAM(msg->lParam);
421 const int yPos = GET_Y_LPARAM(msg->lParam);
423 GetWindowRect(
reinterpret_cast<HWND
>(w->handle()), &rect);
425 if (!hasFixedWidthOrHeight && xPos >= rect.left && xPos <= rect.left + borderWidth && yPos <= rect.bottom
426 && yPos >= rect.bottom - borderWidth && features.hasResize()) {
427 *result = HTBOTTOMLEFT;
428 }
else if (!hasFixedWidthOrHeight && xPos < rect.right && xPos >= rect.right - borderWidth && yPos <= rect.bottom
429 && yPos >= rect.bottom - borderWidth && features.hasResize()) {
430 *result = HTBOTTOMRIGHT;
431 }
else if (!hasFixedWidthOrHeight && xPos >= rect.left && xPos <= rect.left + borderWidth && yPos >= rect.top
432 && yPos <= rect.top + borderWidth && features.hasResize()) {
434 }
else if (!hasFixedWidthOrHeight && xPos <= rect.right && xPos >= rect.right - borderWidth && yPos >= rect.top
435 && yPos < rect.top + borderWidth && features.hasResize()) {
436 *result = HTTOPRIGHT;
437 }
else if (!hasFixedWidth && xPos >= rect.left && xPos <= rect.left + borderWidth
438 && features.hasResize()) {
440 }
else if (!hasFixedHeight && yPos >= rect.top && yPos <= rect.top + borderWidth
441 && features.hasResize()) {
443 }
else if (!hasFixedHeight && yPos <= rect.bottom && yPos >= rect.bottom - borderWidth
444 && features.hasResize()) {
446 }
else if (!hasFixedWidth && xPos <= rect.right && xPos >= rect.right - borderWidth
447 && features.hasResize()) {
449 }
else if (features.hasDrag()) {
450 const Point globalPosQt = w->fromNativePixels(Point(xPos, yPos));
452 const Rect htCaptionRect = features.htCaptionRect;
453 if (globalPosQt.y() >= htCaptionRect.top() && globalPosQt.y() <= htCaptionRect.bottom()
454 && globalPosQt.x() >= htCaptionRect.left()
455 && globalPosQt.x() <= htCaptionRect.right()) {
465 }
else if (msg->message == WM_NCLBUTTONDBLCLK && features.hasMaximize()) {
472 }
else if (msg->message == WM_GETMINMAXINFO) {
480 if (!screen || w->screen() != screen) {
484 DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
486 const Rect availableGeometry = screen->availableGeometry();
488 auto mmi =
reinterpret_cast<MINMAXINFO *
>(msg->lParam);
489 const double dpr = screen->devicePixelRatio();
491 mmi->ptMaxSize.y = int(availableGeometry.height() * dpr);
493 int(availableGeometry.width() * dpr) - 1;
494 mmi->ptMaxPosition.x = availableGeometry.x();
495 mmi->ptMaxPosition.y = availableGeometry.y();
497 mmi->ptMinTrackSize.x = int(w->minWidth() * dpr);
498 mmi->ptMinTrackSize.y = int(w->minHeight() * dpr);
509void WidgetResizeHandler::setTarget(
View *w)
515 if (m_usesGlobalEventFilter) {
518 mTarget->installViewEventFilter(
this);
521 KDDW_ERROR(
"Target widget is null!");
527 if (!m_handlesMouseCursor)
532 restoreMouseCursor();
538 const auto childViews = mTarget->childViews();
539 for (
const auto &child : childViews) {
563 restoreMouseCursor();
575 if (m_usesGlobalEventFilter) {
577 m_overrideCursorSet =
true;
578 }
else if (mTargetGuard) {
579 mTarget->setCursor(cursor);
583void WidgetResizeHandler::restoreMouseCursor()
585 if (!m_handlesMouseCursor)
588 if (m_usesGlobalEventFilter) {
589 if (m_overrideCursorSet) {
591 m_overrideCursorSet =
false;
593 }
else if (mTargetGuard) {
598CursorPosition WidgetResizeHandler::cursorPosition_(Point globalPos)
const
600#ifdef KDDW_FRONTEND_QTQUICK
605 const QVariant v = qtview->viewProperty(
"cursorPosition");
613 Point pos = mTarget->mapFromGlobal(globalPos);
615 const int x = pos.x();
616 const int y = pos.y();
617 const int margin = widgetResizeHandlerMargin();
620 if (y >= -margin && y <= mTarget->height() + margin) {
621 if (std::abs(x) <= margin)
623 else if (std::abs(x - (mTarget->width() - margin)) <= margin)
627 if (x >= -margin && x <= mTarget->width() + margin) {
628 if (std::abs(y) <= margin)
630 else if (std::abs(y - (mTarget->height() - margin)) <= margin)
635 result = result & mAllowedResizeSides;
640CursorPosition WidgetResizeHandler::cursorPosition(Point globalPos)
const
645 auto candidatePos = cursorPosition_(globalPos);
648 int result = int(candidatePos);
650 if (
auto group = mTarget->asGroupController()) {
653 result &= ~CursorPosition_Vertical;
656 result &= ~CursorPosition_Horizontal;
658 KDDW_ERROR(
"WidgetResizeHandler::cursorPosition: Expected group");
669void WidgetResizeHandler::setupWindow(Core::Window::Ptr window)
673#ifdef KDDW_FRONTEND_QT_WINDOWS
674 if (KDDockWidgets::usesAeroSnapWithCustomDecos()) {
675 const auto wid = HWND(window->handle());
676 window->onScreenChanged(
nullptr, [](
QObject *, Window::Ptr win) {
681 requestNCCALCSIZE(HWND(win->handle()));
684 const bool usesTransparentFloatingWindow =
686 if (!usesTransparentFloatingWindow) {
691 MARGINS margins = { 0, 0, 0, 1 };
692 DwmExtendFrameIntoClientArea(wid, &margins);
700#ifdef KDDW_FRONTEND_QT_WINDOWS
701bool WidgetResizeHandler::isInterestingNativeEvent(
unsigned int nativeEvent)
703 switch (nativeEvent) {
706 case WM_NCLBUTTONDBLCLK:
707 case WM_GETMINMAXINFO:
715#if defined(Q_OS_WIN) && defined(KDDW_FRONTEND_QTWIDGETS)
716bool NCHITTESTEventFilter::nativeEventFilter(
const QByteArray &eventType,
void *message,
717 Qt5Qt6Compat::qintptr *result)
720 if (eventType !=
"windows_generic_MSG" || !m_guard)
723 auto msg =
static_cast<MSG *
>(message);
724 if (msg->message != WM_NCHITTEST)
726 const WId wid =
WId(msg->hwnd);
730 if (!child || !m_floatingWindow->equals(child->rootView()))
732 const bool isThisWindow = m_floatingWindow->equals(child);
735 *result = HTTRANSPARENT;
743void WidgetResizeHandler::setEventFilterStartsManually()
745 m_eventFilteringStartsManually =
true;
749void WidgetResizeHandler::setHandlesMouseCursor(
bool handles)
751 m_handlesMouseCursor = handles;
Application-wide config to tune certain behaviours of the framework.
bool isValid() const const
int toInt(bool *ok) const const