Skip to content

Commit 1318170

Browse files
daddel80donho
authored andcommitted
Fix dragging dockable dialog on 2nd monitor visual glitch
Fix notepad-plus-plus#16805, fix notepad-plus-plus#16155, fix notepad-plus-plus#16077, close notepad-plus-plus#17336
1 parent 6a83e90 commit 1318170

2 files changed

Lines changed: 203 additions & 115 deletions

File tree

PowerEditor/src/WinControls/DockingWnd/Gripper.cpp

Lines changed: 184 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ using namespace std;
3232

3333

3434
BOOL Gripper::_isRegistered = FALSE;
35+
bool Gripper::_isOverlayClassRegistered = false;
3536

3637
static HWND hWndServer = NULL;
3738
static HHOOK hookMouse = NULL;
@@ -481,53 +482,118 @@ void Gripper::doTabReordering(POINT pt)
481482
::UpdateWindow(_hParent);
482483
}
483484

484-
// Changed behaviour (jg): Now this function handles erasing of drag-rectangles and drawing of
485-
// new ones within one drawing step to the desktop. This is against flickering, but also it is
486-
// necessary for the Vista Aero style - because in this case the control is given so much to
487-
// the graphics driver, that accesses (especially read accesses) to the desktop window become
488-
// too expensive to access it more than absolutely necessary. Besides, usage of the function
489-
// ::LockWindowUpdate() was added, because with often redrawn windows in the background we had
490-
// inconsistencies while erasing our drag-rectangle (because it could already have been erased
491-
// on some places).
485+
// ============================================================================
486+
// Overlay window for drag rectangle rendering (tk)
492487
//
493-
// Parameter pPt==NULL says that only erasing is wanted and the drag-rectangle is no more needed,
494-
// thatswhy this also leads to a call of ::LockWindowUpdate(NULL) to enable drawing by others again.
495-
// The previously drawn rectangle is memoried within _rectPrev (and _bPtOldValid says if it already
496-
// is valid - did not change this members name because didn't want change too much at once).
488+
// Uses a layered window spanning the entire virtual screen with color-key
489+
// transparency (magenta). The drag rectangle is drawn to a memory DC and
490+
// blitted to the overlay - only the rectangle frame is visible.
497491
//
498-
// I was too lazy to always draw four rectangles for the four edges of the drag-rectangle - it seems
499-
// that drawing an outer rectangle first and then erasing the inner stuff by drawing a second,
500-
// smaller rectangle inside seems to be not slower - wich comes not unawaited, because it is mostly
501-
// hardware-driven and each single draw has its own fixed costs.
502-
//
503-
// For further solutions I think we should leave this classic way of dragging and better use
504-
// alpha-blending and always move the whole content of the toolbars - so we could leave the
505-
// ::LockWindowUpdate() behind us.
506-
//
507-
// Besides, while debugging into the dragging process please let the ::LockWindowUpdate() out,
508-
// by #undef the USE_LOCKWINDOWUPDATE in gripper.h, because it works for your debugging window
509-
// as well, of course. Or just try by this #define what difference it makes.
510-
//
511-
void Gripper::drawRectangle(const POINT* pPt)
492+
// Coordinates: Screen coordinates are translated to overlay-local by
493+
// subtracting the virtual screen origin (SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN).
494+
// ============================================================================
495+
496+
static constexpr COLORREF clrMagenta = RGB(255, 0, 255);
497+
498+
LRESULT CALLBACK Gripper::overlayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
499+
{
500+
return ::DefWindowProc(hwnd, msg, wParam, lParam);
501+
}
502+
503+
bool Gripper::createOverlayWindow()
512504
{
513-
HBRUSH hbrushOrig= NULL;
514-
HBITMAP hbmOrig = NULL;
515-
RECT rc = {};
516-
RECT rcNew = {};
517-
RECT rcOld = _rcPrev;
505+
if (_hOverlayWnd)
506+
return true;
507+
508+
if (!_isOverlayClassRegistered)
509+
{
510+
WNDCLASSEX wc = {};
511+
wc.cbSize = sizeof(wc);
512+
wc.lpfnWndProc = overlayWndProc;
513+
wc.hInstance = _hInst;
514+
wc.lpszClassName = L"NppGripperOverlay";
515+
516+
if (!::RegisterClassEx(&wc))
517+
return false;
518+
519+
_isOverlayClassRegistered = true;
520+
}
518521

519-
// Get a screen device context with backstage redrawing disabled - to have a consistently
520-
// and stable drawn rectangle while floating - keep in mind, that we must ensure, that
521-
// finally ::LockWindowUpdate(NULL) will be called, to enable drawing for others again.
522-
if (!_hdc)
522+
_xVirtScreen = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
523+
_yVirtScreen = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
524+
_overlayWidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
525+
_overlayHeight = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
526+
527+
_hOverlayWnd = ::CreateWindowEx(
528+
WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT,
529+
L"NppGripperOverlay",
530+
L"",
531+
WS_POPUP,
532+
_xVirtScreen, _yVirtScreen, _overlayWidth, _overlayHeight,
533+
nullptr, nullptr, _hInst, nullptr
534+
);
535+
536+
if (!_hOverlayWnd)
537+
return false;
538+
539+
::SetLayeredWindowAttributes(_hOverlayWnd, clrMagenta, 0, LWA_COLORKEY);
540+
541+
HDC hdcScreen = ::GetDC(nullptr);
542+
_hdcOverlayMem = ::CreateCompatibleDC(hdcScreen);
543+
_hBitmapOverlay = ::CreateCompatibleBitmap(hdcScreen, _overlayWidth, _overlayHeight);
544+
_hOldBitmap = static_cast<HBITMAP>(::SelectObject(_hdcOverlayMem, _hBitmapOverlay));
545+
::ReleaseDC(nullptr, hdcScreen);
546+
547+
// Fill with transparent color
548+
HBRUSH hBrushTransparent = ::CreateSolidBrush(clrMagenta);
549+
RECT rcFill = { 0, 0, _overlayWidth, _overlayHeight };
550+
::FillRect(_hdcOverlayMem, &rcFill, hBrushTransparent);
551+
::DeleteObject(hBrushTransparent);
552+
553+
_hdcOverlay = ::GetDC(_hOverlayWnd);
554+
555+
::ShowWindow(_hOverlayWnd, SW_SHOWNOACTIVATE);
556+
::BitBlt(_hdcOverlay, 0, 0, _overlayWidth, _overlayHeight, _hdcOverlayMem, 0, 0, SRCCOPY);
557+
558+
return true;
559+
}
560+
561+
void Gripper::destroyOverlayWindow()
562+
{
563+
if (_hdcOverlay && _hOverlayWnd)
564+
{
565+
::ReleaseDC(_hOverlayWnd, _hdcOverlay);
566+
_hdcOverlay = nullptr;
567+
}
568+
if (_hdcOverlayMem)
523569
{
524-
HWND hWnd = ::GetDesktopWindow();
525-
#if defined (USE_LOCKWINDOWUPDATE)
526-
_hdc = ::GetDCEx(hWnd, NULL, ::LockWindowUpdate(hWnd) ? DCX_WINDOW|DCX_CACHE|DCX_LOCKWINDOWUPDATE : DCX_WINDOW|DCX_CACHE);
527-
#else
528-
_hdc = ::GetDCEx(hWnd, NULL, DCX_WINDOW|DCX_CACHE);
529-
#endif
570+
if (_hOldBitmap)
571+
::SelectObject(_hdcOverlayMem, _hOldBitmap);
572+
::DeleteDC(_hdcOverlayMem);
573+
_hdcOverlayMem = nullptr;
574+
_hOldBitmap = nullptr;
530575
}
576+
if (_hBitmapOverlay)
577+
{
578+
::DeleteObject(_hBitmapOverlay);
579+
_hBitmapOverlay = nullptr;
580+
}
581+
if (_hOverlayWnd)
582+
{
583+
::DestroyWindow(_hOverlayWnd);
584+
_hOverlayWnd = nullptr;
585+
}
586+
}
587+
588+
// Draw drag rectangle using layered overlay window
589+
//
590+
// Uses overlay window approach to fix clipping issues on multi-monitor setups
591+
// where the virtual screen has negative coordinates (Issue #16805).
592+
//
593+
void Gripper::drawRectangle(const POINT* pPt)
594+
{
595+
RECT rcNew = {};
596+
RECT rcOld = _rcPrev;
531597

532598
// Create a brush with the appropriate bitmap pattern to draw our drag rectangle
533599
if (!_hbm)
@@ -537,93 +603,109 @@ void Gripper::drawRectangle(const POINT* pPt)
537603

538604
if (pPt != NULL)
539605
{
540-
// Determine whether to draw a solid drag rectangle or checkered
541-
// ???(jg) solid or checked ??? - must have been an old comment, I didn't
542-
// find here this difference, but at least it's a question of drag-rects size
543-
//
606+
// getMovingRect() returns rect where right/bottom = width/height (not coordinates)
544607
getMovingRect(*pPt, &rcNew);
545-
_rcPrev= rcNew; // save the new drawn rcNew
546-
547-
// note that from here for handling purposes the right and bottom values of the rects
548-
// contain width and height - its handsome, but i find it dangerous, but didn't want to
549-
// change that already this time.
608+
_rcPrev = rcNew;
550609

551610
if (_bPtOldValid)
552611
{
553-
// okay, there already a drag-rect has been drawn - and its position
554-
// had been saved within the rectangle _rectPrev, wich already had been
555-
// copied into rcOld in the beginning, and a new drag position
556-
// is available, too.
557-
// If now rcOld and rcNew are the same, just stop further handling to not
558-
// draw the same drag-rectangle twice (this really happens, it should be
559-
// better avoided anywhere earlier)
560-
//
561-
if (rcOld.left==rcNew.left && rcOld.right==rcNew.right && rcOld.top== rcNew.top && rcOld.bottom==rcNew.bottom)
612+
if (rcOld.left == rcNew.left && rcOld.right == rcNew.right &&
613+
rcOld.top == rcNew.top && rcOld.bottom == rcNew.bottom)
562614
return;
563-
564-
rc.left = std::min<LONG>(rcOld.left, rcNew.left);
565-
rc.top = std::min<LONG>(rcOld.top, rcNew.top);
566-
rc.right = std::max<LONG>(rcOld.left + rcOld.right, rcNew.left + rcNew.right);
567-
rc.bottom = std::max<LONG>(rcOld.top + rcOld.bottom, rcNew.top + rcNew.bottom);
568-
rc.right -= rc.left;
569-
rc.bottom-= rc.top;
570615
}
571-
else rc = rcNew; // only new rect will be drawn
572616
}
573-
else rc = rcOld; // only old rect will be drawn - to erase it
574617

575-
// now rc contains the rectangle wich encloses all needed, new and/or previous rectangle
576-
// because in the following we drive within a memory device context wich is limited to rc,
577-
// we have to localize rcNew and rcOld within rc...
578-
//
579-
rcOld.left = rcOld.left - rc.left;
580-
rcOld.top = rcOld.top - rc.top;
581-
rcNew.left = rcNew.left - rc.left;
582-
rcNew.top = rcNew.top - rc.top;
618+
// Create overlay window on first draw
619+
if (!_hOverlayWnd)
620+
{
621+
if (!createOverlayWindow())
622+
return;
623+
}
583624

584-
HDC hdcMem = ::CreateCompatibleDC(_hdc);
585-
HBITMAP hBm = ::CreateCompatibleBitmap(_hdc, rc.right, rc.bottom);
586-
hbrushOrig = (HBRUSH)::SelectObject(hdcMem, hBm);
625+
// Save absolute coordinates before any modifications
626+
RECT rcOldAbsolute = rcOld;
627+
RECT rcNewAbsolute = rcNew;
587628

588-
::SetBrushOrgEx(hdcMem, rc.left%8, rc.top%8, 0);
589-
hbmOrig = (HBITMAP)::SelectObject(hdcMem, _hbrush);
629+
// Calculate overlay-local coordinates
630+
LONG newOverlayX = rcNewAbsolute.left - _xVirtScreen;
631+
LONG newOverlayY = rcNewAbsolute.top - _yVirtScreen;
632+
LONG newWidth = rcNewAbsolute.right;
633+
LONG newHeight = rcNewAbsolute.bottom;
590634

591-
::BitBlt(hdcMem, 0, 0, rc.right, rc.bottom, _hdc, rc.left, rc.top, SRCCOPY);
635+
// Erase old rectangle
592636
if (_bPtOldValid)
593-
{ // erase the old drag-rectangle
594-
::PatBlt(hdcMem, rcOld.left , rcOld.top , rcOld.right , rcOld.bottom , PATINVERT);
595-
::PatBlt(hdcMem, rcOld.left+3, rcOld.top+3, rcOld.right-6, rcOld.bottom-6, PATINVERT);
637+
{
638+
HBRUSH hBrushTransparent = ::CreateSolidBrush(clrMagenta);
639+
LONG oldOverlayX = rcOldAbsolute.left - _xVirtScreen;
640+
LONG oldOverlayY = rcOldAbsolute.top - _yVirtScreen;
641+
LONG oldWidth = rcOldAbsolute.right;
642+
LONG oldHeight = rcOldAbsolute.bottom;
643+
RECT rcClear = { oldOverlayX, oldOverlayY, oldOverlayX + oldWidth, oldOverlayY + oldHeight };
644+
::FillRect(_hdcOverlayMem, &rcClear, hBrushTransparent);
645+
::DeleteObject(hBrushTransparent);
596646
}
597647

648+
// Draw new rectangle
598649
if (pPt != NULL)
599-
{ // draw the new drag-rectangle
600-
::PatBlt(hdcMem, rcNew.left , rcNew.top , rcNew.right , rcNew.bottom , PATINVERT);
601-
::PatBlt(hdcMem, rcNew.left+3, rcNew.top+3, rcNew.right-6, rcNew.bottom-6, PATINVERT);
650+
{
651+
HBRUSH hBrushTransparent = ::CreateSolidBrush(clrMagenta);
652+
RECT rcFrame = { newOverlayX, newOverlayY, newOverlayX + newWidth, newOverlayY + newHeight };
653+
::FillRect(_hdcOverlayMem, &rcFrame, hBrushTransparent);
654+
::DeleteObject(hBrushTransparent);
655+
656+
HBRUSH hBrushGray = ::CreateSolidBrush(RGB(128, 128, 128));
657+
RECT rcTop = { newOverlayX, newOverlayY, newOverlayX + newWidth, newOverlayY + 3 };
658+
::FillRect(_hdcOverlayMem, &rcTop, hBrushGray);
659+
RECT rcBottom = { newOverlayX, newOverlayY + newHeight - 3, newOverlayX + newWidth, newOverlayY + newHeight };
660+
::FillRect(_hdcOverlayMem, &rcBottom, hBrushGray);
661+
RECT rcLeft = { newOverlayX, newOverlayY, newOverlayX + 3, newOverlayY + newHeight };
662+
::FillRect(_hdcOverlayMem, &rcLeft, hBrushGray);
663+
RECT rcRight = { newOverlayX + newWidth - 3, newOverlayY, newOverlayX + newWidth, newOverlayY + newHeight };
664+
::FillRect(_hdcOverlayMem, &rcRight, hBrushGray);
665+
::DeleteObject(hBrushGray);
666+
667+
HBRUSH hOldBrush = static_cast<HBRUSH>(::SelectObject(_hdcOverlayMem, _hbrush));
668+
::SetBrushOrgEx(_hdcOverlayMem, rcNewAbsolute.left % 8, rcNewAbsolute.top % 8, nullptr);
669+
::PatBlt(_hdcOverlayMem, newOverlayX, newOverlayY, newWidth, 3, PATINVERT);
670+
::PatBlt(_hdcOverlayMem, newOverlayX, newOverlayY + newHeight - 3, newWidth, 3, PATINVERT);
671+
::PatBlt(_hdcOverlayMem, newOverlayX, newOverlayY, 3, newHeight, PATINVERT);
672+
::PatBlt(_hdcOverlayMem, newOverlayX + newWidth - 3, newOverlayY, 3, newHeight, PATINVERT);
673+
::SelectObject(_hdcOverlayMem, hOldBrush);
674+
}
675+
676+
// Copy to overlay window (only changed region for performance)
677+
LONG copyMinX = newOverlayX;
678+
LONG copyMinY = newOverlayY;
679+
LONG copyMaxX = newOverlayX + newWidth;
680+
LONG copyMaxY = newOverlayY + newHeight;
681+
682+
if (_bPtOldValid)
683+
{
684+
LONG oldOverlayX = rcOldAbsolute.left - _xVirtScreen;
685+
LONG oldOverlayY = rcOldAbsolute.top - _yVirtScreen;
686+
LONG oldWidth = rcOldAbsolute.right;
687+
LONG oldHeight = rcOldAbsolute.bottom;
688+
689+
if (oldOverlayX < copyMinX) copyMinX = oldOverlayX;
690+
if (oldOverlayY < copyMinY) copyMinY = oldOverlayY;
691+
if (oldOverlayX + oldWidth > copyMaxX) copyMaxX = oldOverlayX + oldWidth;
692+
if (oldOverlayY + oldHeight > copyMaxY) copyMaxY = oldOverlayY + oldHeight;
602693
}
603-
::BitBlt(_hdc, rc.left, rc.top, rc.right, rc.bottom, hdcMem, 0, 0, SRCCOPY);
604694

605-
SelectObject(hdcMem, hbrushOrig);
606-
SelectObject(hdcMem, hbmOrig);
607-
DeleteObject(hBm);
608-
DeleteDC(hdcMem);
695+
::BitBlt(_hdcOverlay, copyMinX, copyMinY, copyMaxX - copyMinX, copyMaxY - copyMinY,
696+
_hdcOverlayMem, copyMinX, copyMinY, SRCCOPY);
609697

610698
if (pPt == NULL)
611699
{
612-
#if defined(USE_LOCKWINDOWUPDATE)
613-
::LockWindowUpdate(NULL);
614-
#endif
700+
destroyOverlayWindow();
615701
_bPtOldValid = FALSE;
616-
if (_hdc)
617-
{
618-
::ReleaseDC(0, _hdc);
619-
_hdc = NULL;
620-
}
621702
}
622703
else
704+
{
623705
_bPtOldValid = TRUE;
706+
}
624707
}
625708

626-
627709
void Gripper::getMousePoints(const POINT& pt, POINT& ptPrev)
628710
{
629711
ptPrev = _ptOld;
@@ -842,4 +924,3 @@ void Gripper::initTabInformation()
842924
_tcItem.cchTextMax = 64;
843925
::SendMessage(_hTabSource, TCM_GETITEM, _iItem, reinterpret_cast<LPARAM>(&_tcItem));
844926
}
845-

0 commit comments

Comments
 (0)