1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2019-2021 - TortoiseGit
4 // Copyright (C) 2003-2006, 2008-2013, 2017, 2020 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "SplitterControl.h"
28 static char THIS_FILE
[] = __FILE__
;
31 /////////////////////////////////////////////////////////////////////////////
34 // hCursor1 is for vertical one
35 // and hCursor2 is for horizontal one
36 static HCURSOR SplitterControl_hCursor1
= nullptr;
37 static HCURSOR SplitterControl_hCursor2
= nullptr;
39 CSplitterControl::CSplitterControl()
43 , m_bMouseOverControl(false)
49 m_AnimVarHot
= Animator::Instance().CreateAnimationVariable(0.0);
51 // GDI+ initialization
52 Gdiplus::GdiplusStartupInput input
;
53 Gdiplus::GdiplusStartup(&m_gdiPlusToken
, &input
, nullptr);
56 CSplitterControl::~CSplitterControl()
59 Gdiplus::GdiplusShutdown(m_gdiPlusToken
);
63 BEGIN_MESSAGE_MAP(CSplitterControl
, CStatic
)
70 ON_MESSAGE(WM_MOUSELEAVE
, OnMouseLeave
)
73 /////////////////////////////////////////////////////////////////////////////
74 // CSplitterControl message handlers
77 // Set style for splitter control
78 // nStyle = SPS_VERTICAL or SPS_HORIZONTAL
79 int CSplitterControl::SetSplitterStyle(int nStyle
)
81 int m_nOldStyle
= m_nType
;
85 int CSplitterControl::GetSplitterStyle()
90 void CSplitterControl::OnPaint()
92 CPaintDC
dcreal(this); // device context for painting
95 GetClientRect(rcClient
);
96 Gdiplus::Rect
rc(rcClient
.left
, rcClient
.top
, rcClient
.Width(), rcClient
.Height());
97 Gdiplus::Graphics
g(dcreal
);
100 c1
.SetFromCOLORREF(CTheme::Instance().IsDarkTheme() ? CTheme::darkBkColor
: GetSysColor(COLOR_3DFACE
));
102 c2
.SetFromCOLORREF(CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNSHADOW
)));
104 Gdiplus::SolidBrush
bkgBrush(c1
);
105 g
.FillRectangle(&bkgBrush
, rc
);
107 // m_AnimVarHot changes from 0.0 (not hot) to 1.0 (hot)
108 auto alpha
= Animator::GetValue(m_AnimVarHot
);
109 c1
.SetValue(Gdiplus::Color::MakeARGB(BYTE(alpha
*255.0), c1
.GetRed(), c1
.GetBlue(), c1
.GetGreen()));
110 c2
.SetValue(Gdiplus::Color::MakeARGB(BYTE(alpha
*255.0), c2
.GetRed(), c2
.GetBlue(), c2
.GetGreen()));
112 if (m_nType
== SPS_VERTICAL
)
114 Gdiplus::LinearGradientBrush
b1(Gdiplus::Point(rc
.GetLeft(), rc
.GetBottom()), Gdiplus::Point(rc
.GetLeft() + rc
.Width
/2, rc
.GetBottom()), c1
, c2
);
116 Gdiplus::LinearGradientBrush
b2(Gdiplus::Point(rc
.GetLeft() + rc
.Width
/ 2, rc
.GetBottom()), Gdiplus::Point(rc
.GetRight(), rc
.GetBottom()), c2
, c1
);
118 g
.FillRectangle(&b1
, Gdiplus::Rect(rcClient
.left
, rcClient
.top
, rcClient
.Width()/2, rcClient
.Height()));
119 g
.FillRectangle(&b2
, Gdiplus::Rect(rcClient
.left
+rcClient
.Width()/2, rcClient
.top
, rcClient
.Width()/2, rcClient
.Height()));
123 Gdiplus::LinearGradientBrush
b1(Gdiplus::Point(rc
.GetLeft(), rc
.GetBottom()), Gdiplus::Point(rc
.GetLeft(), rc
.GetTop() + rc
.Height
/ 2), c1
, c2
);
125 Gdiplus::LinearGradientBrush
b2(Gdiplus::Point(rc
.GetLeft(), rc
.GetTop() + rc
.Height
/ 2), Gdiplus::Point(rc
.GetLeft(), rc
.GetTop()), c2
, c1
);
127 g
.FillRectangle(&b1
, Gdiplus::Rect(rcClient
.left
, rcClient
.top
+ rcClient
.Height() / 2, rcClient
.Width(), rcClient
.Height()));
128 g
.FillRectangle(&b2
, Gdiplus::Rect(rcClient
.left
, rcClient
.top
, rcClient
.Width(), rcClient
.Height() / 2));
132 //dc.SetBkColor(GetSysColor(COLOR_3DFACE));
133 //dc.ExtTextOut(0, 0, ETO_OPAQUE, &rcClient, NULL, 0, NULL);
135 //auto c2 = ::GetSysColor(COLOR_BTNSHADOW);
136 //auto c1 = ::GetSysColor(COLOR_3DFACE);
138 //// m_AnimVarHot changes from 0.0 (not hot) to 1.0 (hot)
139 //auto fraction = Animator::GetValue(m_AnimVarHot);
141 //int r1 = static_cast<int>(GetRValue(c1)); int g1 = static_cast<int>(GetGValue(c1)); int b1 = static_cast<int>(GetBValue(c1));
142 //int r2 = static_cast<int>(GetRValue(c2)); int g2 = static_cast<int>(GetGValue(c2)); int b2 = static_cast<int>(GetBValue(c2));
143 //auto clr = RGB((r2 - r1)*fraction + r1, (g2 - g1)*fraction + g1, (b2 - b1)*fraction + b1);
146 //brush.CreateHatchBrush(HS_DIAGCROSS, clr);
148 //auto oldBrush = dc.SelectObject(&brush);
149 //rcClient.DeflateRect(1, 1, 1, 1);
150 //dc.FillRect(&rcClient, &brush);
151 //dc.SelectObject(&oldBrush);
155 void CSplitterControl::OnMouseMove(UINT nFlags
, CPoint point
)
159 CWnd
* pParent
= GetParent();
163 pParent
->ScreenToClient(&pt
);
175 GetParent()->ClientToScreen(&pt
);
179 CPoint
pt(m_nX
, m_nY
);
180 CWnd
* pOwner
= GetOwner();
181 if (pOwner
&& IsWindow(pOwner
->m_hWnd
))
185 pOwner
->GetClientRect(rc
);
186 pOwner
->ScreenToClient(&pt
);
189 if (m_nType
== SPS_VERTICAL
)
190 delta
= m_nX
- m_nSavePos
;
192 delta
= m_nY
- m_nSavePos
;
195 nmsp
.hdr
.hwndFrom
= m_hWnd
;
196 nmsp
.hdr
.idFrom
= GetDlgCtrlID();
197 nmsp
.hdr
.code
= SPN_SIZED
;
200 pOwner
->SendMessage(WM_NOTIFY
, nmsp
.hdr
.idFrom
, reinterpret_cast<LPARAM
>(&nmsp
));
201 if (m_nType
== SPS_VERTICAL
)
205 pOwner
->Invalidate();
209 else if (!m_bMouseOverControl
)
212 Tme
.cbSize
= sizeof(TRACKMOUSEEVENT
);
213 Tme
.dwFlags
= TME_LEAVE
;
214 Tme
.hwndTrack
= m_hWnd
;
215 TrackMouseEvent(&Tme
);
217 m_bMouseOverControl
= true;
218 auto transHot
= Animator::Instance().CreateLinearTransition(0.3, 1.0);
219 auto storyBoard
= Animator::Instance().CreateStoryBoard();
220 if (storyBoard
&& transHot
&& m_AnimVarHot
)
222 storyBoard
->AddTransition(m_AnimVarHot
, transHot
);
223 Animator::Instance().RunStoryBoard(storyBoard
, [this]()
225 if (!this->GetSafeHwnd())
227 InvalidateRect(nullptr, false);
231 CStatic::OnMouseMove(nFlags
, point
);
234 LRESULT
CSplitterControl::OnMouseLeave(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
236 if (m_bMouseOverControl
)
238 auto transHot
= Animator::Instance().CreateLinearTransition(0.3, 0.0);
239 auto storyBoard
= Animator::Instance().CreateStoryBoard();
240 if (storyBoard
&& transHot
&& m_AnimVarHot
)
242 storyBoard
->AddTransition(m_AnimVarHot
, transHot
);
243 Animator::Instance().RunStoryBoard(storyBoard
, [this]()
245 if (!this->GetSafeHwnd())
247 InvalidateRect(nullptr, false);
251 m_bMouseOverControl
= false;
255 BOOL
CSplitterControl::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
257 if (nHitTest
== HTCLIENT
)
259 (m_nType
== SPS_VERTICAL
)?(::SetCursor(SplitterControl_hCursor1
))
260 :(::SetCursor(SplitterControl_hCursor2
));
264 return CStatic::OnSetCursor(pWnd
, nHitTest
, message
);
267 void CSplitterControl::OnLButtonDown(UINT nFlags
, CPoint point
)
269 CStatic::OnLButtonDown(nFlags
, point
);
274 GetWindowRect(rcWnd
);
276 if (m_nType
== SPS_VERTICAL
)
277 m_nX
= rcWnd
.left
+ rcWnd
.Width() / 2;
280 m_nY
= rcWnd
.top
+ rcWnd
.Height() / 2;
282 if (m_nType
== SPS_VERTICAL
)
288 void CSplitterControl::OnLButtonUp(UINT nFlags
, CPoint point
)
290 CStatic::OnLButtonUp(nFlags
, point
);
291 m_bIsPressed
= false;
295 void CSplitterControl::MoveWindowTo(CPoint pt
)
300 pParent
= GetParent();
301 if (!pParent
|| !::IsWindow(pParent
->m_hWnd
))
304 pParent
->ScreenToClient(rc
);
305 if (m_nType
== SPS_VERTICAL
)
307 int nMidX
= (rc
.left
+ rc
.right
) / 2;
308 int dx
= pt
.x
- nMidX
;
309 rc
.OffsetRect(dx
, 0);
313 int nMidY
= (rc
.top
+ rc
.bottom
) / 2;
314 int dy
= pt
.y
- nMidY
;
315 rc
.OffsetRect(0, dy
);
320 HDWP
CSplitterControl::ChangeRect(HDWP hdwp
, CWnd
* pWnd
, int dleft
, int dtop
, int dright
, int dbottom
)
322 CWnd
* pParent
= pWnd
->GetParent();
323 if (pParent
&& ::IsWindow(pParent
->m_hWnd
))
326 pWnd
->GetWindowRect(rcWnd
);
327 pParent
->ScreenToClient(rcWnd
);
330 rcWnd
.right
+= dright
;
331 rcWnd
.bottom
+= dbottom
;
333 return DeferWindowPos(hdwp
, pWnd
->GetSafeHwnd(), nullptr, rcWnd
.left
, rcWnd
.top
, rcWnd
.Width(), rcWnd
.Height(), SWP_NOACTIVATE
| SWP_NOOWNERZORDER
| SWP_NOREDRAW
| SWP_NOZORDER
);
334 //pWnd->MoveWindow(rcWnd);
339 void CSplitterControl::SetRange(int nMin
, int nMax
)
345 // Set splitter range from (nRoot - nSubtraction) to (nRoot + nAddition)
347 // nRoot = <current position of the splitter>
348 void CSplitterControl::SetRange(int nSubtraction
, int nAddition
, int nRoot
)
353 GetWindowRect(rcWnd
);
354 if (m_nType
== SPS_VERTICAL
)
355 nRoot
= rcWnd
.left
+ rcWnd
.Width() / 2;
356 else // if m_nType == SPS_HORIZONTAL
357 nRoot
= rcWnd
.top
+ rcWnd
.Height() / 2;
359 m_nMin
= nRoot
- nSubtraction
;
360 m_nMax
= nRoot
+ nAddition
;
362 void CSplitterControl::PreSubclassWindow()
364 // Enable notifications - CStatic has this disabled by default
365 ModifyStyle(0, SS_NOTIFY
);
370 // Determine default type base on it's size.
371 m_nType
= (rc
.Width() < rc
.Height())?SPS_VERTICAL
:SPS_HORIZONTAL
;
373 if (!SplitterControl_hCursor1
)
375 SplitterControl_hCursor1
= AfxGetApp()->LoadStandardCursor(IDC_SIZEWE
);
376 SplitterControl_hCursor2
= AfxGetApp()->LoadStandardCursor(IDC_SIZENS
);
379 // force the splitter not to be splitted.
383 CStatic::PreSubclassWindow();
386 BOOL
CSplitterControl::OnEraseBkgnd(CDC
* /*pDC*/)