QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskScrollView.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskScrollView.h"
7#include "QskAnimationHint.h"
8#include "QskBoxBorderMetrics.h"
9#include "QskEvent.h"
10
11QSK_SUBCONTROL( QskScrollView, Panel )
12QSK_SUBCONTROL( QskScrollView, Viewport )
13QSK_SUBCONTROL( QskScrollView, HorizontalScrollBar )
14QSK_SUBCONTROL( QskScrollView, HorizontalScrollHandle )
15QSK_SUBCONTROL( QskScrollView, VerticalScrollBar )
16QSK_SUBCONTROL( QskScrollView, VerticalScrollHandle )
17
18QSK_SYSTEM_STATE( QskScrollView, Pressed, QskAspect::FirstSystemState << 1 )
19
20static inline QskAspect::Subcontrol qskSubControlAt(
21 const QskScrollView* scrollView, const QPointF& pos )
22{
23 using Q = QskScrollView;
24
25 const auto rect = scrollView->contentsRect();
26
27 // order is important as the handles are inside the bars !
28
29 for ( auto subControl : { Q::VerticalScrollHandle, Q::VerticalScrollBar,
30 Q::HorizontalScrollHandle, Q::HorizontalScrollBar } )
31 {
32 if ( scrollView->subControlRect( rect, subControl ).contains( pos ) )
33 return subControl;
34 }
35
36 return QskAspect::NoSubcontrol;
37}
38
39class QskScrollView::PrivateData
40{
41 public:
42 inline void resetScrolling( QskScrollView* scrollView )
43 {
44 setScrolling( scrollView, QskAspect::NoSubcontrol, 0.0 );
45 }
46
47 inline void setScrolling( QskScrollView* scrollView,
48 QskAspect::Subcontrol subControl, qreal pos )
49 {
50 if ( subControl == pressedSubControl )
51 return;
52
54 if ( subControl == VerticalScrollHandle || pressedSubControl == VerticalScrollHandle )
55 {
56 subControls[0] = VerticalScrollHandle;
57 subControls[1] = VerticalScrollBar;
58 }
59 else
60 {
61 subControls[0] = HorizontalScrollHandle;;
62 subControls[1] = HorizontalScrollBar;
63 }
64
65 pressedSubControl = subControl;
66 scrollPressPos = pos;
67
68 scrollView->update();
69
70 auto oldStates = scrollView->skinStates() | scrollView->scrollBarStates( subControl );
71 auto newStates = oldStates | QskScrollView::Pressed;
72
73 if ( pressedSubControl == QskAspect::NoSubcontrol )
74 qSwap( oldStates, newStates );
75
76 scrollView->startHintTransitions( { subControls[0] }, oldStates, newStates );
77 scrollView->startHintTransitions( { subControls[1] }, oldStates, newStates );
78 }
79
80 void setHovered( QskScrollView* scrollView, QskAspect::Subcontrol subControl )
81 {
82 if ( subControl == this->hoveredSubControl )
83 return;
84
86 if ( subControl == VerticalScrollHandle
87 || hoveredSubControl == VerticalScrollHandle
88 || subControl == VerticalScrollBar
89 || hoveredSubControl == VerticalScrollBar )
90 {
91 subControls[0] = VerticalScrollHandle;
92 subControls[1] = VerticalScrollBar;
93 }
94 else
95 {
96 subControls[0] = HorizontalScrollHandle;
97 subControls[1] = HorizontalScrollBar;
98 }
99
100 const bool wasHovered = hasState( subControls[1], QskScrollView::Hovered );
101
102 hoveredSubControl = subControl;
103
104 auto oldStates = scrollView->skinStates();
105 auto newStates = oldStates | QskScrollView::Hovered;
106
107 if ( hoveredSubControl == QskAspect::NoSubcontrol )
108 qSwap( oldStates, newStates );
109
110 scrollView->startHintTransitions( { subControls[0] }, oldStates, newStates );
111
112 if ( wasHovered != hasState( subControls[1], QskScrollView::Hovered ) )
113 scrollView->startHintTransitions( { subControls[1] }, oldStates, newStates );
114 }
115
116 bool hasState( QskAspect::Subcontrol subControl, QskAspect::State state ) const
117 {
118 if ( subControl == QskAspect::NoSubcontrol )
119 return false;
120
121 const auto stateSubcontrol =
122 ( state == QskControl::Hovered ) ? hoveredSubControl : pressedSubControl;
123
124 if ( subControl == stateSubcontrol )
125 return true;
126
127 // the scroll bar inherits pressed/hovered from the handle
128 if ( subControl == VerticalScrollBar )
129 return stateSubcontrol == VerticalScrollHandle;
130
131 if ( subControl == HorizontalScrollBar )
132 return stateSubcontrol == HorizontalScrollHandle;
133
134 return false;
135 }
136
137 Qt::ScrollBarPolicy horizontalScrollBarPolicy = Qt::ScrollBarAsNeeded;
138 Qt::ScrollBarPolicy verticalScrollBarPolicy = Qt::ScrollBarAsNeeded;
139
140 qreal scrollPressPos = 0.0;
141
142 QskAspect::Subcontrol pressedSubControl = QskAspect::NoSubcontrol;
143 QskAspect::Subcontrol hoveredSubControl = QskAspect::NoSubcontrol;
144};
145
146QskScrollView::QskScrollView( QQuickItem* parent )
147 : Inherited( parent )
148 , m_data( new PrivateData() )
149{
150 setAcceptHoverEvents( true );
151}
152
153QskScrollView::~QskScrollView()
154{
155}
156
157QskAnimationHint QskScrollView::flickHint() const
158{
160 QskScrollView::Viewport, QskAspect::NoState );
161}
162
163void QskScrollView::setVerticalScrollBarPolicy( Qt::ScrollBarPolicy policy )
164{
165 if ( policy != m_data->verticalScrollBarPolicy )
166 {
167 m_data->verticalScrollBarPolicy = policy;
168 update();
169
170 Q_EMIT verticalScrollBarPolicyChanged();
171 }
172}
173
174Qt::ScrollBarPolicy QskScrollView::verticalScrollBarPolicy() const
175{
176 return m_data->verticalScrollBarPolicy;
177}
178
179void QskScrollView::setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy policy )
180{
181 if ( policy != m_data->horizontalScrollBarPolicy )
182 {
183 m_data->horizontalScrollBarPolicy = policy;
184 update();
185
186 Q_EMIT horizontalScrollBarPolicyChanged();
187 }
188}
189
190Qt::ScrollBarPolicy QskScrollView::horizontalScrollBarPolicy() const
191{
192 return m_data->horizontalScrollBarPolicy;
193}
194
195bool QskScrollView::isScrolling( Qt::Orientation orientation ) const
196{
197 if ( orientation == Qt::Vertical )
198 return m_data->pressedSubControl == VerticalScrollHandle;
199 else
200 return m_data->pressedSubControl == HorizontalScrollHandle;
201}
202
203QskAspect::States QskScrollView::scrollBarStates(
204 QskAspect::Subcontrol subControl ) const
205{
206 auto states = skinStates();
207
208 if ( m_data->hasState( subControl, Pressed ) )
209 states |= Pressed;
210
211 if ( m_data->hasState( subControl, Hovered ) )
212 states |= Hovered;
213
214 return states;
215}
216
217QRectF QskScrollView::viewContentsRect() const
218{
219 const auto borderMetrics = boxBorderMetricsHint( Viewport );
220
221 const QRectF r = subControlRect( Viewport );
222 return r.marginsRemoved( borderMetrics.widths() );
223}
224
225void QskScrollView::mousePressEvent( QMouseEvent* event )
226{
227 const auto mousePos = qskMousePosition( event );
228
229 if ( subControlRect( VerticalScrollBar ).contains( mousePos ) )
230 {
231 const auto handleRect = subControlRect( VerticalScrollHandle );
232
233 if ( handleRect.contains( mousePos ) )
234 {
235 m_data->setScrolling( this, VerticalScrollHandle, mousePos.y() );
236 }
237 else
238 {
239 const QRectF vRect = viewContentsRect();
240
241 qreal y = scrollPos().y();
242
243 if ( mousePos.y() < handleRect.top() )
244 y -= vRect.height();
245 else
246 y += vRect.height();
247
248 setScrollPos( QPointF( scrollPos().x(), y ) );
249 }
250
251 return;
252 }
253
254 if ( subControlRect( HorizontalScrollBar ).contains( mousePos ) )
255 {
256 const QRectF handleRect = subControlRect( HorizontalScrollHandle );
257
258 if ( handleRect.contains( mousePos ) )
259 {
260 m_data->setScrolling( this, HorizontalScrollHandle, mousePos.x() );
261 }
262 else
263 {
264 const QRectF vRect = viewContentsRect();
265
266 qreal x = scrollPos().x();
267
268 if ( mousePos.x() < handleRect.left() )
269 x -= vRect.width();
270 else
271 x += vRect.width();
272
273 setScrollPos( QPointF( x, scrollPos().y() ) );
274 }
275
276 return;
277 }
278
279 Inherited::mousePressEvent( event );
280}
281
282void QskScrollView::mouseMoveEvent( QMouseEvent* event )
283{
284 if ( m_data->pressedSubControl == QskAspect::NoSubcontrol )
285 {
286 Inherited::mouseMoveEvent( event );
287 return;
288 }
289
290 const auto mousePos = qskMousePosition( event );
291 QPointF pos = scrollPos();
292
293 if ( m_data->pressedSubControl == HorizontalScrollHandle )
294 {
295 const qreal dx = mousePos.x() - m_data->scrollPressPos;
296 const qreal w = subControlRect( HorizontalScrollBar ).width();
297
298 pos.rx() += dx / w * scrollableSize().width();
299 m_data->scrollPressPos = mousePos.x();
300 }
301 else
302 {
303 const qreal dy = mousePos.y() - m_data->scrollPressPos;
304 const qreal h = subControlRect( VerticalScrollBar ).height();
305
306 pos.ry() += dy / h * scrollableSize().height();
307 m_data->scrollPressPos = mousePos.y();
308 }
309
310 if ( pos != scrollPos() )
311 setScrollPos( pos );
312}
313
314void QskScrollView::mouseReleaseEvent( QMouseEvent* event )
315{
316 if ( m_data->pressedSubControl == QskAspect::NoSubcontrol )
317 {
318 Inherited::mouseReleaseEvent( event );
319 return;
320 }
321
322 m_data->resetScrolling( this );
323}
324
325void QskScrollView::mouseUngrabEvent()
326{
327 m_data->resetScrolling( this );
328}
329
330void QskScrollView::hoverEnterEvent( QHoverEvent* event )
331{
332 const auto subControl = qskSubControlAt( this, qskHoverPosition( event ) );
333 m_data->setHovered( this, subControl );
334}
335
336void QskScrollView::hoverMoveEvent( QHoverEvent* event )
337{
338 const auto subControl = qskSubControlAt( this, qskHoverPosition( event ) );
339 m_data->setHovered( this, subControl );
340}
341
342void QskScrollView::hoverLeaveEvent( QHoverEvent* )
343{
344 m_data->setHovered( this, QskAspect::NoSubcontrol );
345}
346
347#ifndef QT_NO_WHEELEVENT
348
349QPointF QskScrollView::scrollOffset( const QWheelEvent* event ) const
350{
351 QPointF offset;
352
353 const auto pos = qskWheelPosition( event );
354 const auto viewRect = viewContentsRect();
355
356 if ( subControlRect( VerticalScrollBar ).contains( pos ) )
357 {
358 const auto steps = qskWheelSteps( event );
359 offset.setY( steps );
360 }
361 else if ( subControlRect( HorizontalScrollBar ).contains( pos ) )
362 {
363 const auto steps = qskWheelSteps( event );
364 offset.setX( steps );
365 }
366 else if ( viewRect.contains( pos ) )
367 {
368 offset = event->pixelDelta();
369 if ( offset.isNull() )
370 offset = event->angleDelta() / QWheelEvent::DefaultDeltasPerStep;
371 }
372
373 if ( !offset.isNull() )
374 {
375 const auto vs = viewRect.size() / 3.0;
376
377 offset.rx() *= vs.width();
378 offset.ry() *= vs.height();
379 }
380
381 return offset;
382}
383
384#endif
385
386Qt::Orientations QskScrollView::scrollableOrientations() const
387{
388 // layoutRect ???
389 const QRectF vr = contentsRect();
390
391 auto policyVertical = m_data->verticalScrollBarPolicy;
392 auto policyHorizontal = m_data->horizontalScrollBarPolicy;
393
394 if ( policyVertical == Qt::ScrollBarAsNeeded )
395 {
396 qreal height = vr.height();
397
398 if ( policyHorizontal == Qt::ScrollBarAlwaysOn )
399 height -= metric( HorizontalScrollBar | QskAspect::Size );
400
401 if ( scrollableSize().height() > height )
402 policyVertical = Qt::ScrollBarAlwaysOn;
403 }
404
405 if ( policyHorizontal == Qt::ScrollBarAsNeeded )
406 {
407 qreal width = vr.width();
408
409 if ( policyVertical == Qt::ScrollBarAlwaysOn )
410 width -= metric( VerticalScrollBar | QskAspect::Size );
411
412 if ( scrollableSize().width() > width )
413 {
414 policyHorizontal = Qt::ScrollBarAlwaysOn;
415
416 // we have to check the vertical once more
417
418 if ( ( policyVertical == Qt::ScrollBarAsNeeded ) &&
419 ( scrollableSize().height() >
420 vr.height() - metric( HorizontalScrollBar | QskAspect::Size ) ) )
421 {
422 policyVertical = Qt::ScrollBarAlwaysOn;
423 }
424 }
425 }
426
427 Qt::Orientations orientations;
428
429 if ( policyHorizontal == Qt::ScrollBarAlwaysOn )
430 orientations |= Qt::Horizontal;
431
432 if ( policyVertical == Qt::ScrollBarAlwaysOn )
433 orientations |= Qt::Vertical;
434
435 return orientations;
436}
437
438#include "moc_QskScrollView.cpp"
Lookup key for a QskSkinHintTable.
Definition QskAspect.h:15
@ FirstSystemState
Definition QskAspect.h:115
Subcontrol
For use within the rendering or lay-outing of a specific QskSkinnable.
Definition QskAspect.h:104
QRectF subControlRect(QskAspect::Subcontrol) const
static const QskAspect::State Hovered
Definition QskControl.h:56
QRectF contentsRect() const
QVector< QskAspect::Subcontrol > subControls() const
QskAnimationHint effectiveAnimation(QskAspect::Type, QskAspect::Subcontrol, QskAspect::States, QskSkinHintStatus *status=nullptr) const
QskBoxBorderMetrics boxBorderMetricsHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a border hint.
qreal metric(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a metric hint.