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 "third_party/base/stl_util.h"
15 #include "xfa/fwl/cfwl_messagemouse.h"
16 #include "xfa/fwl/cfwl_messagemousewheel.h"
17 #include "xfa/fwl/cfwl_notedriver.h"
18 #include "xfa/fwl/cfwl_themebackground.h"
19 #include "xfa/fwl/cfwl_themepart.h"
20 #include "xfa/fwl/cfwl_timerinfo.h"
21 #include "xfa/fwl/ifwl_themeprovider.h"
22
23 #define FWL_SCROLLBAR_Elapse 500
24
25 namespace {
26
27 const float kMinThumbSize = 5.0f;
28
29 } // namespace
30
CFWL_ScrollBar(const CFWL_App * app,std::unique_ptr<CFWL_WidgetProperties> properties,CFWL_Widget * pOuter)31 CFWL_ScrollBar::CFWL_ScrollBar(
32 const CFWL_App* app,
33 std::unique_ptr<CFWL_WidgetProperties> properties,
34 CFWL_Widget* pOuter)
35 : CFWL_Widget(app, std::move(properties), pOuter),
36 m_pTimerInfo(nullptr),
37 m_fRangeMin(0),
38 m_fRangeMax(-1),
39 m_fPageSize(0),
40 m_fStepSize(0),
41 m_fPos(0),
42 m_fTrackPos(0),
43 m_iMinButtonState(CFWL_PartState_Normal),
44 m_iMaxButtonState(CFWL_PartState_Normal),
45 m_iThumbButtonState(CFWL_PartState_Normal),
46 m_iMinTrackState(CFWL_PartState_Normal),
47 m_iMaxTrackState(CFWL_PartState_Normal),
48 m_fLastTrackPos(0),
49 m_iMouseWheel(0),
50 m_bMouseDown(false),
51 m_fButtonLen(0),
52 m_bMinSize(false),
53 m_Timer(this) {
54 m_rtClient.Reset();
55 m_rtThumb.Reset();
56 m_rtMinBtn.Reset();
57 m_rtMaxBtn.Reset();
58 m_rtMinTrack.Reset();
59 m_rtMaxTrack.Reset();
60 }
61
~CFWL_ScrollBar()62 CFWL_ScrollBar::~CFWL_ScrollBar() {}
63
GetClassID() const64 FWL_Type CFWL_ScrollBar::GetClassID() const {
65 return FWL_Type::ScrollBar;
66 }
67
Update()68 void CFWL_ScrollBar::Update() {
69 if (IsLocked())
70 return;
71 if (!m_pProperties->m_pThemeProvider)
72 m_pProperties->m_pThemeProvider = GetAvailableTheme();
73
74 Layout();
75 }
76
DrawWidget(CXFA_Graphics * pGraphics,const CFX_Matrix & matrix)77 void CFWL_ScrollBar::DrawWidget(CXFA_Graphics* pGraphics,
78 const CFX_Matrix& matrix) {
79 if (!pGraphics)
80 return;
81 if (!m_pProperties->m_pThemeProvider)
82 return;
83
84 IFWL_ThemeProvider* pTheme = m_pProperties->m_pThemeProvider;
85 if (HasBorder())
86 DrawBorder(pGraphics, CFWL_Part::Border, pTheme, matrix);
87 DrawTrack(pGraphics, pTheme, true, &matrix);
88 DrawTrack(pGraphics, pTheme, false, &matrix);
89 DrawArrowBtn(pGraphics, pTheme, true, &matrix);
90 DrawArrowBtn(pGraphics, pTheme, false, &matrix);
91 DrawThumb(pGraphics, pTheme, &matrix);
92 }
93
SetTrackPos(float fTrackPos)94 void CFWL_ScrollBar::SetTrackPos(float fTrackPos) {
95 m_fTrackPos = fTrackPos;
96 m_rtThumb = CalcThumbButtonRect(m_rtThumb);
97 m_rtMinTrack = CalcMinTrackRect(m_rtMinTrack);
98 m_rtMaxTrack = CalcMaxTrackRect(m_rtMaxTrack);
99 }
100
DoScroll(CFWL_EventScroll::Code dwCode,float fPos)101 bool CFWL_ScrollBar::DoScroll(CFWL_EventScroll::Code dwCode, float fPos) {
102 if (dwCode == CFWL_EventScroll::Code::None)
103 return false;
104 return OnScroll(dwCode, fPos);
105 }
106
DrawTrack(CXFA_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,bool bLower,const CFX_Matrix * pMatrix)107 void CFWL_ScrollBar::DrawTrack(CXFA_Graphics* pGraphics,
108 IFWL_ThemeProvider* pTheme,
109 bool bLower,
110 const CFX_Matrix* pMatrix) {
111 CFWL_ThemeBackground param;
112 param.m_pWidget = this;
113 param.m_iPart = bLower ? CFWL_Part::LowerTrack : CFWL_Part::UpperTrack;
114 param.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
115 ? CFWL_PartState_Disabled
116 : (bLower ? m_iMinTrackState : m_iMaxTrackState);
117 param.m_pGraphics = pGraphics;
118 param.m_matrix.Concat(*pMatrix);
119 param.m_rtPart = bLower ? m_rtMinTrack : m_rtMaxTrack;
120 pTheme->DrawBackground(¶m);
121 }
122
DrawArrowBtn(CXFA_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,bool bMinBtn,const CFX_Matrix * pMatrix)123 void CFWL_ScrollBar::DrawArrowBtn(CXFA_Graphics* pGraphics,
124 IFWL_ThemeProvider* pTheme,
125 bool bMinBtn,
126 const CFX_Matrix* pMatrix) {
127 CFWL_ThemeBackground param;
128 param.m_pWidget = this;
129 param.m_iPart = bMinBtn ? CFWL_Part::ForeArrow : CFWL_Part::BackArrow;
130 param.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
131 ? CFWL_PartState_Disabled
132 : (bMinBtn ? m_iMinButtonState : m_iMaxButtonState);
133 param.m_pGraphics = pGraphics;
134 param.m_matrix.Concat(*pMatrix);
135 param.m_rtPart = bMinBtn ? m_rtMinBtn : m_rtMaxBtn;
136 if (param.m_rtPart.height > 0 && param.m_rtPart.width > 0)
137 pTheme->DrawBackground(¶m);
138 }
139
DrawThumb(CXFA_Graphics * pGraphics,IFWL_ThemeProvider * pTheme,const CFX_Matrix * pMatrix)140 void CFWL_ScrollBar::DrawThumb(CXFA_Graphics* pGraphics,
141 IFWL_ThemeProvider* pTheme,
142 const CFX_Matrix* pMatrix) {
143 CFWL_ThemeBackground param;
144 param.m_pWidget = this;
145 param.m_iPart = CFWL_Part::Thumb;
146 param.m_dwStates = (m_pProperties->m_dwStates & FWL_WGTSTATE_Disabled)
147 ? CFWL_PartState_Disabled
148 : m_iThumbButtonState;
149 param.m_pGraphics = pGraphics;
150 param.m_matrix.Concat(*pMatrix);
151 param.m_rtPart = m_rtThumb;
152 pTheme->DrawBackground(¶m);
153 }
154
Layout()155 void CFWL_ScrollBar::Layout() {
156 m_rtClient = GetClientRect();
157
158 CalcButtonLen();
159 m_rtMinBtn = CalcMinButtonRect();
160 m_rtMaxBtn = CalcMaxButtonRect();
161 m_rtThumb = CalcThumbButtonRect(m_rtThumb);
162 m_rtMinTrack = CalcMinTrackRect(m_rtMinTrack);
163 m_rtMaxTrack = CalcMaxTrackRect(m_rtMaxTrack);
164 }
165
CalcButtonLen()166 void CFWL_ScrollBar::CalcButtonLen() {
167 m_fButtonLen = IsVertical() ? m_rtClient.width : m_rtClient.height;
168 float fLength = IsVertical() ? m_rtClient.height : m_rtClient.width;
169 if (fLength < m_fButtonLen * 2) {
170 m_fButtonLen = fLength / 2;
171 m_bMinSize = true;
172 } else {
173 m_bMinSize = false;
174 }
175 }
176
CalcMinButtonRect()177 CFX_RectF CFWL_ScrollBar::CalcMinButtonRect() {
178 if (IsVertical())
179 return CFX_RectF(m_rtClient.TopLeft(), m_rtClient.width, m_fButtonLen);
180 return CFX_RectF(m_rtClient.TopLeft(), m_fButtonLen, m_rtClient.height);
181 }
182
CalcMaxButtonRect()183 CFX_RectF CFWL_ScrollBar::CalcMaxButtonRect() {
184 if (IsVertical()) {
185 return CFX_RectF(m_rtClient.left, m_rtClient.bottom() - m_fButtonLen,
186 m_rtClient.width, m_fButtonLen);
187 }
188 return CFX_RectF(m_rtClient.right() - m_fButtonLen, m_rtClient.top,
189 m_fButtonLen, m_rtClient.height);
190 }
191
CalcThumbButtonRect(const CFX_RectF & rtThumb)192 CFX_RectF CFWL_ScrollBar::CalcThumbButtonRect(const CFX_RectF& rtThumb) {
193 CFX_RectF rect;
194 if (!IsEnabled())
195 return rect;
196
197 if (m_bMinSize) {
198 rect.left = rtThumb.left;
199 rect.top = rtThumb.top;
200 return rect;
201 }
202
203 float fRange = m_fRangeMax - m_fRangeMin;
204 if (fRange < 0) {
205 if (IsVertical()) {
206 return CFX_RectF(m_rtClient.left, m_rtMaxBtn.bottom(), m_rtClient.width,
207 0);
208 }
209 return CFX_RectF(m_rtMaxBtn.right(), m_rtClient.top, 0, m_rtClient.height);
210 }
211
212 CFX_RectF rtClient = m_rtClient;
213 float fLength = IsVertical() ? rtClient.height : rtClient.width;
214 float fSize = m_fButtonLen;
215 fLength -= fSize * 2.0f;
216 if (fLength < fSize)
217 fLength = 0.0f;
218
219 float fThumbSize = fLength * fLength / (fRange + fLength);
220 fThumbSize = std::max(fThumbSize, kMinThumbSize);
221
222 float fDiff = std::max(fLength - fThumbSize, 0.0f);
223 float fTrackPos = pdfium::clamp(m_fTrackPos, m_fRangeMin, m_fRangeMax);
224 if (!fRange)
225 return rect;
226
227 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 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 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 float CFWL_ScrollBar::GetTrackPointPos(const CFX_PointF& point) {
278 CFX_PointF diff = point - m_cpTrackPoint;
279 float fRange = m_fRangeMax - m_fRangeMin;
280 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 pdfium::clamp(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,float fPos)320 bool CFWL_ScrollBar::OnScroll(CFWL_EventScroll::Code dwCode, 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(CXFA_Graphics * pGraphics,const CFX_Matrix & matrix)358 void CFWL_ScrollBar::OnDrawWidget(CXFA_Graphics* pGraphics,
359 const CFX_Matrix& matrix) {
360 DrawWidget(pGraphics, matrix);
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.Get());
489 if (pButton->m_pTimerInfo)
490 pButton->m_pTimerInfo->StopTimer();
491
492 if (!pButton->SendEvent())
493 pButton->m_pTimerInfo = StartTimer(0, true);
494 }
495