Fix color of the splitter control in dark mode
[TortoiseGit.git] / src / Utils / MiscUI / SplitterControl.cpp
blob0ef05af9853f86773ecf6e80b42724ed00e7a003
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.
20 #include "stdafx.h"
21 #include "SplitterControl.h"
22 #include "MyMemDC.h"
23 #include "Theme.h"
25 #ifdef _DEBUG
26 #define new DEBUG_NEW
27 #undef THIS_FILE
28 static char THIS_FILE[] = __FILE__;
29 #endif
31 /////////////////////////////////////////////////////////////////////////////
32 // CSplitterControl
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()
40 : m_bIsPressed(false)
41 , m_nMin(-1)
42 , m_nMax(-1)
43 , m_bMouseOverControl(false)
44 , m_nType(0)
45 , m_nX(0)
46 , m_nY(0)
47 , m_nSavePos(0)
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()
58 // GDI+ cleanup
59 Gdiplus::GdiplusShutdown(m_gdiPlusToken);
63 BEGIN_MESSAGE_MAP(CSplitterControl, CStatic)
64 ON_WM_PAINT()
65 ON_WM_MOUSEMOVE()
66 ON_WM_SETCURSOR()
67 ON_WM_LBUTTONDOWN()
68 ON_WM_LBUTTONUP()
69 ON_WM_ERASEBKGND()
70 ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
71 END_MESSAGE_MAP()
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;
82 m_nType = nStyle;
83 return m_nOldStyle;
85 int CSplitterControl::GetSplitterStyle()
87 return m_nType;
90 void CSplitterControl::OnPaint()
92 CPaintDC dcreal(this); // device context for painting
94 CRect rcClient;
95 GetClientRect(rcClient);
96 Gdiplus::Rect rc(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height());
97 Gdiplus::Graphics g(dcreal);
99 Gdiplus::Color c1;
100 c1.SetFromCOLORREF(CTheme::Instance().IsDarkTheme() ? CTheme::darkBkColor : GetSysColor(COLOR_3DFACE));
101 Gdiplus::Color c2;
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()));
121 else
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);
145 //CBrush brush;
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)
157 if (m_bIsPressed)
159 CWnd * pParent = GetParent();
161 CPoint pt = point;
162 ClientToScreen(&pt);
163 pParent->ScreenToClient(&pt);
165 if (pt.x < m_nMin)
166 pt.x = m_nMin;
167 if (pt.y < m_nMin)
168 pt.y = m_nMin;
170 if (pt.x > m_nMax)
171 pt.x = m_nMax;
172 if (pt.y > m_nMax)
173 pt.y = m_nMax;
175 GetParent()->ClientToScreen(&pt);
176 m_nX = pt.x;
177 m_nY = pt.y;
179 CPoint pt(m_nX, m_nY);
180 CWnd* pOwner = GetOwner();
181 if (pOwner && IsWindow(pOwner->m_hWnd))
183 CRect rc;
184 int delta;
185 pOwner->GetClientRect(rc);
186 pOwner->ScreenToClient(&pt);
187 MoveWindowTo(pt);
189 if (m_nType == SPS_VERTICAL)
190 delta = m_nX - m_nSavePos;
191 else
192 delta = m_nY - m_nSavePos;
194 SPC_NMHDR nmsp;
195 nmsp.hdr.hwndFrom = m_hWnd;
196 nmsp.hdr.idFrom = GetDlgCtrlID();
197 nmsp.hdr.code = SPN_SIZED;
198 nmsp.delta = delta;
200 pOwner->SendMessage(WM_NOTIFY, nmsp.hdr.idFrom, reinterpret_cast<LPARAM>(&nmsp));
201 if (m_nType == SPS_VERTICAL)
202 m_nSavePos = m_nX;
203 else
204 m_nSavePos = m_nY;
205 pOwner->Invalidate();
209 else if (!m_bMouseOverControl)
211 TRACKMOUSEEVENT Tme;
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())
226 return;
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())
246 return;
247 InvalidateRect(nullptr, false);
251 m_bMouseOverControl = false;
252 return 0;
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));
261 return 0;
263 else
264 return CStatic::OnSetCursor(pWnd, nHitTest, message);
267 void CSplitterControl::OnLButtonDown(UINT nFlags, CPoint point)
269 CStatic::OnLButtonDown(nFlags, point);
271 m_bIsPressed = true;
272 SetCapture();
273 CRect rcWnd;
274 GetWindowRect(rcWnd);
276 if (m_nType == SPS_VERTICAL)
277 m_nX = rcWnd.left + rcWnd.Width() / 2;
279 else
280 m_nY = rcWnd.top + rcWnd.Height() / 2;
282 if (m_nType == SPS_VERTICAL)
283 m_nSavePos = m_nX;
284 else
285 m_nSavePos = m_nY;
288 void CSplitterControl::OnLButtonUp(UINT nFlags, CPoint point)
290 CStatic::OnLButtonUp(nFlags, point);
291 m_bIsPressed = false;
292 ReleaseCapture();
295 void CSplitterControl::MoveWindowTo(CPoint pt)
297 CRect rc;
298 GetWindowRect(rc);
299 CWnd* pParent;
300 pParent = GetParent();
301 if (!pParent || !::IsWindow(pParent->m_hWnd))
302 return;
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);
311 else
313 int nMidY = (rc.top + rc.bottom) / 2;
314 int dy = pt.y - nMidY;
315 rc.OffsetRect(0, dy);
317 MoveWindow(rc);
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))
325 CRect rcWnd;
326 pWnd->GetWindowRect(rcWnd);
327 pParent->ScreenToClient(rcWnd);
328 rcWnd.left += dleft;
329 rcWnd.top += dtop;
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);
336 return hdwp;
339 void CSplitterControl::SetRange(int nMin, int nMax)
341 m_nMin = nMin;
342 m_nMax = nMax;
345 // Set splitter range from (nRoot - nSubtraction) to (nRoot + nAddition)
346 // If (nRoot < 0)
347 // nRoot = <current position of the splitter>
348 void CSplitterControl::SetRange(int nSubtraction, int nAddition, int nRoot)
350 if (nRoot < 0)
352 CRect rcWnd;
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);
367 CRect rc;
368 GetClientRect(rc);
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.
380 SetRange(0, 0, -1);
383 CStatic::PreSubclassWindow();
386 BOOL CSplitterControl::OnEraseBkgnd(CDC* /*pDC*/)
388 return TRUE;