QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskSegmentedBarSkinlet.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskSegmentedBarSkinlet.h"
7#include "QskSegmentedBar.h"
8
9#include "QskLabelData.h"
10#include "QskGraphic.h"
11#include "QskColorFilter.h"
12#include "QskFunctions.h"
13#include "QskSGNode.h"
14#include "QskSkin.h"
15#include "QskSkinStateChanger.h"
16#include "QskSubcontrolLayoutEngine.h"
17#include "QskBoxHints.h"
18
19#include <qfontmetrics.h>
20#include <qmath.h>
21
22namespace
23{
24 QskBoxHints effectiveBoxHints( QskAspect::Subcontrol subControl,
25 const QskSegmentedBar* bar, int index )
26 {
27 using Q = QskSegmentedBar;
28
29 auto boxHints = bar->boxHints( subControl );
30
31 const bool leading = ( index == 0 );
32 const bool trailing = ( index == bar->count() - 1 );
33
34 if ( !( leading || trailing ) )
35 return boxHints;
36
37 // something more expressive than just a boolean. TODO ...
38 if ( !bar->flagHint< bool >( Q::Panel | QskAspect::Option, false ) )
39 return boxHints;
40
41 const auto panelShape = bar->boxShapeHint( Q::Panel );
42 auto& shape = boxHints.shape;
43
44 // when there is only 1 segment we have to fit both ends
45
46 if ( leading )
47 {
48 Qt::Corner corners[2];
49
50 corners[0] = Qt::TopLeftCorner;
51
52 if ( bar->orientation() == Qt::Vertical )
53 corners[1] = Qt::TopRightCorner;
54 else
55 corners[1] = Qt::BottomLeftCorner;
56
57 shape.setSizeMode( panelShape.sizeMode() );
58 shape.setRadius( corners[0], panelShape.radius( corners[0] ) );
59 shape.setRadius( corners[1], panelShape.radius( corners[1] ) );
60 }
61
62 if ( trailing )
63 {
64 Qt::Corner corners[2];
65
66 corners[0] = Qt::BottomRightCorner;
67
68 if ( bar->orientation() == Qt::Vertical )
69 corners[1] = Qt::BottomLeftCorner;
70 else
71 corners[1] = Qt::TopRightCorner;
72
73 shape.setSizeMode( panelShape.sizeMode() );
74 shape.setRadius( corners[0], panelShape.radius( corners[0] ) );
75 shape.setRadius( corners[1], panelShape.radius( corners[1] ) );
76 }
77
78 boxHints.borderMetrics = bar->boxBorderMetricsHint( Q::Panel );
79 boxHints.borderColors = QColor();
80
81 return boxHints;
82 }
83
84
85 QskGraphic iconAt( const QskSegmentedBar* bar, const int index )
86 {
87 using Q = QskSegmentedBar;
88
89 if ( bar->selectedIndex() == index )
90 {
91 /*
92 Material 3 replaces the icon of the selected element by a checkmark,
93 when icon and text are set. So this code is actually not correct
94 as it also replaces the icon when there is no text
95 */
96
97 const auto icon = bar->symbolHint( Q::Icon | Q::Selected );
98 if ( !icon.isNull() )
99 return icon;
100 }
101
102 return bar->optionAt( index ).icon().graphic();
103 }
104
105 class LayoutEngine : public QskSubcontrolLayoutEngine
106 {
107 public:
108 LayoutEngine( const QskSegmentedBar* bar, int index )
109 : QskSubcontrolLayoutEngine( bar->orientation() )
110 {
111 using Q = QskSegmentedBar;
112
113 setSpacing( bar->spacingHint( Q::Panel ) );
114
115 const auto option = bar->optionAt( index );
116
117 setGraphicTextElements( bar, Q::Text, option.text(),
118 Q::Icon, iconAt( bar, index ).defaultSize() );
119
120 if( bar->orientation() == Qt::Horizontal )
121 {
122 const auto alignment = bar->alignmentHint( Q::Panel, Qt::AlignCenter );
123 setFixedContent( Q::Text, Qt::Horizontal, alignment );
124 }
125 }
126 };
127}
128
129QskSegmentedBarSkinlet::QskSegmentedBarSkinlet( QskSkin* skin )
130 : Inherited( skin )
131{
132 setNodeRoles( { PanelRole, SegmentRole, SeparatorRole,
133 CursorRole, SplashRole, TextRole, IconRole } );
134}
135
136QskSegmentedBarSkinlet::~QskSegmentedBarSkinlet() = default;
137
138QRectF QskSegmentedBarSkinlet::subControlRect(
139 const QskSkinnable* skinnable, const QRectF& contentsRect,
140 QskAspect::Subcontrol subControl ) const
141{
142 using Q = QskSegmentedBar;
143
144 const auto bar = static_cast< const QskSegmentedBar* >( skinnable );
145
146 if( subControl == Q::Panel )
147 return contentsRect;
148
149 if( subControl == Q::Cursor )
150 return cursorRect( bar, contentsRect );
151
152 if( subControl == Q::Splash )
153 return splashRect( bar, contentsRect );
154
155 return Inherited::subControlRect( skinnable, contentsRect, subControl );
156}
157
158QRectF QskSegmentedBarSkinlet::cursorRect(
159 const QskSegmentedBar* bar, const QRectF& contentsRect ) const
160{
161 using Q = QskSegmentedBar;
162
163 if( bar->selectedIndex() < 0 || bar->count() <= 0 )
164 return QRectF();
165
166 auto rect = subControlRect( bar, contentsRect, Q::Panel );
167
168 if( rect.isEmpty() )
169 return QRectF();
170
171 // position is related to the index: 2.5 means
172 // the cursor is between segment[2] and segment[3]
173
174 const qreal position = bar->positionHint( Q::Cursor );
175
176 const int index1 = qFloor( position );
177 const int index2 = qCeil( position );
178
179 auto cursorRect = segmentRect( bar, contentsRect, index1 );
180 if ( index1 != index2 )
181 {
182 const auto targetRect = segmentRect( bar, contentsRect, index2 );
183 cursorRect = qskInterpolatedRect( cursorRect, targetRect, position - index1 );
184 }
185
186 return cursorRect;
187}
188
189QRectF QskSegmentedBarSkinlet::splashRect(
190 const QskSegmentedBar* bar, const QRectF& contentsRect ) const
191{
192 using Q = QskSegmentedBar;
193
194 QRectF rect;
195
196 const auto ratio = bar->metric( Q::Splash | QskAspect::Size );
197
198 if ( ratio > 0.0 )
199 {
200 const auto pos = bar->effectiveSkinHint(
201 Q::Splash | QskAspect::Metric | QskAspect::Position ).toPointF();
202
203 const int index = bar->indexAtPosition( pos );
204
205 if( index >= 0 && index < bar->count() )
206 {
207 const auto sr = segmentRect( bar, contentsRect, index );
208 rect = sr;
209 qreal w, h;
210
211 if( bar->orientation() == Qt::Horizontal )
212 {
213 w = 2.0 * rect.width() * ratio;
214 h = 2.0 * rect.height();
215 }
216 else
217 {
218 w = 2.0 * rect.width();
219 h = 2.0 * rect.height() * ratio;
220 }
221
222 rect.setSize( { w, h } );
223 rect.moveCenter( pos );
224 rect = rect.intersected( sr );
225 }
226 }
227
228 return rect;
229}
230
231QRectF QskSegmentedBarSkinlet::segmentRect(
232 const QskSegmentedBar* bar, const QRectF& contentsRect, int index ) const
233{
234 using Q = QskSegmentedBar;
235
236 const auto count = bar->count();
237
238 auto rect = subControlRect( bar, contentsRect, Q::Panel );
239
240 if( bar->orientation() == Qt::Horizontal )
241 {
242 const qreal w = rect.width() / count;
243
244 rect.setLeft( index * w );
245 rect.setWidth( w );
246 }
247 else
248 {
249 const qreal h = rect.height() / count;
250
251 rect.setTop( index * h );
252 rect.setHeight( h );
253 }
254
255 return rect;
256}
257
258QRectF QskSegmentedBarSkinlet::separatorRect(
259 const QskSegmentedBar* bar, const QRectF& contentsRect, int index ) const
260{
261 using Q = QskSegmentedBar;
262
263 if( index == bar->count() - 1 )
264 return QRectF(); // no separator behind the last segment
265
266 auto rect = segmentRect( bar, contentsRect, index );
267
268 auto sh = bar->sizeHint();
269
270 const QSizeF strutSize = bar->strutSizeHint( Q::Separator );
271
272 if( bar->orientation() == Qt::Horizontal )
273 {
274 rect.setLeft( rect.right() );
275 rect.setSize( { strutSize.width(), sh.height() } );
276 }
277 else
278 {
279 rect.setTop( rect.bottom() );
280 rect.setSize( { sh.width(), strutSize.height() } );
281 }
282
283 const auto padding = bar->paddingHint( Q::Separator );
284 rect = rect.marginsRemoved( padding );
285
286 return rect;
287}
288
289QSGNode* QskSegmentedBarSkinlet::updateSubNode(
290 const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
291{
292 using Q = QskSegmentedBar;
293
294 const auto bar = static_cast< const QskSegmentedBar* >( skinnable );
295
296 switch( nodeRole )
297 {
298 case CursorRole:
299 return updateBoxNode( skinnable, node, Q::Cursor );
300
301 case SplashRole:
302 return updateSplashNode( bar, node );
303
304 case PanelRole:
305 return updateBoxNode( skinnable, node, Q::Panel );
306
307 case SegmentRole:
308 return updateSeriesNode( skinnable, Q::Segment, node );
309
310 case SeparatorRole:
311 return updateSeriesNode( skinnable, Q::Separator, node );
312
313 case TextRole:
314 return updateSeriesNode( skinnable, Q::Text, node );
315
316 case IconRole:
317 return updateSeriesNode( skinnable, Q::Icon, node );
318 }
319
320 return nullptr;
321}
322
323QSizeF QskSegmentedBarSkinlet::segmentSizeHint(
324 const QskSegmentedBar* bar, Qt::SizeHint which ) const
325{
326 using Q = QskSegmentedBar;
327
328 const QSizeF sizeSymbol =
329 bar->symbolHint( Q::Icon | Q::Selected ).defaultSize();
330
331 QSizeF segmentSize;
332
333 for ( int i = 0; i < bar->count(); i++ )
334 {
335 const auto option = bar->optionAt( i );
336
337 auto iconSize = option.icon().graphic().defaultSize();
338 iconSize = iconSize.expandedTo( sizeSymbol );
339
340 LayoutEngine layoutEngine( bar, i );
341 layoutEngine.setGraphicTextElements( bar,
342 Q::Text, option.text(), Q::Icon, iconSize );
343
344 const auto size = layoutEngine.sizeHint( which, QSizeF() );
345 segmentSize = segmentSize.expandedTo( size );
346 }
347
348 segmentSize = bar->outerBoxSize( Q::Segment, segmentSize );
349 segmentSize = segmentSize.expandedTo( bar->strutSizeHint( Q::Segment ) );
350 segmentSize = segmentSize.grownBy( bar->marginHint( Q::Segment ) );
351
352 return segmentSize;
353}
354
355QSizeF QskSegmentedBarSkinlet::sizeHint( const QskSkinnable* skinnable,
356 Qt::SizeHint which, const QSizeF& ) const
357{
358 using Q = QskSegmentedBar;
359
360 if ( which != Qt::PreferredSize )
361 return QSizeF();
362
363 const auto count = sampleCount( skinnable, Q::Segment );
364 if( count == 0 )
365 return QSizeF( 0, 0 );
366
367 qreal w = 0;
368 qreal h = 0;
369
370 if ( count > 0 )
371 {
372 const qreal spacing = skinnable->spacingHint( Q::Panel );
373
374 const auto bar = static_cast< const QskSegmentedBar* >( skinnable );
375 const auto segmentSize = segmentSizeHint( bar, which );
376
377 if( bar->orientation() == Qt::Horizontal )
378 {
379 w = count * segmentSize.width() + ( count - 1 ) * spacing;
380 h = segmentSize.height();
381 }
382 else
383 {
384 w = segmentSize.width();
385 h = count * segmentSize.height() + ( count - 1 ) * spacing;
386 }
387 }
388
389 const auto hint = skinnable->outerBoxSize( Q::Panel, QSizeF( w, h ) );
390 return hint.expandedTo( skinnable->strutSizeHint( Q::Panel ) );
391}
392
393int QskSegmentedBarSkinlet::sampleCount(
394 const QskSkinnable* skinnable, QskAspect::Subcontrol ) const
395{
396 const auto bar = static_cast< const QskSegmentedBar* >( skinnable );
397 return bar->count();
398}
399
400QRectF QskSegmentedBarSkinlet::sampleRect( const QskSkinnable* skinnable,
401 const QRectF& contentsRect, QskAspect::Subcontrol subControl, int index ) const
402{
403 using Q = QskSegmentedBar;
404 const auto bar = static_cast< const QskSegmentedBar* >( skinnable );
405
406 if ( subControl == Q::Segment )
407 {
408 return segmentRect( bar, contentsRect, index );
409 }
410
411 if ( subControl == Q::Separator )
412 {
413 return separatorRect( bar, contentsRect, index );
414 }
415
416 if ( subControl == Q::Text || subControl == Q::Icon )
417 {
418 const auto rect = sampleRect( skinnable, contentsRect, Q::Segment, index );
419
420 LayoutEngine layoutEngine( bar, index );
421 layoutEngine.setGeometries( rect );
422
423 return layoutEngine.subControlRect( subControl );
424 }
425
426 return Inherited::sampleRect( skinnable, contentsRect, subControl, index );
427}
428
429QskAspect::States QskSegmentedBarSkinlet::sampleStates(
430 const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const
431{
432 using Q = QskSegmentedBar;
433 using A = QskAspect;
434
435 auto states = Inherited::sampleStates( skinnable, subControl, index );
436
437 const auto* bar = static_cast< const QskSegmentedBar* >( skinnable );
438
439 if ( subControl == Q::Segment )
440 {
441 if ( bar->isSegmentEnabled( index ) )
442 {
443 if ( bar->selectedIndex() == index )
444 states |= Q::Selected;
445 }
446 else
447 {
448 states |= Q::Disabled;
449 }
450
451 const auto cursorPos = bar->effectiveSkinHint(
452 Q::Segment | Q::Hovered | A::Metric | A::Position ).toPointF();
453
454 if( !cursorPos.isNull() && bar->indexAtPosition( cursorPos ) == index )
455 {
456 states |= Q::Hovered;
457 }
458 else
459 {
460 states &= ~Q::Hovered;
461 }
462
463 const auto focusIndex = bar->positionHint( Q::Segment | Q::Focused );
464
465 if( focusIndex >= 0 && focusIndex < bar->count() )
466 {
467 if( focusIndex == index )
468 {
469 states |= Q::Focused;
470 }
471 else
472 {
473 states &= ~Q::Focused;
474 }
475 }
476 }
477 else if( subControl == Q::Icon || subControl == Q::Text )
478 {
479 if ( bar->isSegmentEnabled( index ) )
480 {
481 if ( bar->selectedIndex() == index )
482 states |= Q::Selected;
483 }
484 else
485 {
486 states |= Q::Disabled;
487 }
488 }
489
490 return states;
491}
492
493QSGNode* QskSegmentedBarSkinlet::updateSampleNode( const QskSkinnable* skinnable,
494 QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
495{
496 using Q = QskSegmentedBar;
497
498 auto bar = static_cast< const QskSegmentedBar* >( skinnable );
499
500 const auto rect = sampleRect( bar, bar->contentsRect(), subControl, index );
501
502 if ( subControl == Q::Separator )
503 {
504 return updateBoxNode( skinnable, node, rect, subControl );
505 }
506
507 if ( subControl == Q::Segment )
508 {
509 const auto boxHints = effectiveBoxHints( subControl, bar, index );
510 return updateBoxNode( bar, node, rect, boxHints );
511 }
512
513 const auto alignment = bar->alignmentHint( subControl, Qt::AlignCenter );
514
515 if ( subControl == Q::Text )
516 {
517 const auto text = bar->optionAt( index ).text();
518
519 if( !text.isEmpty() )
520 {
521 return QskSkinlet::updateTextNode( bar, node,
522 rect, alignment, text, Q::Text );
523 }
524
525 return nullptr;
526 }
527
528 if ( subControl == Q::Icon )
529 {
530 const auto graphic = iconAt( bar, index );
531
532 if( !graphic.isEmpty() )
533 {
534 const auto filter = bar->effectiveGraphicFilter( subControl );
535 const auto padding = bar->paddingHint( Q::Icon );
536 const auto graphicRect = rect.marginsRemoved( padding );
537
538 return QskSkinlet::updateGraphicNode(
539 bar, node, graphic, filter, graphicRect, alignment );
540 }
541
542 return nullptr;
543 }
544
545 return Inherited::updateSampleNode( skinnable, subControl, index, node );
546}
547
548QSGNode* QskSegmentedBarSkinlet::updateSplashNode(
549 const QskSegmentedBar* bar, QSGNode* node ) const
550{
551 using Q = QskSegmentedBar;
552
553 const auto splashRect = bar->subControlRect( Q::Splash );
554 if ( splashRect.isEmpty() )
555 return nullptr;
556
557 auto clipNode = updateBoxClipNode(
558 bar, node, bar->subControlRect( Q::Panel ), Q::Panel );
559
560 if ( clipNode )
561 {
562 auto boxNode = updateBoxNode( bar, clipNode->firstChild(), splashRect, Q::Splash );
563 if ( boxNode->parent() == nullptr )
564 clipNode->appendChildNode( boxNode );
565 }
566
567 return clipNode;
568}
569
570#include "moc_QskSegmentedBarSkinlet.cpp"
Lookup key for a QskSkinHintTable.
Definition QskAspect.h:15
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 Disabled
Definition QskControl.h:56
static const QskAspect::State Hovered
Definition QskControl.h:56
QRectF contentsRect() const
static const QskAspect::State Focused
Definition QskControl.h:56
QSizeF sizeHint() const
Definition QskControl.h:214
A paint device for scalable graphics.
Definition QskGraphic.h:28
QVariant effectiveSkinHint(QskAspect, QskSkinHintStatus *=nullptr) const
Find the value for a specific aspect.
qreal spacingHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a spacing hint.
QMarginsF paddingHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a padding hint.
QMarginsF marginHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a margin hint.
QSizeF strutSizeHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a strut size hint.
QSizeF outerBoxSize(QskAspect, const QSizeF &innerBoxSize) const
Calculate the size, when being expanded by paddings, indentations.
QskBoxShapeMetrics boxShapeHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a shape hint.
QskColorFilter effectiveGraphicFilter(QskAspect::Subcontrol) const
QskBoxBorderMetrics boxBorderMetricsHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a border hint.
T flagHint(QskAspect, T=T()) const
Retrieves a flag hint.
qreal metric(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a metric hint.
Qt::Alignment alignmentHint(QskAspect, Qt::Alignment=Qt::Alignment()) const
Retrieves an alignment hint.