QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskDrawer.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskDrawer.h"
7#include "QskAspect.h"
8#include "QskQuick.h"
9#include "QskEvent.h"
10
11#include "QskPanGestureRecognizer.h"
12#include "QskGesture.h"
13
14#include <qguiapplication.h>
15#include <qstylehints.h>
16
17QSK_QT_PRIVATE_BEGIN
18#include <private/qquickitem_p.h>
19QSK_QT_PRIVATE_END
20
21QSK_SUBCONTROL( QskDrawer, Panel )
22
23static inline qreal qskDefaultDragMargin()
24{
25 // a skin hint ???
26 return QGuiApplication::styleHints()->startDragDistance();
27}
28
29static inline void qskCatchMouseEvents( QQuickItem* item )
30{
31#if 1
32 // manipulating other items - do we really want to do this ?
33 item->setAcceptedMouseButtons( Qt::LeftButton );
34 item->setFiltersChildMouseEvents( true );
35#endif
36}
37
38static bool qskCheckDirection( Qt::Edge edge, const QskPanGesture* gesture )
39{
40 const auto degrees = gesture->angle();
41
42 switch( edge )
43 {
44 case Qt::LeftEdge:
45 return ( degrees < 90.0 ) || ( degrees ) > 270.0;
46
47 case Qt::RightEdge:
48 return ( degrees > 90.0 ) && ( degrees < 270.0 );
49
50 case Qt::TopEdge:
51 return degrees > 180.0;
52
53 case Qt::BottomEdge:
54 return degrees < 180.0;
55 }
56
57 return false;
58}
59
60static inline QRectF qskAlignedToEdge(
61 const QRectF& r, const QSizeF& sz, Qt::Edge edge )
62{
63 switch( edge )
64 {
65 case Qt::LeftEdge:
66 return QRectF( r.left(), r.top(), sz.width(), r.height() );
67
68 case Qt::RightEdge:
69 return QRectF( r.right() - sz.width(), r.top(), sz.width(), r.height() );
70
71 case Qt::TopEdge:
72 return QRectF( r.left(), r.top(), r.width(), sz.height() );
73
74 case Qt::BottomEdge:
75 return QRectF( r.left(), r.bottom() - sz.height(), r.width(), sz.height() );
76 }
77
78 return QRectF();
79}
80
81static QPointF qskDrawerTranslation( const QskDrawer* drawer, const QSizeF& size )
82{
83 const auto ratio = 1.0 - drawer->fadingFactor();
84
85 auto dx = 0.0;
86 auto dy = 0.0;
87
88 switch( drawer->edge() )
89 {
90 case Qt::LeftEdge:
91 dx = -ratio * size.width();
92 break;
93
94 case Qt::RightEdge:
95 dx = ratio * size.width();
96 break;
97
98 case Qt::TopEdge:
99 dy = -ratio * size.height();
100 break;
101
102 case Qt::BottomEdge:
103 dy = ratio * size.height();
104 break;
105 }
106
107 return QPointF( dx, dy );
108}
109
110namespace
111{
112 class GestureRecognizer : public QskPanGestureRecognizer
113 {
114 using Inherited = QskPanGestureRecognizer;
115
116 public:
117 GestureRecognizer( QskDrawer* drawer )
118 : QskPanGestureRecognizer( drawer )
119 {
120 setWatchedItem( drawer->parentItem() );
121 setTargetItem( drawer );
122 }
123
124 protected:
125 bool isAcceptedPos( const QPointF& pos ) const override
126 {
127 auto drawer = qobject_cast< const QskDrawer* >( targetItem() );
128 if ( drawer->isFading() )
129 return false;
130
131 auto rect = qskItemRect( watchedItem() );
132
133 if ( !drawer->isOpen() )
134 {
135 const auto dragMargin = drawer->dragMargin();
136 if ( dragMargin <= 0.0 )
137 return false;
138
139 switch( drawer->edge() )
140 {
141 case Qt::LeftEdge:
142 rect.setRight( rect.left() + dragMargin );
143 break;
144
145 case Qt::RightEdge:
146 rect.setLeft( rect.right() - dragMargin );
147 break;
148
149 case Qt::TopEdge:
150 rect.setBottom( rect.top() + dragMargin );
151 break;
152
153 case Qt::BottomEdge:
154 rect.setTop( rect.bottom() - dragMargin );
155 break;
156 }
157 }
158
159 return rect.contains( pos );
160 }
161 };
162}
163
164class QskDrawer::PrivateData
165{
166 public:
167 PrivateData( Qt::Edge edge )
168 : edge( edge )
169 {
170 }
171
172 GestureRecognizer* gestureRecognizer = nullptr;
173
174 qreal dragMargin = qskDefaultDragMargin();
175 Qt::Edge edge;
176};
177
178QskDrawer::QskDrawer( QQuickItem* parentItem )
179 : QskDrawer( Qt::LeftEdge, parentItem )
180{
181 setPolishOnParentResize( true );
182}
183
184QskDrawer::QskDrawer( Qt::Edge edge, QQuickItem* parentItem )
185 : Inherited ( parentItem )
186 , m_data( new PrivateData( edge ) )
187{
188#if 1
189 setZ( 1 );
190#endif
191
192 setPopupFlag( PopupFlag::CloseOnPressOutside, true );
193
194 /*
195 A drawer wants to be on top of its parent - not being
196 layouted into its layoutRect(). So we opt out and do
197 the layout updates manually.
198 */
199 setPlacementPolicy( QskPlacementPolicy::Ignore );
200
201 setAutoLayoutChildren( true );
202 setInteractive( true );
203
204 connect( this, &QskPopup::fadingChanged, this, &QQuickItem::setClip );
205}
206
207QskDrawer::~QskDrawer()
208{
209}
210
211Qt::Edge QskDrawer::edge() const
212{
213 return m_data->edge;
214}
215
216void QskDrawer::setEdge( Qt::Edge edge )
217{
218 if( m_data->edge == edge )
219 return;
220
221 update();
222 m_data->edge = edge;
223 edgeChanged( edge );
224}
225
226void QskDrawer::setInteractive( bool on )
227{
228 if ( on == isInteractive() )
229 return;
230
231 if ( on )
232 {
233 m_data->gestureRecognizer = new GestureRecognizer( this );
234 if ( parentItem() )
235 qskCatchMouseEvents( parentItem() );
236 }
237 else
238 {
239 // how to revert qskCatchMouseEvents properly ???
240 delete m_data->gestureRecognizer;
241 m_data->gestureRecognizer = nullptr;
242 }
243
244 Q_EMIT interactiveChanged( on );
245}
246
247bool QskDrawer::isInteractive() const
248{
249 return m_data->gestureRecognizer != nullptr;
250}
251
252void QskDrawer::setDragMargin( qreal margin )
253{
254 margin = std::max( margin, 0.0 );
255
256 if ( margin != m_data->dragMargin )
257 {
258 m_data->dragMargin = margin;
259 Q_EMIT dragMarginChanged( margin );
260 }
261}
262
263void QskDrawer::resetDragMargin()
264{
265 setDragMargin( qskDefaultDragMargin() );
266}
267
268qreal QskDrawer::dragMargin() const
269{
270 return m_data->dragMargin;
271}
272
273void QskDrawer::gestureEvent( QskGestureEvent* event )
274{
275 if ( event->gesture()->type() == QskGesture::Pan )
276 {
277 /*
278 For the moment we treat the gesture like a swipe gesture
279 without dragging the drawer when moving the mouse. TODO ...
280 */
281 const auto gesture = static_cast< const QskPanGesture* >( event->gesture().get() );
282 if ( gesture->state() == QskGesture::Finished )
283 {
284 const auto forwards = qskCheckDirection( m_data->edge, gesture );
285 if ( forwards != isOpen() )
286 setOpen( forwards );
287 }
288
289 return;
290 }
291
292 Inherited::gestureEvent( event );
293}
294
295void QskDrawer::itemChange( QQuickItem::ItemChange change,
296 const QQuickItem::ItemChangeData& value )
297{
298 Inherited::itemChange( change, value );
299
300 switch( static_cast< int >( change ) )
301 {
302 case QQuickItem::ItemParentHasChanged:
303 {
304 if ( parentItem() && isInteractive() )
305 qskCatchMouseEvents( parentItem() );
306
307 break;
308 }
309 }
310}
311
312void QskDrawer::updateResources()
313{
314 Inherited::updateResources();
315
316 /*
317 Adjusting the geometry to the parent needs to be done before
318 the layouting of the children ( -> autoLayoutChildren ) is done.
319 So we are using this hook even if it is not about resources: TODO ...
320 */
321 if ( const auto item = parentItem() )
322 {
323 auto r = qskItemRect( item );
324 r = qskAlignedToEdge( r, sizeConstraint( Qt::PreferredSize ), edge() );
325
326 r.translate( qskDrawerTranslation( this, r.size() ) );
327 setGeometry( r );
328 }
329}
330
331void QskDrawer::updateNode( QSGNode* node )
332{
333 if ( isFading() && clip() )
334 {
335 if ( auto clipNode = QQuickItemPrivate::get( this )->clipNode() )
336 {
337 /*
338 The clipRect is changing while fading. Couldn't
339 find a way how to trigger updates - maybe be enabling/disabling
340 the clip. So we do the updates manually. TODO ...
341 */
342 const auto r = clipRect();
343 if ( r != clipNode->rect() )
344 {
345 clipNode->setRect( r );
346 clipNode->update();
347 }
348 }
349 }
350
351 Inherited::updateNode( node );
352}
353
354QRectF QskDrawer::clipRect() const
355{
356 if ( isFading() && parentItem() )
357 {
358 /*
359 We might not fit into our parent and our children not
360 into our rect. So we want to have a clip against the
361 edge, where the drawer slides in/out only.
362 Otherwise we would have unwanted effects, when clipping gets
363 disabled once the transition is over.
364 */
365 constexpr qreal d = 1e6;
366
367 QRectF r( -d, -d, 2.0 * d, 2.0 * d );
368
369 switch( edge() )
370 {
371 case Qt::LeftEdge:
372 r.setLeft( -x() );
373 break;
374
375 case Qt::RightEdge:
376 r.setRight( parentItem()->width() - x() );
377 break;
378
379 case Qt::TopEdge:
380 r.setTop( -y() );
381 break;
382
383 case Qt::BottomEdge:
384 r.setBottom( parentItem()->height() - y() );
385 break;
386 }
387
388 return r;
389 }
390
391 return Inherited::clipRect();
392}
393
394QskAspect QskDrawer::fadingAspect() const
395{
396 return QskDrawer::Panel | QskAspect::Position;
397}
398
399QRectF QskDrawer::layoutRectForSize( const QSizeF& size ) const
400{
401 return subControlContentsRect( size, Panel );
402}
403
404#include "moc_QskDrawer.cpp"
Lookup key for a QskSkinHintTable.
Definition QskAspect.h:15
QRectF subControlContentsRect(QskAspect::Subcontrol) const
QSizeF sizeConstraint
Definition QskControl.h:50
QRectF layoutRectForSize(const QSizeF &) const override
void updateNode(QSGNode *) override
void setGeometry(qreal x, qreal y, qreal width, qreal height)
Definition QskItem.cpp:330
virtual void updateNode(QSGNode *)