1 // Copyright 2014 The PDFium Authors
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/cxx17_backports.h"
14 #include "xfa/fwl/cfwl_app.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/ifwl_themeprovider.h"
21
22 namespace {
23
24 constexpr int kScrollbarElapsedMsecs = 500;
25 constexpr float kMinThumbSize = 5.0f;
26
27 } // namespace
28
CFWL_ScrollBar(CFWL_App * app,const Properties & properties,CFWL_Widget * pOuter)29 CFWL_ScrollBar::CFWL_ScrollBar(CFWL_App* app,
30 const Properties& properties,
31 CFWL_Widget* pOuter)
32 : CFWL_Widget(app, properties, pOuter) {}
33
34 CFWL_ScrollBar::~CFWL_ScrollBar() = default;
35
GetClassID() const36 FWL_Type CFWL_ScrollBar::GetClassID() const {
37 return FWL_Type::ScrollBar;
38 }
39
Update()40 void CFWL_ScrollBar::Update() {
41 if (IsLocked())
42 return;
43
44 Layout();
45 }
46
DrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)47 void CFWL_ScrollBar::DrawWidget(CFGAS_GEGraphics* pGraphics,
48 const CFX_Matrix& matrix) {
49 if (!pGraphics)
50 return;
51
52 if (HasBorder())
53 DrawBorder(pGraphics, CFWL_ThemePart::Part::kBorder, matrix);
54
55 DrawLowerTrack(pGraphics, matrix);
56 DrawUpperTrack(pGraphics, matrix);
57 DrawMinArrowBtn(pGraphics, matrix);
58 DrawMaxArrowBtn(pGraphics, matrix);
59 DrawThumb(pGraphics, matrix);
60 }
61
SetTrackPos(float fTrackPos)62 void CFWL_ScrollBar::SetTrackPos(float fTrackPos) {
63 m_fTrackPos = fTrackPos;
64 m_ThumbRect = CalcThumbButtonRect(m_ThumbRect);
65 m_MinTrackRect = CalcMinTrackRect(m_MinTrackRect);
66 m_MaxTrackRect = CalcMaxTrackRect(m_MaxTrackRect);
67 }
68
DoScroll(CFWL_EventScroll::Code dwCode,float fPos)69 bool CFWL_ScrollBar::DoScroll(CFWL_EventScroll::Code dwCode, float fPos) {
70 if (dwCode == CFWL_EventScroll::Code::None)
71 return false;
72 return OnScroll(dwCode, fPos);
73 }
74
DrawUpperTrack(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)75 void CFWL_ScrollBar::DrawUpperTrack(CFGAS_GEGraphics* pGraphics,
76 const CFX_Matrix& mtMatrix) {
77 CFWL_ThemeBackground param(CFWL_ThemePart::Part::kUpperTrack, this,
78 pGraphics);
79 param.m_dwStates = (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)
80 ? CFWL_PartState::kDisabled
81 : m_iMaxTrackState;
82 param.m_matrix = mtMatrix;
83 param.m_PartRect = m_MaxTrackRect;
84 GetThemeProvider()->DrawBackground(param);
85 }
86
DrawLowerTrack(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)87 void CFWL_ScrollBar::DrawLowerTrack(CFGAS_GEGraphics* pGraphics,
88 const CFX_Matrix& mtMatrix) {
89 CFWL_ThemeBackground param(CFWL_ThemePart::Part::kLowerTrack, this,
90 pGraphics);
91 param.m_dwStates = (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)
92 ? CFWL_PartState::kDisabled
93 : m_iMinTrackState;
94 param.m_matrix = mtMatrix;
95 param.m_PartRect = m_MinTrackRect;
96 GetThemeProvider()->DrawBackground(param);
97 }
98
DrawMaxArrowBtn(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)99 void CFWL_ScrollBar::DrawMaxArrowBtn(CFGAS_GEGraphics* pGraphics,
100 const CFX_Matrix& mtMatrix) {
101 CFWL_ThemeBackground param(CFWL_ThemePart::Part::kBackArrow, this, pGraphics);
102 param.m_dwStates = (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)
103 ? CFWL_PartState::kDisabled
104 : m_iMaxButtonState;
105 param.m_matrix = mtMatrix;
106 param.m_PartRect = m_MaxBtnRect;
107 if (param.m_PartRect.height > 0 && param.m_PartRect.width > 0)
108 GetThemeProvider()->DrawBackground(param);
109 }
110
DrawMinArrowBtn(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)111 void CFWL_ScrollBar::DrawMinArrowBtn(CFGAS_GEGraphics* pGraphics,
112 const CFX_Matrix& mtMatrix) {
113 CFWL_ThemeBackground param(CFWL_ThemePart::Part::kForeArrow, this, pGraphics);
114 param.m_dwStates = (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)
115 ? CFWL_PartState::kDisabled
116 : m_iMinButtonState;
117 param.m_matrix = mtMatrix;
118 param.m_PartRect = m_MinBtnRect;
119 if (param.m_PartRect.height > 0 && param.m_PartRect.width > 0)
120 GetThemeProvider()->DrawBackground(param);
121 }
122
DrawThumb(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)123 void CFWL_ScrollBar::DrawThumb(CFGAS_GEGraphics* pGraphics,
124 const CFX_Matrix& mtMatrix) {
125 CFWL_ThemeBackground param(CFWL_ThemePart::Part::kThumb, this, pGraphics);
126 param.m_dwStates = (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)
127 ? CFWL_PartState::kDisabled
128 : m_iThumbButtonState;
129 param.m_matrix = mtMatrix;
130 param.m_PartRect = m_ThumbRect;
131 GetThemeProvider()->DrawBackground(param);
132 }
133
Layout()134 void CFWL_ScrollBar::Layout() {
135 m_ClientRect = GetClientRect();
136
137 CalcButtonLen();
138 m_MinBtnRect = CalcMinButtonRect();
139 m_MaxBtnRect = CalcMaxButtonRect();
140 m_ThumbRect = CalcThumbButtonRect(m_ThumbRect);
141 m_MinTrackRect = CalcMinTrackRect(m_MinTrackRect);
142 m_MaxTrackRect = CalcMaxTrackRect(m_MaxTrackRect);
143 }
144
CalcButtonLen()145 void CFWL_ScrollBar::CalcButtonLen() {
146 m_fButtonLen = IsVertical() ? m_ClientRect.width : m_ClientRect.height;
147 float fLength = IsVertical() ? m_ClientRect.height : m_ClientRect.width;
148 if (fLength < m_fButtonLen * 2) {
149 m_fButtonLen = fLength / 2;
150 m_bMinSize = true;
151 } else {
152 m_bMinSize = false;
153 }
154 }
155
CalcMinButtonRect()156 CFX_RectF CFWL_ScrollBar::CalcMinButtonRect() {
157 if (IsVertical())
158 return CFX_RectF(m_ClientRect.TopLeft(), m_ClientRect.width, m_fButtonLen);
159 return CFX_RectF(m_ClientRect.TopLeft(), m_fButtonLen, m_ClientRect.height);
160 }
161
CalcMaxButtonRect()162 CFX_RectF CFWL_ScrollBar::CalcMaxButtonRect() {
163 if (IsVertical()) {
164 return CFX_RectF(m_ClientRect.left, m_ClientRect.bottom() - m_fButtonLen,
165 m_ClientRect.width, m_fButtonLen);
166 }
167 return CFX_RectF(m_ClientRect.right() - m_fButtonLen, m_ClientRect.top,
168 m_fButtonLen, m_ClientRect.height);
169 }
170
CalcThumbButtonRect(const CFX_RectF & rtThumb)171 CFX_RectF CFWL_ScrollBar::CalcThumbButtonRect(const CFX_RectF& rtThumb) {
172 CFX_RectF rect;
173 if (!IsEnabled())
174 return rect;
175
176 if (m_bMinSize) {
177 rect.left = rtThumb.left;
178 rect.top = rtThumb.top;
179 return rect;
180 }
181
182 float fRange = m_fRangeMax - m_fRangeMin;
183 if (fRange < 0) {
184 if (IsVertical()) {
185 return CFX_RectF(m_ClientRect.left, m_MaxBtnRect.bottom(),
186 m_ClientRect.width, 0);
187 }
188 return CFX_RectF(m_MaxBtnRect.right(), m_ClientRect.top, 0,
189 m_ClientRect.height);
190 }
191
192 CFX_RectF rtClient = m_ClientRect;
193 float fLength = IsVertical() ? rtClient.height : rtClient.width;
194 float fSize = m_fButtonLen;
195 fLength -= fSize * 2.0f;
196 if (fLength < fSize)
197 fLength = 0.0f;
198
199 float fThumbSize = fLength * fLength / (fRange + fLength);
200 fThumbSize = std::max(fThumbSize, kMinThumbSize);
201
202 float fDiff = std::max(fLength - fThumbSize, 0.0f);
203 float fTrackPos = pdfium::clamp(m_fTrackPos, m_fRangeMin, m_fRangeMax);
204 if (!fRange)
205 return rect;
206
207 float iPos = fSize + fDiff * (fTrackPos - m_fRangeMin) / fRange;
208 rect.left = rtClient.left;
209 rect.top = rtClient.top;
210 if (IsVertical()) {
211 rect.top += iPos;
212 rect.width = rtClient.width;
213 rect.height = fThumbSize;
214 } else {
215 rect.left += iPos;
216 rect.width = fThumbSize;
217 rect.height = rtClient.height;
218 }
219 return rect;
220 }
221
CalcMinTrackRect(const CFX_RectF & rtMinRect)222 CFX_RectF CFWL_ScrollBar::CalcMinTrackRect(const CFX_RectF& rtMinRect) {
223 CFX_RectF rect;
224 if (m_bMinSize) {
225 rect.left = rtMinRect.left;
226 rect.top = rtMinRect.top;
227 return rect;
228 }
229
230 rect.left = m_ClientRect.left;
231 rect.top = m_ClientRect.top;
232 if (IsVertical()) {
233 rect.width = m_ClientRect.width;
234 rect.height = (m_ThumbRect.top + m_ThumbRect.bottom()) / 2;
235 } else {
236 rect.width = (m_ThumbRect.left + m_ThumbRect.right()) / 2;
237 rect.height = m_ClientRect.height;
238 }
239 return rect;
240 }
241
CalcMaxTrackRect(const CFX_RectF & rtMaxRect)242 CFX_RectF CFWL_ScrollBar::CalcMaxTrackRect(const CFX_RectF& rtMaxRect) {
243 if (m_bMinSize)
244 return CFX_RectF(rtMaxRect.TopLeft(), 0, 0);
245
246 if (IsVertical()) {
247 float iy = (m_ThumbRect.top + m_ThumbRect.bottom()) / 2;
248 return CFX_RectF(m_ClientRect.left, iy, m_ClientRect.width,
249 m_ClientRect.bottom() - iy);
250 }
251
252 float ix = (m_ThumbRect.left + m_ThumbRect.right()) / 2;
253 return CFX_RectF(ix, m_ClientRect.top, m_ClientRect.height - ix,
254 m_ClientRect.height);
255 }
256
GetTrackPointPos(const CFX_PointF & point)257 float CFWL_ScrollBar::GetTrackPointPos(const CFX_PointF& point) {
258 CFX_PointF diff = point - m_cpTrackPoint;
259 float fRange = m_fRangeMax - m_fRangeMin;
260 float fPos;
261
262 if (IsVertical()) {
263 fPos = fRange * diff.y /
264 (m_MaxBtnRect.top - m_MinBtnRect.bottom() - m_ThumbRect.height);
265 } else {
266 fPos = fRange * diff.x /
267 (m_MaxBtnRect.left - m_MinBtnRect.right() - m_ThumbRect.width);
268 }
269
270 fPos += m_fLastTrackPos;
271 return pdfium::clamp(fPos, m_fRangeMin, m_fRangeMax);
272 }
273
SendEvent()274 bool CFWL_ScrollBar::SendEvent() {
275 if (m_iMinButtonState == CFWL_PartState::kPressed) {
276 DoScroll(CFWL_EventScroll::Code::StepBackward, m_fTrackPos);
277 return false;
278 }
279 if (m_iMaxButtonState == CFWL_PartState::kPressed) {
280 DoScroll(CFWL_EventScroll::Code::StepForward, m_fTrackPos);
281 return false;
282 }
283 if (m_iMinTrackState == CFWL_PartState::kPressed) {
284 DoScroll(CFWL_EventScroll::Code::PageBackward, m_fTrackPos);
285 return m_ThumbRect.Contains(m_cpTrackPoint);
286 }
287 if (m_iMaxTrackState == CFWL_PartState::kPressed) {
288 DoScroll(CFWL_EventScroll::Code::PageForward, m_fTrackPos);
289 return m_ThumbRect.Contains(m_cpTrackPoint);
290 }
291 if (m_iMouseWheel) {
292 CFWL_EventScroll::Code dwCode = m_iMouseWheel < 0
293 ? CFWL_EventScroll::Code::StepForward
294 : CFWL_EventScroll::Code::StepBackward;
295 DoScroll(dwCode, m_fTrackPos);
296 }
297 return true;
298 }
299
OnScroll(CFWL_EventScroll::Code dwCode,float fPos)300 bool CFWL_ScrollBar::OnScroll(CFWL_EventScroll::Code dwCode, float fPos) {
301 CFWL_EventScroll ev(this, dwCode, fPos);
302 DispatchEvent(&ev);
303 return true;
304 }
305
OnProcessMessage(CFWL_Message * pMessage)306 void CFWL_ScrollBar::OnProcessMessage(CFWL_Message* pMessage) {
307 CFWL_Message::Type type = pMessage->GetType();
308 if (type == CFWL_Message::Type::kMouse) {
309 CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
310 switch (pMsg->m_dwCmd) {
311 case CFWL_MessageMouse::MouseCommand::kLeftButtonDown:
312 OnLButtonDown(pMsg->m_pos);
313 break;
314 case CFWL_MessageMouse::MouseCommand::kLeftButtonUp:
315 OnLButtonUp(pMsg->m_pos);
316 break;
317 case CFWL_MessageMouse::MouseCommand::kMove:
318 OnMouseMove(pMsg->m_pos);
319 break;
320 case CFWL_MessageMouse::MouseCommand::kLeave:
321 OnMouseLeave();
322 break;
323 default:
324 break;
325 }
326 } else if (type == CFWL_Message::Type::kMouseWheel) {
327 auto* pMsg = static_cast<CFWL_MessageMouseWheel*>(pMessage);
328 OnMouseWheel(pMsg->delta());
329 }
330 }
331
OnDrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)332 void CFWL_ScrollBar::OnDrawWidget(CFGAS_GEGraphics* pGraphics,
333 const CFX_Matrix& matrix) {
334 DrawWidget(pGraphics, matrix);
335 }
336
OnLButtonDown(const CFX_PointF & point)337 void CFWL_ScrollBar::OnLButtonDown(const CFX_PointF& point) {
338 if (!IsEnabled())
339 return;
340
341 m_bMouseDown = true;
342 SetGrab(true);
343
344 m_cpTrackPoint = point;
345 m_fLastTrackPos = m_fTrackPos;
346 if (m_MinBtnRect.Contains(point))
347 DoMouseDown(0, m_MinBtnRect, &m_iMinButtonState, point);
348 else if (m_ThumbRect.Contains(point))
349 DoMouseDown(1, m_ThumbRect, &m_iThumbButtonState, point);
350 else if (m_MaxBtnRect.Contains(point))
351 DoMouseDown(2, m_MaxBtnRect, &m_iMaxButtonState, point);
352 else if (m_MinTrackRect.Contains(point))
353 DoMouseDown(3, m_MinTrackRect, &m_iMinTrackState, point);
354 else
355 DoMouseDown(4, m_MaxTrackRect, &m_iMaxTrackState, point);
356
357 if (!SendEvent()) {
358 m_pTimer = std::make_unique<CFX_Timer>(GetFWLApp()->GetTimerHandler(), this,
359 kScrollbarElapsedMsecs);
360 }
361 }
362
OnLButtonUp(const CFX_PointF & point)363 void CFWL_ScrollBar::OnLButtonUp(const CFX_PointF& point) {
364 m_pTimer.reset();
365 m_bMouseDown = false;
366 DoMouseUp(0, m_MinBtnRect, &m_iMinButtonState, point);
367 DoMouseUp(1, m_ThumbRect, &m_iThumbButtonState, point);
368 DoMouseUp(2, m_MaxBtnRect, &m_iMaxButtonState, point);
369 DoMouseUp(3, m_MinTrackRect, &m_iMinTrackState, point);
370 DoMouseUp(4, m_MaxTrackRect, &m_iMaxTrackState, point);
371 SetGrab(false);
372 }
373
OnMouseMove(const CFX_PointF & point)374 void CFWL_ScrollBar::OnMouseMove(const CFX_PointF& point) {
375 DoMouseMove(0, m_MinBtnRect, &m_iMinButtonState, point);
376 DoMouseMove(1, m_ThumbRect, &m_iThumbButtonState, point);
377 DoMouseMove(2, m_MaxBtnRect, &m_iMaxButtonState, point);
378 DoMouseMove(3, m_MinTrackRect, &m_iMinTrackState, point);
379 DoMouseMove(4, m_MaxTrackRect, &m_iMaxTrackState, point);
380 }
381
OnMouseLeave()382 void CFWL_ScrollBar::OnMouseLeave() {
383 DoMouseLeave(0, m_MinBtnRect, &m_iMinButtonState);
384 DoMouseLeave(1, m_ThumbRect, &m_iThumbButtonState);
385 DoMouseLeave(2, m_MaxBtnRect, &m_iMaxButtonState);
386 DoMouseLeave(3, m_MinTrackRect, &m_iMinTrackState);
387 DoMouseLeave(4, m_MaxTrackRect, &m_iMaxTrackState);
388 }
389
OnMouseWheel(const CFX_Vector & delta)390 void CFWL_ScrollBar::OnMouseWheel(const CFX_Vector& delta) {
391 m_iMouseWheel = delta.y;
392 SendEvent();
393 m_iMouseWheel = 0;
394 }
395
DoMouseDown(int32_t iItem,const CFX_RectF & rtItem,CFWL_PartState * pState,const CFX_PointF & point)396 void CFWL_ScrollBar::DoMouseDown(int32_t iItem,
397 const CFX_RectF& rtItem,
398 CFWL_PartState* pState,
399 const CFX_PointF& point) {
400 if (!rtItem.Contains(point))
401 return;
402 if (*pState == CFWL_PartState::kPressed)
403 return;
404
405 *pState = CFWL_PartState::kPressed;
406 RepaintRect(rtItem);
407 }
408
DoMouseUp(int32_t iItem,const CFX_RectF & rtItem,CFWL_PartState * pState,const CFX_PointF & point)409 void CFWL_ScrollBar::DoMouseUp(int32_t iItem,
410 const CFX_RectF& rtItem,
411 CFWL_PartState* pState,
412 const CFX_PointF& point) {
413 CFWL_PartState iNewState = rtItem.Contains(point) ? CFWL_PartState::kHovered
414 : CFWL_PartState::kNormal;
415 if (*pState == iNewState)
416 return;
417
418 *pState = iNewState;
419 RepaintRect(rtItem);
420 OnScroll(CFWL_EventScroll::Code::EndScroll, m_fTrackPos);
421 }
422
DoMouseMove(int32_t iItem,const CFX_RectF & rtItem,CFWL_PartState * pState,const CFX_PointF & point)423 void CFWL_ScrollBar::DoMouseMove(int32_t iItem,
424 const CFX_RectF& rtItem,
425 CFWL_PartState* pState,
426 const CFX_PointF& point) {
427 if (!m_bMouseDown) {
428 CFWL_PartState iNewState = rtItem.Contains(point) ? CFWL_PartState::kHovered
429 : CFWL_PartState::kNormal;
430 if (*pState == iNewState)
431 return;
432
433 *pState = iNewState;
434 RepaintRect(rtItem);
435 } else if ((2 == iItem) &&
436 (m_iThumbButtonState == CFWL_PartState::kPressed)) {
437 m_fTrackPos = GetTrackPointPos(point);
438 OnScroll(CFWL_EventScroll::Code::TrackPos, m_fTrackPos);
439 }
440 }
441
DoMouseLeave(int32_t iItem,const CFX_RectF & rtItem,CFWL_PartState * pState)442 void CFWL_ScrollBar::DoMouseLeave(int32_t iItem,
443 const CFX_RectF& rtItem,
444 CFWL_PartState* pState) {
445 if (*pState == CFWL_PartState::kNormal)
446 return;
447
448 *pState = CFWL_PartState::kNormal;
449 RepaintRect(rtItem);
450 }
451
DoMouseHover(int32_t iItem,const CFX_RectF & rtItem,CFWL_PartState * pState)452 void CFWL_ScrollBar::DoMouseHover(int32_t iItem,
453 const CFX_RectF& rtItem,
454 CFWL_PartState* pState) {
455 if (*pState == CFWL_PartState::kHovered)
456 return;
457
458 *pState = CFWL_PartState::kHovered;
459 RepaintRect(rtItem);
460 }
461
OnTimerFired()462 void CFWL_ScrollBar::OnTimerFired() {
463 m_pTimer.reset();
464 if (!SendEvent()) {
465 m_pTimer =
466 std::make_unique<CFX_Timer>(GetFWLApp()->GetTimerHandler(), this, 0);
467 }
468 }
469