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