QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskSubcontrolLayoutEngine.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskSubcontrolLayoutEngine.h"
7#include "QskLayoutElement.h"
8#include "QskLayoutChain.h"
9#include "QskTextRenderer.h"
10#include "QskSkinnable.h"
11#include "QskMargins.h"
12#include "QskTextOptions.h"
13
14#include <qdebug.h>
15#include <qfont.h>
16#include <qfontmetrics.h>
17#include <qmath.h>
18
19QskSubcontrolLayoutEngine::LayoutElement::LayoutElement(
20 const QskSkinnable* skinnable, const QskAspect::Subcontrol subControl )
21 : m_skinnable( skinnable )
22 , m_subControl( subControl )
23{
24}
25
26Qt::Alignment QskSubcontrolLayoutEngine::LayoutElement::alignment() const
27{
28 return m_skinnable->alignmentHint( m_subControl );
29}
30
31void QskSubcontrolLayoutEngine::LayoutElement::setMaximumSize( const QSizeF& size )
32{
33 setExplicitSizeHint( Qt::MaximumSize, size );
34}
35
36void QskSubcontrolLayoutEngine::LayoutElement::setMinimumSize( const QSizeF& size )
37{
38 setExplicitSizeHint( Qt::MinimumSize, size );
39}
40
41void QskSubcontrolLayoutEngine::LayoutElement::setPreferredSize( const QSizeF& size )
42{
43 setExplicitSizeHint( Qt::PreferredSize, size );
44}
45
46void QskSubcontrolLayoutEngine::LayoutElement::setFixedSize( const QSizeF& size )
47{
48 setSizePolicy( QskSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ) );
49
50 const auto newSize = size.expandedTo( QSizeF( 0, 0 ) );
51 setExplicitSizeHint( Qt::PreferredSize, newSize );
52}
53
54void QskSubcontrolLayoutEngine::LayoutElement::setExplicitSizeHint(
55 Qt::SizeHint which, const QSizeF& size )
56{
57 if ( which >= Qt::MinimumSize && which <= Qt::MaximumSize )
58 {
59 const QSizeF newSize( ( size.width() < 0 ) ? -1.0 : size.width(),
60 ( size.height() < 0 ) ? -1.0 : size.height() );
61
62 m_explicitSizeHints[ which ] = size;
63 }
64}
65
66QSizeF QskSubcontrolLayoutEngine::LayoutElement::sizeHint(
67 Qt::SizeHint which, const QSizeF& constraint ) const
68{
69 if ( which < Qt::MinimumSize || which > Qt::MaximumSize )
70 return QSizeF( 0, 0 );
71
72 if ( constraint.isValid() )
73 return constraint;
74
75 const bool isConstrained =
76 constraint.width() >= 0 || constraint.height() >= 0;
77
78 QSizeF hint;
79
80 if ( !isConstrained )
81 {
82 // explicit size hints are never constrained
83 hint = m_explicitSizeHints[ which ];
84 }
85
86 if ( which == Qt::PreferredSize )
87 {
88 if ( isConstrained )
89 {
90 const QskMargins padding = m_skinnable->paddingHint( m_subControl );
91
92 auto innerConstraint = constraint;
93
94 if ( constraint.width() > 0 )
95 {
96 const auto w = constraint.width() - padding.width();
97 if ( w <= 0 )
98 return QSizeF();
99
100 innerConstraint.setWidth( w );
101 }
102 else if ( constraint.height() > 0 )
103 {
104 const auto h = constraint.height() - padding.height();
105 if ( h <= 0 )
106 return QSizeF();
107
108 innerConstraint.setHeight( h );
109 }
110
111 hint = implicitSize( innerConstraint );
112
113 if ( hint.width() >= 0 )
114 hint.setWidth( hint.width() + padding.width() );
115
116 if ( hint.height() >= 0 )
117 hint.setHeight( hint.height() + padding.height() );
118 }
119 else
120 {
121 if ( !hint.isValid() )
122 {
123 const auto sz = implicitSize( constraint );
124
125 if ( hint.width() < 0 )
126 hint.setWidth( sz.width() );
127
128 if ( hint.height() < 0 )
129 hint.setHeight( sz.height() );
130 }
131 }
132 }
133 else
134 {
135 if ( isConstrained )
136 hint = QSizeF(); // not implemented
137 }
138
139 return hint;
140}
141
142QSizeF QskSubcontrolLayoutEngine::TextElement::implicitSize( const QSizeF& constraint ) const
143{
144 const auto font = skinnable()->effectiveFont( subControl() );
145 const auto textOptions = skinnable()->textOptionsHint( subControl() );
146
147#if 0
148 // what about skinnable()->strutSizeHint( subControl() ); ????
149#endif
150
151 QSizeF hint;
152
153 const qreal lineHeight = QFontMetricsF( font ).height();
154
155 if ( m_text.isEmpty() )
156 {
157 if ( constraint.height() < 0.0 )
158 hint.setHeight( qCeil( lineHeight ) );
159 }
160 else if ( constraint.width() >= 0.0 )
161 {
162 if ( textOptions.effectiveElideMode() != Qt::ElideNone )
163 {
164 hint.setHeight( qCeil( lineHeight ) );
165 }
166 else
167 {
168 /*
169 In case of QskTextOptions::NoWrap we could count
170 the line numbers and calculate the height from
171 lineHeight. TODO ...
172 */
173
174 qreal maxHeight = std::numeric_limits< qreal >::max();
175 if ( maxHeight / lineHeight > textOptions.maximumLineCount() )
176 {
177 // be careful with overflows
178 maxHeight = textOptions.maximumLineCount() * lineHeight;
179 }
180
181 QSizeF size( constraint.width(), maxHeight );
182 size = QskTextRenderer::textSize( m_text, font, textOptions, size );
183
184 hint.setHeight( qCeil( size.height() ) );
185 }
186 }
187 else if ( constraint.height() >= 0.0 )
188 {
189 const qreal maxWidth = std::numeric_limits< qreal >::max();
190
191 QSizeF size( maxWidth, constraint.height() );
192 size = QskTextRenderer::textSize( m_text, font, textOptions, size );
193
194 hint.setWidth( qCeil( size.width() ) );
195 }
196 else
197 {
198 hint = QskTextRenderer::textSize( m_text, font, textOptions );
199 }
200
201#if 1
202 if ( hint.width() > 0.0 )
203 {
204 /*
205 workaround for a rounding problem that might lead to
206 seeing dots, when being in elide mode. TODO ...
207 */
208 hint.rwidth() += 1;
209 }
210#endif
211
212 return hint;
213}
214
215QSizeF QskSubcontrolLayoutEngine::GraphicElement::effectiveStrutSize() const
216{
217 auto size = skinnable()->strutSizeHint( subControl() );
218
219 if ( size.isEmpty() )
220 {
221 const qreal aspectRatio = m_sourceSize.width() / m_sourceSize.height();
222
223 if ( size.width() > 0 )
224 {
225 size.setHeight( size.width() / aspectRatio );
226 }
227 else if ( size.height() > 0 )
228 {
229 size.setWidth( size.height() * aspectRatio );
230 }
231 }
232
233 return size;
234}
235
236QSizeF QskSubcontrolLayoutEngine::GraphicElement::implicitSize(
237 const QSizeF& constraint ) const
238{
239 auto hint = m_sourceSize;
240
241 if ( !hint.isEmpty() )
242 {
243 const qreal aspectRatio = hint.width() / hint.height();
244
245 if ( constraint.width() >= 0.0 )
246 {
247 hint.setHeight( constraint.width() / aspectRatio );
248 hint.setWidth( -1.0 );
249 }
250 else if ( constraint.height() > 0.0 )
251 {
252 hint.setWidth( constraint.height() * aspectRatio );
253 hint.setHeight( -1.0 );
254 }
255 }
256
257 return hint;
258}
259
260static QskLayoutChain::CellData qskCell(
262 Qt::Orientation orientation, bool isLayoutOrientation, qreal constraint )
263{
265 cell.isValid = true;
266
267 const auto policy = element->sizePolicy().policy( orientation );
268
269 if ( isLayoutOrientation )
270 {
271 const auto stretch = element->stretch();
272
273 if ( stretch < 0 )
274 cell.stretch = ( policy & QskSizePolicy::ExpandFlag ) ? 1 : 0;
275 else
276 cell.stretch = stretch;
277 }
278
279 cell.canGrow = policy & QskSizePolicy::GrowFlag;
280 cell.metrics = element->metrics( orientation, constraint );
281
282 return cell;
283}
284
285class QskSubcontrolLayoutEngine::PrivateData
286{
287 public:
288 PrivateData( Qt::Orientation orientation )
289 : orientation( orientation )
290 {
291 elements.reserve( 2 ); // often Graphic + Text
292 }
293
294 Qt::Orientation orientation;
295 QVector< LayoutElement* > elements;
296};
297
298QskSubcontrolLayoutEngine::QskSubcontrolLayoutEngine( Qt::Orientation orientation )
299 : m_data( new PrivateData( orientation ) )
300{
301 setExtraSpacingAt( Qt::TopEdge | Qt::BottomEdge | Qt::LeftEdge | Qt::RightEdge );
302}
303
304QskSubcontrolLayoutEngine::~QskSubcontrolLayoutEngine()
305{
306 qDeleteAll( m_data->elements );
307}
308
309bool QskSubcontrolLayoutEngine::setOrientation( Qt::Orientation orientation )
310{
311 if ( m_data->orientation != orientation )
312 {
313 m_data->orientation = orientation;
314 invalidate( LayoutCache );
315
316 return true;
317 }
318
319 return false;
320}
321
322Qt::Orientation QskSubcontrolLayoutEngine::orientation() const
323{
324 return m_data->orientation;
325}
326
327void QskSubcontrolLayoutEngine::setSpacing( qreal spacing )
328{
329 Inherited::setSpacing( spacing, Qt::Horizontal | Qt::Vertical );
330}
331
332qreal QskSubcontrolLayoutEngine::spacing() const
333{
334 return Inherited::spacing( m_data->orientation );
335}
336
337void QskSubcontrolLayoutEngine::setGraphicTextElements( const QskSkinnable* skinnable,
338 QskAspect::Subcontrol textSubcontrol, const QString& text,
339 QskAspect::Subcontrol graphicSubControl, const QSizeF& graphicSize )
340{
341 /*
342 QskSubcontrolLayoutEngine was initially created to support the
343 situation of an icon and a text, that can be found at several places
344 in conrols. This method supports to set up such a layout without
345 having to deal with the details of the layout classes.
346 */
347
348 auto graphicElement = appendGraphicElement( skinnable, graphicSubControl, graphicSize );
349 auto textElement = appendTextElement( skinnable, textSubcontrol, text );
350
351 /*
352 Now the difficult part: setting up size policies and the preferred size.
353 The code below is probably not the final word - let's see what type of
354 default settings we need most often. TODO ...
355 */
356
357 using SP = QskSizePolicy;
358
359 if ( textElement && graphicElement == nullptr )
360 {
361 textElement->setSizePolicy( SP::Preferred, SP::Constrained );
362 }
363 else if ( graphicElement && textElement == nullptr )
364 {
365 const auto size = graphicElement->effectiveStrutSize();
366
367 if ( !size.isEmpty() )
368 graphicElement->setFixedSize( size );
369 else
370 graphicElement->setSizePolicy( SP::Ignored, SP::ConstrainedExpanding );
371 }
372 else if ( textElement && graphicElement )
373 {
374 if ( orientation() == Qt::Horizontal )
375 {
376 graphicElement->setSizePolicy( SP::Constrained, SP::Fixed );
377 textElement->setSizePolicy( SP::Preferred, SP::Preferred );
378 }
379 else
380 {
381 graphicElement->setSizePolicy( SP::Preferred, SP::Fixed );
382 textElement->setSizePolicy( SP::Preferred, SP::Constrained );
383 }
384
385 auto size = graphicElement->effectiveStrutSize();
386
387 if ( size.isEmpty() )
388 {
389 const auto h = 1.5 * skinnable->effectiveFontHeight( textSubcontrol );
390
391 size.setWidth( graphicElement->widthForHeight( h ) );
392 size.setHeight( h );
393 }
394
395 graphicElement->setPreferredSize( size );
396 }
397}
398
399QskSubcontrolLayoutEngine::GraphicElement* QskSubcontrolLayoutEngine::appendGraphicElement( const QskSkinnable* skinnable,
400 QskAspect::Subcontrol graphicSubcontrol, const QSizeF& graphicSize )
401{
402 GraphicElement* graphicElement = nullptr;
403
404 if ( !graphicSize.isEmpty() && ( graphicSubcontrol != QskAspect::NoSubcontrol ) )
405 {
406 graphicElement = dynamic_cast< GraphicElement* >( element( graphicSubcontrol ) );
407 if ( graphicElement == nullptr )
408 {
409 graphicElement = new GraphicElement( skinnable, graphicSubcontrol );
410 m_data->elements.prepend( graphicElement );
411 }
412
413 graphicElement->setSourceSize( graphicSize );
414 }
415
416 return graphicElement;
417}
418
419QskSubcontrolLayoutEngine::TextElement* QskSubcontrolLayoutEngine::appendTextElement( const QskSkinnable* skinnable,
420 QskAspect::Subcontrol textSubcontrol, const QString& text )
421{
422 TextElement* textElement = nullptr;
423
424 if ( !text.isEmpty() && ( textSubcontrol != QskAspect::NoSubcontrol ) )
425 {
426 textElement = dynamic_cast< TextElement* >( element( textSubcontrol ) );
427 if ( textElement == nullptr )
428 {
429 textElement = new TextElement( skinnable, textSubcontrol );
430 m_data->elements.append( textElement );
431 }
432
433 textElement->setText( text );
434 }
435
436 return textElement;
437}
438
439void QskSubcontrolLayoutEngine::setFixedContent(
440 QskAspect::Subcontrol subcontrol, Qt::Orientation orientation, Qt::Alignment alignment )
441{
442 if( auto e = element( subcontrol ) )
443 e->setSizePolicy( QskSizePolicy::Maximum, e->sizePolicy().verticalPolicy() );
444
445 Qt::Edges extraSpacing;
446
447 switch( orientation )
448 {
449 case Qt::Horizontal:
450 {
451 extraSpacing |= ( extraSpacingAt() & ( Qt::TopEdge | Qt::BottomEdge ) );
452
453 if( alignment & Qt::AlignLeft )
454 {
455 extraSpacing |= Qt::RightEdge;
456 }
457 else if( alignment & Qt::AlignRight )
458 {
459 extraSpacing |= Qt::LeftEdge;
460 }
461 else if( alignment & Qt::AlignHCenter )
462 {
463 extraSpacing |= Qt::LeftEdge | Qt::RightEdge;
464 }
465 break;
466 }
467 case Qt::Vertical:
468 {
469 extraSpacing |= ( extraSpacingAt() & ( Qt::LeftEdge | Qt::RightEdge ) );
470
471 if( alignment & Qt::AlignTop )
472 {
473 extraSpacing |= Qt::BottomEdge;
474 }
475 else if( alignment & Qt::AlignBottom )
476 {
477 extraSpacing |= Qt::TopEdge;
478 }
479 else if( alignment & Qt::AlignVCenter )
480 {
481 extraSpacing |= Qt::TopEdge | Qt::BottomEdge;
482 }
483 break;
484 }
485 }
486
487 setExtraSpacingAt( extraSpacing );
488}
489
490void QskSubcontrolLayoutEngine::addElement( LayoutElement* element )
491{
492 m_data->elements += element;
493}
494
495QskSubcontrolLayoutEngine::LayoutElement* QskSubcontrolLayoutEngine::elementAt( int index ) const
496{
497 if ( index >= 0 && index < count() )
498 return m_data->elements[ index ];
499
500 return nullptr;
501}
502
503QskSubcontrolLayoutEngine::LayoutElement* QskSubcontrolLayoutEngine::element(
504 QskAspect::Subcontrol subControl ) const
505{
506 for ( auto element : m_data->elements )
507 {
508 if ( element->subControl() == subControl )
509 return element;
510 }
511
512 return nullptr;
513}
514
515QskSizePolicy QskSubcontrolLayoutEngine::sizePolicyAt( int index ) const
516{
517 if ( index >= 0 && index < count() )
518 {
519 if ( auto element = m_data->elements[ index ] )
520 return element->sizePolicy();
521 }
522
523 return QskSizePolicy();
524}
525
526int QskSubcontrolLayoutEngine::count() const
527{
528 return m_data->elements.count();
529}
530
531void QskSubcontrolLayoutEngine::layoutItems()
532{
533 int row = 0;
534 int col = 0;
535
536 int& index = m_data->orientation == Qt::Horizontal ? col : row;
537
538 for ( auto element : m_data->elements )
539 {
540 const auto rect = geometryAt( element, QRect( col, row, 1, 1 ) );
541 element->setGeometry( rect );
542
543 index++;
544 }
545}
546
547int QskSubcontrolLayoutEngine::effectiveCount( Qt::Orientation orientation ) const
548{
549 const auto count = m_data->elements.count();
550
551 if ( orientation == m_data->orientation )
552 return count;
553 else
554 return ( count >= 1 ) ? 1 : 0;
555}
556
557QRectF QskSubcontrolLayoutEngine::subControlRect( QskAspect::Subcontrol subControl ) const
558{
559 if ( const auto el = element( subControl ) )
560 return el->geometry();
561
562 return QRectF( 0.0, 0.0, -1.0, -1.0 ); // something invalid
563}
564
565void QskSubcontrolLayoutEngine::invalidateElementCache()
566{
567}
568
569void QskSubcontrolLayoutEngine::setupChain( Qt::Orientation orientation,
570 const QskLayoutChain::Segments& constraints, QskLayoutChain& chain ) const
571{
572 uint index1 = 0;
573 uint index2 = 0;
574
575 const bool isLayoutOrientation = ( orientation == m_data->orientation );
576
577 for ( auto element : m_data->elements )
578 {
579 qreal constraint = -1.0;
580 if ( !constraints.isEmpty() )
581 constraint = constraints[ index1 ].length;
582
583 const auto cell = qskCell( element, orientation, isLayoutOrientation, constraint );
584 chain.expandCell( index2, cell );
585
586 if ( isLayoutOrientation )
587 index2++;
588 else
589 index1++;
590 }
591}
Subcontrol
For use within the rendering or lay-outing of a specific QskSkinnable.
Definition QskAspect.h:104
Qt::Alignment alignmentHint(QskAspect, Qt::Alignment=Qt::Alignment()) const
Retrieves an alignment hint.