QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGraduationRenderer.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGraduationRenderer.h"
7#include "QskGraduationMetrics.h"
8#include "QskTickmarks.h"
9#include "QskSkinlet.h"
10#include "QskSGNode.h"
11#include "QskGraduationNode.h"
12#include "QskTextOptions.h"
13#include "QskTextColors.h"
14#include "QskGraphic.h"
15#include "QskColorFilter.h"
16#include "QskControl.h"
17#include "QskIntervalF.h"
18#include "QskFunctions.h"
19
20#include <qstring.h>
21#include <qfontmetrics.h>
22#include <qquickwindow.h>
23
24namespace
25{
26 class ScaleMap
27 {
28 public:
29 inline ScaleMap( bool isHorizontal, const QTransform& transform )
30 : t( isHorizontal ? transform.dx() : transform.dy() )
31 , f( isHorizontal ? transform.m11() : transform.m22() )
32 {
33 }
34
35 inline qreal map( qreal v ) const { return t + f * v; };
36
37 private:
38 const qreal t;
39 const qreal f;
40 };
41}
42
43static inline bool qskIsHorizontal( Qt::Edge edge )
44{
45 return edge & ( Qt::TopEdge | Qt::BottomEdge );
46}
47
48static QSGNode* qskRemoveTraillingNodes( QSGNode* node, QSGNode* childNode )
49{
50 QskSGNode::removeAllChildNodesFrom( node, childNode );
51 return nullptr;
52}
53
54static inline QTransform qskScaleTransform( Qt::Edge edge,
55 const QskIntervalF& boundaries, const QskIntervalF& range )
56{
57 using T = QTransform;
58
59 if ( qskIsHorizontal( edge ) )
60 {
61 auto transform = T::fromTranslate( -boundaries.lowerBound(), 0.0 );
62 transform *= T::fromScale( range.length() / boundaries.length(), 1.0 );
63 transform *= T::fromTranslate( range.lowerBound(), 0.0 );
64
65 return transform;
66 }
67 else
68 {
69 auto transform = T::fromTranslate( 0.0, -boundaries.lowerBound() );
70 transform *= T::fromScale( 1.0, -range.length() / boundaries.length() );
71 transform *= T::fromTranslate( 0.0, range.upperBound() );
72
73 return transform;
74 }
75}
76
77static inline quint8 qskLabelNodeRole( const QVariant& label )
78{
79 if ( !label.isNull() )
80 {
81 if ( label.canConvert< QString >() )
82 return 1;
83
84 if ( label.canConvert< QskGraphic >() )
85 return 2;
86 }
87
88 return QskSGNode::NoRole;
89}
90
91class QskGraduationRenderer::PrivateData
92{
93 public:
94
95 // Coordinates related to the scales
96 QskIntervalF boundaries;
97 QskTickmarks tickmarks;
98
99 /*
100 Item cooordinates. In case of an horizontal scale
101 position is an y coordinate, while range corresponds to x coordinates
102 ( vertical: v.v )
103 */
104 qreal position = 0.0;
105 QskIntervalF range;
106
107#if 1
108 QColor tickColor = Qt::black; // rgb value ???
109#endif
110
111 QskGraduationMetrics metrics = { 4, 6, 8 };
112 qreal spacing = 5.0;
113
114 QFont font;
115 QskTextColors textColors;
116
117 QskColorFilter colorFilter;
118
119 Qt::Edge edge = Qt::BottomEdge;
120 QskGraduationRenderer::Flags flags = ClampedLabels;
121};
122
123QskGraduationRenderer::QskGraduationRenderer()
124 : m_data( new PrivateData() )
125{
126}
127
128QskGraduationRenderer::~QskGraduationRenderer()
129{
130}
131
132void QskGraduationRenderer::setEdge( Qt::Edge edge )
133{
134 m_data->edge = edge;
135}
136
137Qt::Edge QskGraduationRenderer::edge() const
138{
139 return m_data->edge;
140}
141
142void QskGraduationRenderer::setFlag( Flag flag, bool on )
143{
144 if ( on )
145 m_data->flags |= flag;
146 else
147 m_data->flags &= ~flag;
148}
149
150void QskGraduationRenderer::setFlags( Flags flags )
151{
152 m_data->flags = flags;
153}
154
155QskGraduationRenderer::Flags QskGraduationRenderer::flags() const
156{
157 return m_data->flags;
158}
159
160void QskGraduationRenderer::setBoundaries( qreal lowerBound, qreal upperBound )
161{
162 setBoundaries( QskIntervalF( lowerBound, upperBound ) );
163}
164
165void QskGraduationRenderer::setBoundaries( const QskIntervalF& boundaries )
166{
167 m_data->boundaries = boundaries;
168}
169
170QskIntervalF QskGraduationRenderer::boundaries() const
171{
172 return m_data->boundaries;
173}
174
175qreal QskGraduationRenderer::position() const
176{
177 return m_data->position;
178}
179
180void QskGraduationRenderer::setPosition( qreal pos )
181{
182 m_data->position = pos;
183}
184
185void QskGraduationRenderer::setRange( qreal from, qreal to )
186{
187 setRange( QskIntervalF( from, to ) );
188}
189
190void QskGraduationRenderer::setRange( const QskIntervalF& range )
191{
192 m_data->range = range;
193}
194
195QskIntervalF QskGraduationRenderer::range() const
196{
197 return m_data->range;
198}
199
200void QskGraduationRenderer::setTickmarks( const QskTickmarks& tickmarks )
201{
202 m_data->tickmarks = tickmarks;
203}
204
205const QskTickmarks& QskGraduationRenderer::tickmarks() const
206{
207 return m_data->tickmarks;
208}
209
210void QskGraduationRenderer::setSpacing( qreal spacing )
211{
212 m_data->spacing = qMax( spacing, 0.0 );
213}
214
215qreal QskGraduationRenderer::spacing() const
216{
217 return m_data->spacing;
218}
219
220void QskGraduationRenderer::setTickColor( const QColor& color )
221{
222 m_data->tickColor = color;
223}
224
225QColor QskGraduationRenderer::tickColor() const
226{
227 return m_data->tickColor;
228}
229
230void QskGraduationRenderer::setTickMetrics( const QskGraduationMetrics& metrics )
231{
232 m_data->metrics = metrics;
233}
234
235const QskGraduationMetrics& QskGraduationRenderer::tickMetrics() const
236{
237 return m_data->metrics;
238}
239
240void QskGraduationRenderer::setFont( const QFont& font )
241{
242 m_data->font = font;
243}
244
245QFont QskGraduationRenderer::font() const
246{
247 return m_data->font;
248}
249
250void QskGraduationRenderer::setTextColors( const QskTextColors& textColors )
251{
252 m_data->textColors = textColors;
253}
254
255QskTextColors QskGraduationRenderer::textColors() const
256{
257 return m_data->textColors;
258}
259
260void QskGraduationRenderer::setColorFilter( const QskColorFilter& colorFilter )
261{
262 m_data->colorFilter = colorFilter;
263}
264
265const QskColorFilter& QskGraduationRenderer::colorFilter() const
266{
267 return m_data->colorFilter;
268}
269
270QSGNode* QskGraduationRenderer::updateNode(
271 const QskSkinnable* skinnable, QSGNode* node )
272{
273 enum Role : quint8 { Ticks = 1, Labels = 2 };
274 static const QVector< quint8 > roles = { Ticks, Labels };
275
276 const auto transform = qskScaleTransform(
277 m_data->edge, m_data->boundaries, m_data->range );
278
279 if ( node == nullptr )
280 node = new QSGNode();
281
282 for ( auto role : roles )
283 {
284 auto oldNode = QskSGNode::findChildNode( node, role );
285
286 auto newNode = ( role == Ticks )
287 ? updateTicksNode( transform, oldNode )
288 : updateLabelsNode( skinnable, transform, oldNode );
289
290 QskSGNode::replaceChildNode( roles, role, node, oldNode, newNode );
291 }
292
293 return node;
294}
295
296QSGNode* QskGraduationRenderer::updateTicksNode(
297 const QTransform& transform, QSGNode* node ) const
298{
299 QskIntervalF backbone;
300 if ( m_data->flags & Backbone )
301 backbone = m_data->boundaries;
302
303 const auto orientation = qskIsHorizontal( m_data->edge )
304 ? Qt::Horizontal : Qt::Vertical;
305
306 auto alignment = QskGraduationNode::Centered;
307
308 if ( !( m_data->flags & CenteredTickmarks ) )
309 {
310 switch( m_data->edge )
311 {
312 case Qt::LeftEdge:
313 case Qt::TopEdge:
314 alignment = QskGraduationNode::Leading;
315 break;
316 case Qt::BottomEdge:
317 case Qt::RightEdge:
318 alignment = QskGraduationNode::Trailing;
319 break;
320 }
321 }
322
323 auto graduationNode = QskSGNode::ensureNode< QskGraduationNode >( node );
324
325 graduationNode->setColor( m_data->tickColor );
326 graduationNode->setAxis( orientation, m_data->position, transform );
327 graduationNode->setTickMetrics( alignment, m_data->metrics );
328 graduationNode->setPixelAlignment( Qt::Horizontal | Qt::Vertical );
329
330 graduationNode->update( m_data->tickmarks, backbone );
331
332 return graduationNode;
333}
334
335QSGNode* QskGraduationRenderer::updateLabelsNode( const QskSkinnable* skinnable,
336 const QTransform& transform, QSGNode* node ) const
337{
338 const auto ticks = m_data->tickmarks.majorTicks();
339 if ( ticks.isEmpty() )
340 return nullptr;
341
342 if( node == nullptr )
343 node = new QSGNode;
344
345 const QFontMetricsF fm( m_data->font );
346
347 auto nextNode = node->firstChild();
348
349 QRectF lastRect; // to skip overlapping label
350
351 for ( auto tick : ticks )
352 {
353 const auto label = labelAt( tick );
354
355 const auto role = qskLabelNodeRole( label );
356
357 if ( nextNode && QskSGNode::nodeRole( nextNode ) != role )
358 nextNode = qskRemoveTraillingNodes( node, nextNode );
359
360 QSizeF size;
361
362 if ( label.canConvert< QString >() )
363 {
364 size = qskTextRenderSize( fm, label.toString() );
365 }
366 else if ( label.canConvert< QskGraphic >() )
367 {
368 const auto graphic = label.value< QskGraphic >();
369 if ( !graphic.isNull() )
370 {
371 size.rheight() = fm.height();
372 size.rwidth() = graphic.widthForHeight( size.height() );
373 }
374 }
375
376 if ( size.isEmpty() )
377 continue;
378
379 const auto rect = labelRect( transform, tick, size );
380
381 if ( !lastRect.isEmpty() && lastRect.intersects( rect ) )
382 {
383 /*
384 Label do overlap: in case it is the last tick we remove
385 the precessor - otherwise we simply skip this one
386 */
387
388 if ( tick != ticks.last() )
389 continue; // skip this label
390
391 if ( auto obsoleteNode = nextNode
392 ? nextNode->previousSibling() : node->lastChild() )
393 {
394 node->removeChildNode( obsoleteNode );
395 if ( obsoleteNode->flags() & QSGNode::OwnedByParent )
396 delete obsoleteNode;
397 }
398 }
399
400 nextNode = updateTickLabelNode( skinnable, nextNode, label, rect );
401
402 if ( nextNode)
403 {
404 lastRect = rect;
405
406 if ( nextNode->parent() != node )
407 {
408 QskSGNode::setNodeRole( nextNode, role );
409 node->appendChildNode( nextNode );
410 }
411
412 nextNode = nextNode->nextSibling();
413 }
414 }
415
416 qskRemoveTraillingNodes( node, nextNode );
417
418 return node;
419}
420
421QVariant QskGraduationRenderer::labelAt( qreal pos ) const
422{
423 return QString::number( pos, 'g' );
424}
425
426// should be cached
427QSizeF QskGraduationRenderer::boundingLabelSize() const
428{
429 QSizeF boundingSize( 0.0, 0.0 );
430
431 const auto ticks = m_data->tickmarks.majorTicks();
432 if ( ticks.isEmpty() )
433 return boundingSize;
434
435 const QFontMetricsF fm( m_data->font );
436
437 const qreal h = fm.height();
438
439 for ( auto tick : ticks )
440 {
441 const auto label = labelAt( tick );
442 if ( label.isNull() )
443 continue;
444
445 if ( label.canConvert< QString >() )
446 {
447 boundingSize = boundingSize.expandedTo(
448 qskTextRenderSize( fm, label.toString() ) );
449 }
450 else if ( label.canConvert< QskGraphic >() )
451 {
452 const auto graphic = label.value< QskGraphic >();
453 if ( !graphic.isNull() )
454 {
455 const auto w = graphic.widthForHeight( h );
456 boundingSize.setWidth( qMax( boundingSize.width(), w ) );
457 }
458 }
459 }
460
461 return boundingSize;
462}
463
464QRectF QskGraduationRenderer::labelRect(
465 const QTransform& transform, qreal tick, const QSizeF& labelSize ) const
466{
467 const auto isHorizontal = qskIsHorizontal( m_data->edge );
468
469 const auto tickLength = m_data->metrics.maxLength();
470
471 auto offset = tickLength + m_data->spacing;
472 if ( m_data->flags & CenteredTickmarks )
473 offset -= 0.5 * tickLength;
474
475 const bool clampLabels = m_data->flags & ClampedLabels;
476
477 const qreal w = labelSize.width();
478 const qreal h = labelSize.height();
479
480 qreal x, y;
481
482 const ScaleMap map( isHorizontal, transform );
483
484 const auto tickPos = map.map( tick );
485
486 qreal min, max;
487 if ( clampLabels )
488 {
489 min = map.map( m_data->boundaries.lowerBound() );
490 max = map.map( m_data->boundaries.upperBound() );
491 }
492
493 if( isHorizontal )
494 {
495 x = tickPos - 0.5 * w;
496
497 if ( clampLabels )
498 x = qBound( min, x, max - w );
499
500 y = m_data->position + offset;
501 }
502 else
503 {
504 const auto tickPos = map.map( tick );
505 y = tickPos - 0.5 * h;
506
507 if ( clampLabels )
508 y = qBound( max, y, min - h );
509
510 x = m_data->position - offset - w;
511 }
512
513 return QRectF( x, y, w, h );
514}
515
516QSGNode* QskGraduationRenderer::updateTickLabelNode( const QskSkinnable* skinnable,
517 QSGNode* node, const QVariant& label, const QRectF& rect ) const
518{
519 if ( label.canConvert< QString >() )
520 {
521 return QskSkinlet::updateTextNode( skinnable, node,
522 rect, Qt::AlignCenter, label.toString(), m_data->font,
523 QskTextOptions(), m_data->textColors, Qsk::Normal );
524 }
525
526 if ( label.canConvert< QskGraphic >() )
527 {
528 const auto alignment = qskIsHorizontal( m_data->edge )
529 ? ( Qt::AlignHCenter | Qt::AlignBottom )
530 : ( Qt::AlignRight | Qt::AlignVCenter );
531
532 return QskSkinlet::updateGraphicNode(
533 skinnable, node, label.value< QskGraphic >(),
534 m_data->colorFilter, rect, alignment );
535 }
536
537 return nullptr;
538}
539
540#include "moc_QskGraduationRenderer.cpp"
A paint device for scalable graphics.
Definition QskGraphic.h:28