#pragma once #include "CIconOverlayWindow.h" #include #include "ppresources.h" #include "win32_utility.h" class CMiddleDragCommon { public: enum { KTimerID = 0x389675f8, KTimerPeriod = 15, }; static double myPow(double p_val, double p_exp); static double ProcessMiddleDragDeltaInternal(double p_delta); static double radiusHelper(double p_x, double p_y); static int mySGN(LONG v); static int32_t Round(double val, double & acc); static LONG LineToPixelsHelper(LONG & p_overflow, LONG p_pixels, LONG p_dpi, LONG p_lineWidth); }; template class CMiddleDragImpl : public TBase, protected CMiddleDragCommon { private: typedef CMiddleDragImpl TSelf; public: template CMiddleDragImpl( arg_t && ... arg ) : TBase(std::forward(arg) ... ) {} BEGIN_MSG_MAP_EX(TSelf) MESSAGE_HANDLER(WM_TIMER,OnTimer); MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged); MESSAGE_HANDLER(WM_MBUTTONDOWN,OnMButtonDown); MESSAGE_HANDLER(WM_MBUTTONDBLCLK,OnMButtonDown); MESSAGE_HANDLER(WM_MBUTTONUP,OnMButtonUp); MESSAGE_HANDLER(WM_LBUTTONDOWN,OnMouseAction); MESSAGE_HANDLER(WM_RBUTTONDOWN,OnMouseAction); MESSAGE_HANDLER(WM_LBUTTONDBLCLK,OnMouseAction); MESSAGE_HANDLER(WM_RBUTTONDBLCLK,OnMouseAction); MESSAGE_HANDLER(WM_LBUTTONUP,OnMouseAction); MESSAGE_HANDLER(WM_RBUTTONUP,OnMouseAction); MESSAGE_HANDLER(WM_XBUTTONDOWN,OnMouseAction); MESSAGE_HANDLER(WM_XBUTTONDBLCLK,OnMouseAction); MESSAGE_HANDLER(WM_XBUTTONUP,OnMouseAction); MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMove); MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru); CHAIN_MSG_MAP(TBase); END_MSG_MAP() protected: bool CanSmoothScroll() const { return !m_active; } private: bool m_active = false, m_dragged = false; CPoint m_base; CIconOverlayWindow m_overlay; double m_accX = 0, m_accY = 0; LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { if (m_overlay != NULL) m_overlay.DestroyWindow(); bHandled = FALSE; return 0; } LRESULT OnMButtonUp(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) { if (m_active /*&& m_dragged*/) { EndDrag(); } return 0; } LRESULT OnMButtonDown(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) { if (m_active) { EndDrag(); return 0; } EndDrag(); this->SetFocus(); CPoint pt(p_lp); WIN32_OP_D( this->ClientToScreen(&pt) ); StartDrag(pt); return 0; } LRESULT OnMouseMove(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) { if (m_active) { if (!m_dragged) { CPoint pt(p_lp); WIN32_OP_D( this->ClientToScreen(&pt) ); if (pt != m_base) { m_dragged = true; } } bHandled = TRUE; } else { bHandled = FALSE; } return 0; } LRESULT OnMouseAction(UINT,WPARAM,LPARAM,BOOL& bHandled) { EndDrag(); bHandled = FALSE; return 0; } void StartDrag(CPoint const & p_point) { ::SetCapture(NULL); if (m_overlay == NULL) { PFC_ASSERT( this->m_hWnd != NULL ); if (m_overlay.Create(*this) == NULL) {PFC_ASSERT(!"Should not get here!"); return;} HANDLE temp; WIN32_OP_D( (temp = LoadImage(GetThisModuleHandle(),MAKEINTRESOURCE(CPPUIResources::get_IDI_SCROLL()),IMAGE_ICON,32,32,LR_DEFAULTCOLOR)) != NULL ); m_overlay.AttachIcon((HICON) temp); } //todo sanity checks - don't drag when the entire content is visible, perhaps use a different icon when only vertical or horizontal drag is possible SetCursor(::LoadCursor(NULL,IDC_SIZEALL)); m_active = true; m_dragged = false; m_base = p_point; m_accX = m_accY = 0; this->SetCapture(); this->SetTimer(KTimerID,KTimerPeriod); { CSize radius(16,16); CPoint center (p_point); CRect rect(center - radius, center + radius); m_overlay.SetWindowPos(HWND_TOPMOST,rect,SWP_SHOWWINDOW | SWP_NOACTIVATE); } } void EndDrag() { if (m_active) ::SetCapture(NULL); } void HandleEndDrag() { if (m_active) { m_active = false; this->KillTimer(KTimerID); if (m_overlay != NULL) m_overlay.ShowWindow(SW_HIDE); } } LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL& bHandled) { HandleEndDrag(); bHandled = FALSE; return 0; } LRESULT OnTimer(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) { switch(p_wp) { case KTimerID: this->MoveViewOriginDelta(ProcessMiddleDragDelta(CPoint(GetCursorPos()) - m_base)); return 0; default: bHandled = FALSE; return 0; } } CPoint ProcessMiddleDragDelta(CPoint p_delta) { const CSize dpi = QueryScreenDPIEx(); if (dpi.cx <= 0 || dpi.cy <= 0 || p_delta == CPoint(0, 0)) return CPoint(0, 0); const double dpiMulX = 96.0 / (double)dpi.cx; const double dpiMulY = 96.0 / (double)dpi.cy; const double deltaTotal = ProcessMiddleDragDeltaInternal(radiusHelper((double)p_delta.x * dpiMulX, (double)p_delta.y * dpiMulY)); double xVal = 0, yVal = 0; if (p_delta.x == 0) { yVal = deltaTotal; } else if (p_delta.y == 0) { xVal = deltaTotal; } else { const double ratio = (double)p_delta.x / (double)p_delta.y; yVal = sqrt((deltaTotal*deltaTotal) / (1.0 + (ratio*ratio))); xVal = yVal * ratio; } xVal = mySGN(p_delta.x) * fabs(xVal); yVal = mySGN(p_delta.y) * fabs(yVal); return CPoint(Round(xVal / dpiMulX, m_accX), Round(yVal / dpiMulY, m_accY)); } }; template class CMiddleDragWrapper : public TBase { private: typedef CMiddleDragWrapper TSelf; public: template CMiddleDragWrapper(arg_t && ... arg ) : TBase(std::forward(arg) ... ) { m_overflow = CPoint(0, 0); m_lineWidth = CSize(4, 16); } BEGIN_MSG_MAP_EX(TSelf) MESSAGE_HANDLER(WM_CAPTURECHANGED,OnCaptureChanged); CHAIN_MSG_MAP(TBase) END_MSG_MAP() void MoveViewOriginDelta(CPoint p_delta) { MoveViewOriginDeltaLines( MiddleDrag_PixelsToLines( m_overflow, p_delta ) ); } void MoveViewOriginDeltaLines(CPoint p_delta) { FireScrollMessage(WM_HSCROLL,p_delta.x); FireScrollMessage(WM_VSCROLL,p_delta.y); } void SetLineWidth(CSize p_width) {m_lineWidth = p_width;} private: void FireScrollMessage(UINT p_msg,int p_delta) { UINT count = (UINT)(p_delta<0?-p_delta:p_delta); const UINT which = (p_msg == WM_HSCROLL ? SB_HORZ : SB_VERT); SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_PAGE | SIF_RANGE; if (!this->GetScrollInfo(which, &si) || si.nPage <= 0) return; while(count >= si.nPage) { const WPARAM code = p_delta < 0 ? SB_PAGEUP : SB_PAGEDOWN; this->SendMessage(p_msg, code, 0); count -= si.nPage; } const WPARAM code = p_delta < 0 ? SB_LINEUP : SB_LINEDOWN; for(UINT walk = 0; walk < count; ++walk) this->SendMessage(p_msg, code, 0); } LRESULT OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL& bHandled) { m_overflow = CPoint(0,0); bHandled = FALSE; return 0; } CPoint MiddleDrag_PixelsToLines(CPoint & p_overflow,CPoint p_pixels) { const CSize dpi = QueryScreenDPIEx(); if (dpi.cx <= 0 || dpi.cy <= 0) return CPoint(0,0); CPoint pt; pt.x = CMiddleDragCommon::LineToPixelsHelper(p_overflow.x,p_pixels.x,dpi.cx,m_lineWidth.cx); pt.y = CMiddleDragCommon::LineToPixelsHelper(p_overflow.y,p_pixels.y,dpi.cy,m_lineWidth.cy); return pt; } CSize m_lineWidth; CPoint m_overflow; }; template class CWindow_DummyMsgMap : public TBase , public CMessageMap { public: BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) {return FALSE;} }; typedef CMiddleDragImpl > > CMiddleDragImplSimple; template class CContainedWindow_MsgMap : public CContainedWindowT > { protected: CContainedWindow_MsgMap() : CContainedWindowT >(msgMapCast(this)) {} private: template static CMessageMap * msgMapCast(t* arg) { return arg; } }; template class CMiddleDragImplCtrlHook : public CMiddleDragImpl > > { }; template class CMiddleDragImplCtrlHookEx : public CMiddleDragImplCtrlHook { public: CMiddleDragImplCtrlHookEx(CMessageMap * hook, DWORD hookMapID = 0) : m_hook(*hook), m_hookMapID(hookMapID) {} BEGIN_MSG_MAP_EX(CMiddleDragImplCtrlHookEx) CHAIN_MSG_MAP(CMiddleDragImplCtrlHook) CHAIN_MSG_MAP_ALT_MEMBER(m_hook,m_hookMapID); END_MSG_MAP() private: DWORD const m_hookMapID; CMessageMap & m_hook; };