1 // Copyright 2014 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "xfa/fwl/cfwl_scrollbar.h"
8
9 #include <algorithm>
10 #include <memory>
11 #include <utility>
12
13 #include "third_party/base/ptr_util.h"
14 #include "xfa/fwl/cfwl_messagemouse.h"
15 #include "xfa/fwl/cfwl_messagemousewheel.h"
16 #include "xfa/fwl/cfwl_notedriver.h"
17 #include "xfa/fwl/cfwl_themebackground.h"
18 #include "xfa/fwl/cfwl_themepart.h"
19 #include "xfa/fwl/cfwl_timerinfo.h"
20 #include "xfa/fwl/ifwl_themeprovider.h"
21
22 #define FWL_SCROLLBAR_Elapse 500
23
24 namespace {
25
26 const float kMinThumbSize = 5.0f;
27
28 } // namespace
29
CFWL_ScrollBar(const CFWL_App * app,std::unique_ptr<CFWL_WidgetProperties> properties,CFWL_Widget * pOuter)30 CFWL_ScrollBar::CFWL_ScrollBar(
31 const CFWL_App* app,
32 std::unique_ptr<CFWL_WidgetProperties> properties,
33 CFWL_Widget* pOuter)
34 : CFWL_Widget(app, std::move(properties), pOuter),
35 m_pTimerInfo(nullptr),
36 m_fRangeMin(0),
37 m_fRangeMax(-1),
38 m_fPageSize(0),
39 m_fStepSize(0),
40 m_fPos(0),
41 m_fTrackPos(0),
42 m_iMinButtonState(CFWL_PartState_Normal),
43 m_iMaxButtonState(CFWL_PartState_Normal),
44 m_iThumbButtonState(CFWL_PartState_Normal),
45 m_iMinTrackState(CFWL_PartState_Normal),
46 m_iMaxTrackState(CFWL_PartState_Normal),
47 m_fLastTrackPos(0),
48 m_iMouseWheel(0),
49 m_bMouseDown(false),
50 m_fButtonLen(0),
51 m_bMinSize(false),
52 m_Timer(this) {
53 m_rtClient.Reset();
54 m_rtThumb.Reset();
55 m_rtMinBtn.Reset();
56 m_rtMaxBtn.Reset();
57 m_rtMinTrack.Reset();
58 m_rtMaxTrack.Reset();
59 }
60
~CFWL_ScrollBar()61 CFWL_ScrollBar::~CFWL_ScrollBar() {}
62
GetClassID() const63 FWL_Type CFWL_ScrollBar::GetClassID() const {
64 return FWL_Type::ScrollBar;
65 }
66
Update()67 void CFWL_ScrollBar::Update() {
68 if (IsLocked())
69 return;
70 if (!m_pProperties->m_pThemeProvider)
71 m_pProperties->m_pThemeProvider = GetAvailableTheme();
72
73 Layout();
74 }
75
DrawWidget(CFX_Graphics * pGraphics,const CFX_Matrix * pMatrix)76 void CFWL_ScrollBar::DrawWidget(CFX_Graphics* pGraphics,
77 const CFX_Matrix* pMatrix) {
78 if (!pGraphics)
79 return;
80 if (!m_pProperties->m_pThemeProvider)
81 return;
82
83 IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider;
84 if (HasBorder())
85 DrawBorder(pGraphics, CFWL_Part::Border, pTheme, pMatrix);
86 DrawTrack(pGraphics, pTheme, true, pMatrix);
87 DrawTrack(pGraphics, pTheme, false, pMatrix);
88 DrawArrowBtn(pGraphics, pTheme, true, pMatrix);
89 DrawArrowBtn(pGraphics, pTheme, false, pMatrix);
90 DrawThumb(pGraphics, pTheme, pMatrix);
91 }
92
SetTrackPos(FX_FLOAT fTrackPos)93 void CFWL_ScrollBar::SetTrackPos(FX_FLOAT fTrackPos) {
94 m_fTrackPos = fTrackPos;
95 m_rtThumb = CalcThumbButtonRect(m_rtThumb);
96 m_rtMinTrack = CalcMinTrackRect(m_rtMinTrack);
97 m_rtMaxTrack = CalcMaxTrackRect(m_rtMaxTrack);
98 }
99
DoScroll(CFWL_EventScroll::Code dwCode,FX_FLOAT fPos)100 bool CFWL_ScrollBar::DoScroll(CFWL_EventScroll::Code dwCode, FX_FLOAT fPos) {
101 if (dwCode == CFWL_EventScroll::Code::None)
102 return false;
103 return OnScroll(dwCode, fPos);
104 }
105
DrawTrack(CFX_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,bool bLower,const CFX_Matrix * pMatrix)106 void CFWL_ScrollBar::DrawTrack(CFX_Graphics* pGraphics,
107 IFWL_ThemeProvider* pTheme,
108 bool bLower,
109 const CFX_Matrix* pMatrix) {
110 CFWL_ThemeBackground param;
111 param.m_pWidget = this;
112 param.m_iPart = bLower ? CFWL_Part::LowerTrack : CFWL_Part::UpperTrack;
113 param.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
114 ? CFWL_PartState_Disabled
115 : (bLower ? m_iMinTrackState : m_iMaxTrackState);
116 param.m_pGraphics = pGraphics;
117 param.m_matrix.Concat(*pMatrix);
118 param.m_rtPart = bLower ? m_rtMinTrack : m_rtMaxTrack;
119 pTheme->DrawBackground(¶m);
120 }
121
DrawArrowBtn(CFX_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,bool bMinBtn,const CFX_Matrix * pMatrix)122 void CFWL_ScrollBar::DrawArrowBtn(CFX_Graphics* pGraphics,
123 IFWL_ThemeProvider* pTheme,
124 bool bMinBtn,
125 const CFX_Matrix* pMatrix) {
126 CFWL_ThemeBackground param;
127 param.m_pWidget = this;
128 param.m_iPart = bMinBtn ? CFWL_Part::ForeArrow : CFWL_Part::BackArrow;
129 param.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
130 ? CFWL_PartState_Disabled
131 : (bMinBtn ? m_iMinButtonState : m_iMaxButtonState);
132 param.m_pGraphics = pGraphics;
133 param.m_matrix.Concat(*pMatrix);
134 param.m_rtPart = bMinBtn ? m_rtMinBtn : m_rtMaxBtn;
135 if (param.m_rtPart.height > 0 && param.m_rtPart.width > 0)
136 pTheme->DrawBackground(¶m);
137 }
138
DrawThumb(CFX_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,const CFX_Matrix * pMatrix)139 void CFWL_ScrollBar::DrawThumb(CFX_Graphics* pGraphics,
140 IFWL_ThemeProvider* pTheme,
141 const CFX_Matrix* pMatrix) {
142 CFWL_ThemeBackground param;
143 param.m_pWidget = this;
144 param.m_iPart = CFWL_Part::Thumb;
145 param.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
146 ? CFWL_PartState_Disabled
147 : m_iThumbButtonState;
148 param.m_pGraphics = pGraphics;
149 param.m_matrix.Concat(*pMatrix);
150 param.m_rtPart = m_rtThumb;
151 pTheme->DrawBackground(¶m);
152 }
153
Layout()154 void CFWL_ScrollBar::Layout() {
155 m_rtClient = GetClientRect();
156
157 CalcButtonLen();
158 m_rtMinBtn = CalcMinButtonRect();
159 m_rtMaxBtn = CalcMaxButtonRect();
160 m_rtThumb = CalcThumbButtonRect(m_rtThumb);
161 m_rtMinTrack = CalcMinTrackRect(m_rtMinTrack);
162 m_rtMaxTrack = CalcMaxTrackRect(m_rtMaxTrack);
163 }
164
CalcButtonLen()165 void CFWL_ScrollBar::CalcButtonLen() {
166 m_fButtonLen = IsVertical() ? m_rtClient.width : m_rtClient.height;
167 FX_FLOAT fLength = IsVertical() ? m_rtClient.height : m_rtClient.width;
168 if (fLength < m_fButtonLen * 2) {
169 m_fButtonLen = fLength / 2;
170 m_bMinSize = true;
171 } else {
172 m_bMinSize = false;
173 }
174 }
175
CalcMinButtonRect()176 CFX_RectF CFWL_ScrollBar::CalcMinButtonRect() {
177 if (IsVertical())
178 return CFX_RectF(m_rtClient.TopLeft(), m_rtClient.width, m_fButtonLen);
179 return CFX_RectF(m_rtClient.TopLeft(), m_fButtonLen, m_rtClient.height);
180 }
181
CalcMaxButtonRect()182 CFX_RectF CFWL_ScrollBar::CalcMaxButtonRect() {
183 if (IsVertical()) {
184 return CFX_RectF(m_rtClient.left, m_rtClient.bottom() - m_fButtonLen,
185 m_rtClient.width, m_fButtonLen);
186 }
187 return CFX_RectF(m_rtClient.right() - m_fButtonLen, m_rtClient.top,
188 m_fButtonLen, m_rtClient.height);
189 }
190
CalcThumbButtonRect(const CFX_RectF & rtThumb)191 CFX_RectF CFWL_ScrollBar::CalcThumbButtonRect(const CFX_RectF& rtThumb) {
192 CFX_RectF rect;
193 if (!IsEnabled())
194 return rect;
195
196 if (m_bMinSize) {
197 rect.left = rtThumb.left;
198 rect.top = rtThumb.top;
199 return rect;
200 }
201
202 FX_FLOAT fRange = m_fRangeMax - m_fRangeMin;
203 if (fRange < 0) {
204 if (IsVertical()) {
205 return CFX_RectF(m_rtClient.left, m_rtMaxBtn.bottom(), m_rtClient.width,
206 0);
207 }
208 return CFX_RectF(m_rtMaxBtn.right(), m_rtClient.top, 0, m_rtClient.height);
209 }
210
211 CFX_RectF rtClient = m_rtClient;
212 FX_FLOAT fLength = IsVertical() ? rtClient.height : rtClient.width;
213 FX_FLOAT fSize = m_fButtonLen;
214 fLength -= fSize * 2.0f;
215 if (fLength < fSize)
216 fLength = 0.0f;
217
218 FX_FLOAT fThumbSize = fLength * fLength / (fRange + fLength);
219 fThumbSize = std::max(fThumbSize, kMinThumbSize);
220
221 FX_FLOAT fDiff = std::max(fLength - fThumbSize, 0.0f);
222 FX_FLOAT fTrackPos =
223 std::max(std::min(m_fTrackPos, m_fRangeMax), m_fRangeMin);
224 if (!fRange)
225 return rect;
226
227 FX_FLOAT iPos = fSize + fDiff * (fTrackPos - m_fRangeMin) / fRange;
228 rect.left = rtClient.left;
229 rect.top = rtClient.top;
230 if (IsVertical()) {
231 rect.top += iPos;
232 rect.width = rtClient.width;
233 rect.height = fThumbSize;
234 } else {
235 rect.left += iPos;
236 rect.width = fThumbSize;
237 rect.height = rtClient.height;
238 }
239 return rect;
240 }
241
CalcMinTrackRect(const CFX_RectF & rtMinRect)242 CFX_RectF CFWL_ScrollBar::CalcMinTrackRect(const CFX_RectF& rtMinRect) {
243 CFX_RectF rect;
244 if (m_bMinSize) {
245 rect.left = rtMinRect.left;
246 rect.top = rtMinRect.top;
247 return rect;
248 }
249
250 rect.left = m_rtClient.left;
251 rect.top = m_rtClient.top;
252 if (IsVertical()) {
253 rect.width = m_rtClient.width;
254 rect.height = (m_rtThumb.top + m_rtThumb.bottom()) / 2;
255 } else {
256 rect.width = (m_rtThumb.left + m_rtThumb.right()) / 2;
257 rect.height = m_rtClient.height;
258 }
259 return rect;
260 }
261
CalcMaxTrackRect(const CFX_RectF & rtMaxRect)262 CFX_RectF CFWL_ScrollBar::CalcMaxTrackRect(const CFX_RectF& rtMaxRect) {
263 if (m_bMinSize)
264 return CFX_RectF(rtMaxRect.TopLeft(), 0, 0);
265
266 if (IsVertical()) {
267 FX_FLOAT iy = (m_rtThumb.top + m_rtThumb.bottom()) / 2;
268 return CFX_RectF(m_rtClient.left, iy, m_rtClient.width,
269 m_rtClient.bottom() - iy);
270 }
271
272 FX_FLOAT ix = (m_rtThumb.left + m_rtThumb.right()) / 2;
273 return CFX_RectF(ix, m_rtClient.top, m_rtClient.height - ix,
274 m_rtClient.height);
275 }
276
GetTrackPointPos(const CFX_PointF & point)277 FX_FLOAT CFWL_ScrollBar::GetTrackPointPos(const CFX_PointF& point) {
278 CFX_PointF diff = point - m_cpTrackPoint;
279 FX_FLOAT fRange = m_fRangeMax - m_fRangeMin;
280 FX_FLOAT fPos;
281
282 if (IsVertical()) {
283 fPos = fRange * diff.y /
284 (m_rtMaxBtn.top - m_rtMinBtn.bottom() - m_rtThumb.height);
285 } else {
286 fPos = fRange * diff.x /
287 (m_rtMaxBtn.left - m_rtMinBtn.right() - m_rtThumb.width);
288 }
289
290 fPos += m_fLastTrackPos;
291 return std::min(std::max(fPos, m_fRangeMin), m_fRangeMax);
292 }
293
SendEvent()294 bool CFWL_ScrollBar::SendEvent() {
295 if (m_iMinButtonState == CFWL_PartState_Pressed) {
296 DoScroll(CFWL_EventScroll::Code::StepBackward, m_fTrackPos);
297 return false;
298 }
299 if (m_iMaxButtonState == CFWL_PartState_Pressed) {
300 DoScroll(CFWL_EventScroll::Code::StepForward, m_fTrackPos);
301 return false;
302 }
303 if (m_iMinTrackState == CFWL_PartState_Pressed) {
304 DoScroll(CFWL_EventScroll::Code::PageBackward, m_fTrackPos);
305 return m_rtThumb.Contains(m_cpTrackPoint);
306 }
307 if (m_iMaxTrackState == CFWL_PartState_Pressed) {
308 DoScroll(CFWL_EventScroll::Code::PageForward, m_fTrackPos);
309 return m_rtThumb.Contains(m_cpTrackPoint);
310 }
311 if (m_iMouseWheel) {
312 CFWL_EventScroll::Code dwCode = m_iMouseWheel < 0
313 ? CFWL_EventScroll::Code::StepForward
314 : CFWL_EventScroll::Code::StepBackward;
315 DoScroll(dwCode, m_fTrackPos);
316 }
317 return true;
318 }
319
OnScroll(CFWL_EventScroll::Code dwCode,FX_FLOAT fPos)320 bool CFWL_ScrollBar::OnScroll(CFWL_EventScroll::Code dwCode, FX_FLOAT fPos) {
321 CFWL_EventScroll ev(this);
322 ev.m_iScrollCode = dwCode;
323 ev.m_fPos = fPos;
324 DispatchEvent(&ev);
325 return true;
326 }
327
OnProcessMessage(CFWL_Message * pMessage)328 void CFWL_ScrollBar::OnProcessMessage(CFWL_Message* pMessage) {
329 if (!pMessage)
330 return;
331
332 CFWL_Message::Type type = pMessage->GetType();
333 if (type == CFWL_Message::Type::Mouse) {
334 CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
335 switch (pMsg->m_dwCmd) {
336 case FWL_MouseCommand::LeftButtonDown:
337 OnLButtonDown(pMsg->m_pos);
338 break;
339 case FWL_MouseCommand::LeftButtonUp:
340 OnLButtonUp(pMsg->m_pos);
341 break;
342 case FWL_MouseCommand::Move:
343 OnMouseMove(pMsg->m_pos);
344 break;
345 case FWL_MouseCommand::Leave:
346 OnMouseLeave();
347 break;
348 default:
349 break;
350 }
351 } else if (type == CFWL_Message::Type::MouseWheel) {
352 CFWL_MessageMouseWheel* pMsg =
353 static_cast<CFWL_MessageMouseWheel*>(pMessage);
354 OnMouseWheel(pMsg->m_delta);
355 }
356 }
357
OnDrawWidget(CFX_Graphics * pGraphics,const CFX_Matrix * pMatrix)358 void CFWL_ScrollBar::OnDrawWidget(CFX_Graphics* pGraphics,
359 const CFX_Matrix* pMatrix) {
360 DrawWidget(pGraphics, pMatrix);
361 }
362
OnLButtonDown(const CFX_PointF & point)363 void CFWL_ScrollBar::OnLButtonDown(const CFX_PointF& point) {
364 if (!IsEnabled())
365 return;
366
367 m_bMouseDown = true;
368 SetGrab(true);
369
370 m_cpTrackPoint = point;
371 m_fLastTrackPos = m_fTrackPos;
372 if (m_rtMinBtn.Contains(point))
373 DoMouseDown(0, m_rtMinBtn, m_iMinButtonState, point);
374 else if (m_rtThumb.Contains(point))
375 DoMouseDown(1, m_rtThumb, m_iThumbButtonState, point);
376 else if (m_rtMaxBtn.Contains(point))
377 DoMouseDown(2, m_rtMaxBtn, m_iMaxButtonState, point);
378 else if (m_rtMinTrack.Contains(point))
379 DoMouseDown(3, m_rtMinTrack, m_iMinTrackState, point);
380 else
381 DoMouseDown(4, m_rtMaxTrack, m_iMaxTrackState, point);
382
383 if (!SendEvent())
384 m_pTimerInfo = m_Timer.StartTimer(FWL_SCROLLBAR_Elapse, true);
385 }
386
OnLButtonUp(const CFX_PointF & point)387 void CFWL_ScrollBar::OnLButtonUp(const CFX_PointF& point) {
388 m_pTimerInfo->StopTimer();
389 m_bMouseDown = false;
390 DoMouseUp(0, m_rtMinBtn, m_iMinButtonState, point);
391 DoMouseUp(1, m_rtThumb, m_iThumbButtonState, point);
392 DoMouseUp(2, m_rtMaxBtn, m_iMaxButtonState, point);
393 DoMouseUp(3, m_rtMinTrack, m_iMinTrackState, point);
394 DoMouseUp(4, m_rtMaxTrack, m_iMaxTrackState, point);
395 SetGrab(false);
396 }
397
OnMouseMove(const CFX_PointF & point)398 void CFWL_ScrollBar::OnMouseMove(const CFX_PointF& point) {
399 DoMouseMove(0, m_rtMinBtn, m_iMinButtonState, point);
400 DoMouseMove(1, m_rtThumb, m_iThumbButtonState, point);
401 DoMouseMove(2, m_rtMaxBtn, m_iMaxButtonState, point);
402 DoMouseMove(3, m_rtMinTrack, m_iMinTrackState, point);
403 DoMouseMove(4, m_rtMaxTrack, m_iMaxTrackState, point);
404 }
405
OnMouseLeave()406 void CFWL_ScrollBar::OnMouseLeave() {
407 DoMouseLeave(0, m_rtMinBtn, m_iMinButtonState);
408 DoMouseLeave(1, m_rtThumb, m_iThumbButtonState);
409 DoMouseLeave(2, m_rtMaxBtn, m_iMaxButtonState);
410 DoMouseLeave(3, m_rtMinTrack, m_iMinTrackState);
411 DoMouseLeave(4, m_rtMaxTrack, m_iMaxTrackState);
412 }
413
OnMouseWheel(const CFX_PointF & delta)414 void CFWL_ScrollBar::OnMouseWheel(const CFX_PointF& delta) {
415 m_iMouseWheel = static_cast<int32_t>(delta.x);
416 SendEvent();
417 m_iMouseWheel = 0;
418 }
419
DoMouseDown(int32_t iItem,const CFX_RectF & rtItem,int32_t & iState,const CFX_PointF & point)420 void CFWL_ScrollBar::DoMouseDown(int32_t iItem,
421 const CFX_RectF& rtItem,
422 int32_t& iState,
423 const CFX_PointF& point) {
424 if (!rtItem.Contains(point))
425 return;
426 if (iState == CFWL_PartState_Pressed)
427 return;
428
429 iState = CFWL_PartState_Pressed;
430 RepaintRect(rtItem);
431 }
432
DoMouseUp(int32_t iItem,const CFX_RectF & rtItem,int32_t & iState,const CFX_PointF & point)433 void CFWL_ScrollBar::DoMouseUp(int32_t iItem,
434 const CFX_RectF& rtItem,
435 int32_t& iState,
436 const CFX_PointF& point) {
437 int32_t iNewState =
438 rtItem.Contains(point) ? CFWL_PartState_Hovered : CFWL_PartState_Normal;
439 if (iState == iNewState)
440 return;
441
442 iState = iNewState;
443 RepaintRect(rtItem);
444 OnScroll(CFWL_EventScroll::Code::EndScroll, m_fTrackPos);
445 }
446
DoMouseMove(int32_t iItem,const CFX_RectF & rtItem,int32_t & iState,const CFX_PointF & point)447 void CFWL_ScrollBar::DoMouseMove(int32_t iItem,
448 const CFX_RectF& rtItem,
449 int32_t& iState,
450 const CFX_PointF& point) {
451 if (!m_bMouseDown) {
452 int32_t iNewState =
453 rtItem.Contains(point) ? CFWL_PartState_Hovered : CFWL_PartState_Normal;
454 if (iState == iNewState)
455 return;
456
457 iState = iNewState;
458 RepaintRect(rtItem);
459 } else if ((2 == iItem) && (m_iThumbButtonState == CFWL_PartState_Pressed)) {
460 m_fTrackPos = GetTrackPointPos(point);
461 OnScroll(CFWL_EventScroll::Code::TrackPos, m_fTrackPos);
462 }
463 }
464
DoMouseLeave(int32_t iItem,const CFX_RectF & rtItem,int32_t & iState)465 void CFWL_ScrollBar::DoMouseLeave(int32_t iItem,
466 const CFX_RectF& rtItem,
467 int32_t& iState) {
468 if (iState == CFWL_PartState_Normal)
469 return;
470
471 iState = CFWL_PartState_Normal;
472 RepaintRect(rtItem);
473 }
474
DoMouseHover(int32_t iItem,const CFX_RectF & rtItem,int32_t & iState)475 void CFWL_ScrollBar::DoMouseHover(int32_t iItem,
476 const CFX_RectF& rtItem,
477 int32_t& iState) {
478 if (iState == CFWL_PartState_Hovered)
479 return;
480
481 iState = CFWL_PartState_Hovered;
482 RepaintRect(rtItem);
483 }
484
Timer(CFWL_ScrollBar * pToolTip)485 CFWL_ScrollBar::Timer::Timer(CFWL_ScrollBar* pToolTip) : CFWL_Timer(pToolTip) {}
486
Run(CFWL_TimerInfo * pTimerInfo)487 void CFWL_ScrollBar::Timer::Run(CFWL_TimerInfo* pTimerInfo) {
488 CFWL_ScrollBar* pButton = static_cast<CFWL_ScrollBar*>(m_pWidget);
489
490 if (pButton->m_pTimerInfo)
491 pButton->m_pTimerInfo->StopTimer();
492
493 if (!pButton->SendEvent())
494 pButton->m_pTimerInfo = StartTimer(0, true);
495 }
496