QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskRichTextRenderer.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskRichTextRenderer.h"
7#include "QskTextColors.h"
8#include "QskTextOptions.h"
9
10#include <qglobalstatic.h>
11#include <qmutex.h>
12#include <qthread.h>
13
14class QQuickWindow;
15
16QSK_QT_PRIVATE_BEGIN
17#include <private/qquicktext_p.h>
18#include <private/qquicktext_p_p.h>
19QSK_QT_PRIVATE_END
20
21// Since Qt 5.7 QQuickTextNode is public and could be used TODO ...
22
23namespace
24{
25 class TextItem final : public QQuickText
26 {
27 public:
28 TextItem()
29 {
30#if 1
31 /*
32 QQuickTextPrivate::ExtraData::ExtraData is not exported with MSVC, so we
33 preallocate it by setting/unsetting the bottom padding
34 */
35 setBottomPadding( 1 );
36 setBottomPadding( 0 );
37#endif
38
39 // fonts are supposed to be defined in the application skin and we
40 // probably don't want to have them scaled
41 setFontSizeMode( QQuickText::FixedSize );
42
43#if 0
44 setAntialiasing( true );
45 setRenderType( QQuickText::QtRendering );
46 setPadding( 0 );
47
48 setMinimumPixelSize();
49 setMinimumPointSize();
50
51 // also something, that should be defined in an application skin
52 setLineHeightMode( ... );
53 setLineHeight();
54#endif
55 }
56
57 inline void setGeometry( const QRectF& rect )
58 {
59 auto d = QQuickTextPrivate::get( this );
60
61#if QT_VERSION >= QT_VERSION_CHECK( 6, 2, 0 )
62 d->heightValidFlag = true;
63 d->widthValidFlag = true;
64#else
65 d->heightValid = true;
66 d->widthValid = true;
67#endif
68
69 if ( ( d->x != rect.x() ) || ( d->y != rect.y() ) )
70 {
71 d->x = rect.x();
72 d->y = rect.y();
73 d->dirty( QQuickItemPrivate::Position );
74 }
75
76 if ( ( d->width != rect.width() ) || ( d->height != rect.height() ) )
77 {
78 d->height = rect.height();
79 d->width = rect.width();
80 d->dirty( QQuickItemPrivate::Size );
81 }
82 }
83
84 inline void setAlignment( Qt::Alignment alignment )
85 {
86 setHAlign( static_cast< QQuickText::HAlignment >( int( alignment ) & 0x0f ) );
87 setVAlign( static_cast< QQuickText::VAlignment >( int( alignment ) & 0xf0 ) );
88 }
89
90 inline void setOptions( const QskTextOptions& options )
91 {
92 // what about Qt::TextShowMnemonic ???
93 setTextFormat( static_cast< QQuickText::TextFormat >( options.format() ) );
94 setElideMode( static_cast< QQuickText::TextElideMode >( options.elideMode() ) );
95 setMaximumLineCount( options.maximumLineCount() );
96 setWrapMode( static_cast< QQuickText::WrapMode >( options.wrapMode() ) );
97 }
98
99 inline void begin()
100 {
101 classBegin();
102 QQuickTextPrivate::get( this )->updateOnComponentComplete = true;
103 }
104
105 inline void end()
106 {
107 componentComplete();
108 }
109
110 inline void reset()
111 {
112 setText( QString() );
113 }
114
115 inline QRectF layedOutTextRect() const
116 {
117 auto that = const_cast< TextItem* >( this );
118 return QQuickTextPrivate::get( that )->layedOutTextRect;
119 }
120
121 void updateTextNode( QQuickWindow* window, QSGNode* parentNode )
122 {
123 QQuickItemPrivate::get( this )->refWindow( window );
124
125 while ( parentNode->firstChild() )
126 delete parentNode->firstChild();
127
128 auto node = QQuickText::updatePaintNode( nullptr, nullptr );
129 node->reparentChildNodesTo( parentNode );
130 delete node;
131
132 QQuickItemPrivate::get( this )->derefWindow();
133 }
134
135 protected:
136 QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override
137 {
138 Q_ASSERT( false );
139 return nullptr;
140 }
141 };
142
143 class TextItemMap
144 {
145 public:
146 ~TextItemMap()
147 {
148 qDeleteAll( m_hash );
149 }
150
151 inline TextItem* item()
152 {
153 const auto thread = QThread::currentThread();
154
155 QMutexLocker locker( &m_mutex );
156
157 auto it = m_hash.constFind( thread );
158 if ( it == m_hash.constEnd() )
159 {
160 auto textItem = new TextItem();
161 QObject::connect( thread, &QThread::finished,
162 textItem, [ this, thread ] { removeItem( thread ); } );
163
164 m_hash.insert( thread, textItem );
165 return textItem;
166 }
167
168 return it.value();
169 }
170
171 private:
172 void removeItem( const QThread* thread )
173 {
174 auto textItem = m_hash.take( thread );
175 if ( textItem )
176 textItem->deleteLater();
177 }
178
179 QMutex m_mutex;
180 QHash< const QThread*, TextItem* > m_hash;
181 };
182}
183
184/*
185 size requests and rendering might be from different threads and we
186 better use different items as we might end up in events internally
187 being sent, that leads to crashes because of it
188 */
189Q_GLOBAL_STATIC( TextItemMap, qskTextItemMap )
190
191QSizeF QskRichTextRenderer::textSize(
192 const QString& text, const QFont& font, const QskTextOptions& options )
193{
194 auto& textItem = *qskTextItemMap->item();
195
196 textItem.begin();
197
198 textItem.setFont( font );
199 textItem.setOptions( options );
200
201 textItem.setWidth( -1 );
202 textItem.setText( text );
203
204 textItem.end();
205
206 const QSizeF sz( textItem.implicitWidth(), textItem.implicitHeight() );
207
208 textItem.reset();
209
210 return sz;
211}
212
213QRectF QskRichTextRenderer::textRect(
214 const QString& text, const QFont& font,
215 const QskTextOptions& options, const QSizeF& size )
216{
217 auto& textItem = *qskTextItemMap->item();
218
219 textItem.begin();
220
221 textItem.setFont( font );
222 textItem.setOptions( options );
223 textItem.setAlignment( Qt::Alignment() );
224
225 textItem.setWidth( size.width() );
226 textItem.setHeight( size.height() );
227
228 textItem.setText( text );
229
230 textItem.end();
231
232 const auto rect = textItem.layedOutTextRect();
233
234 textItem.reset();
235
236 return rect;
237}
238
239void QskRichTextRenderer::updateNode(
240 const QString& text, const QFont& font,
241 const QskTextOptions& options, Qsk::TextStyle style,
242 const QskTextColors& colors, Qt::Alignment alignment,
243 const QRectF& rect, const QQuickItem* item, QSGTransformNode* node )
244{
245 // are we killing internal caches of QQuickText, when always using
246 // the same item for the creation the text nodes. TODO ...
247
248 auto& textItem = *qskTextItemMap->item();
249
250 textItem.begin();
251
252 textItem.setGeometry( rect );
253
254 textItem.setBottomPadding( 0 );
255 textItem.setTopPadding( 0 );
256 textItem.setFont( font );
257 textItem.setOptions( options );
258 textItem.setAlignment( alignment );
259
260 textItem.setColor( colors.textColor );
261 textItem.setStyle( static_cast< QQuickText::TextStyle >( style ) );
262 textItem.setStyleColor( colors.styleColor );
263 textItem.setLinkColor( colors.linkColor );
264
265 textItem.setText( text );
266
267 textItem.end();
268
269 if ( alignment & Qt::AlignVCenter )
270 {
271 /*
272 We need to have a stable algo for rounding the text base line,
273 so that texts don't start wobbling, when processing transitions
274 between margins/paddings. We manipulate the layout code
275 by adding some padding, so that the position of base line
276 gets always floored.
277 */
278 auto d = QQuickTextPrivate::get( &textItem );
279
280 const qreal h = d->layedOutTextRect.height() + d->lineHeightOffset();
281
282 if ( static_cast< int >( rect.height() - h ) % 2 )
283 {
284 if ( static_cast< int >( h ) % 2 )
285 d->extra->bottomPadding = 1;
286 else
287 d->extra->topPadding = 1;
288 }
289 }
290
291 textItem.updateTextNode( item->window(), node );
292 textItem.reset();
293}