8
8
using System ;
9
9
using System . Collections . Generic ;
10
10
using System . Linq ;
11
+ using JetBrains . Annotations ;
11
12
using osu . Framework . Allocation ;
12
13
using osu . Framework . Bindables ;
13
14
using osu . Framework . Extensions . EnumExtensions ;
14
15
using osu . Framework . Extensions . IEnumerableExtensions ;
15
16
using osu . Framework . Localisation ;
17
+ using osuTK ;
16
18
17
19
namespace osu . Framework . Graphics . Containers
18
20
{
19
21
/// <summary>
20
22
/// A drawable text object that supports more advanced text formatting.
21
23
/// </summary>
22
- public partial class TextFlowContainer : FillFlowContainer
24
+ public partial class TextFlowContainer : CompositeDrawable
23
25
{
24
26
private float firstLineIndent ;
25
27
private readonly Action < SpriteText > defaultCreationParameters ;
@@ -115,6 +117,9 @@ public Anchor TextAnchor
115
117
116
118
textAnchor = value ;
117
119
120
+ Flow . Anchor = value ;
121
+ Flow . Origin = value ;
122
+
118
123
layout . Invalidate ( ) ;
119
124
}
120
125
}
@@ -127,23 +132,104 @@ public LocalisableString Text
127
132
{
128
133
set
129
134
{
130
- Clear ( ) ;
135
+ Flow . Clear ( ) ;
131
136
parts . Clear ( ) ;
132
137
133
138
AddText ( value ) ;
134
139
}
135
140
}
136
141
142
+ public new Axes RelativeSizeAxes
143
+ {
144
+ get => base . RelativeSizeAxes ;
145
+ set
146
+ {
147
+ base . RelativeSizeAxes = value ;
148
+ setFlowSizing ( ) ;
149
+ }
150
+ }
151
+
152
+ public new Axes AutoSizeAxes
153
+ {
154
+ get => base . AutoSizeAxes ;
155
+ set
156
+ {
157
+ base . AutoSizeAxes = value ;
158
+ setFlowSizing ( ) ;
159
+ }
160
+ }
161
+
162
+ public override float Width
163
+ {
164
+ get => base . Width ;
165
+ set
166
+ {
167
+ base . Width = value ;
168
+ setFlowSizing ( ) ;
169
+ }
170
+ }
171
+
172
+ private void setFlowSizing ( )
173
+ {
174
+ // if the user has imposed `RelativeSizeAxes` or a fixed size on the X axis on the entire flow,
175
+ // we want the child flow that actually does the layout here to match that.
176
+ // however, the child flow must always be auto-sized in the Y axis
177
+ // to correctly respect `TextAnchor`.
178
+ Flow . AutoSizeAxes = ( AutoSizeAxes & ~ RelativeSizeAxes ) | Axes . Y ;
179
+ Flow . RelativeSizeAxes = RelativeSizeAxes & ~ Flow . AutoSizeAxes ;
180
+ if ( ( Flow . AutoSizeAxes & Axes . X ) == 0 )
181
+ Flow . Width = Width ;
182
+ }
183
+
184
+ public new MarginPadding Padding
185
+ {
186
+ get => base . Padding ;
187
+ set => base . Padding = value ;
188
+ }
189
+
190
+ public Vector2 Spacing
191
+ {
192
+ get => Flow . Spacing ;
193
+ set => Flow . Spacing = value ;
194
+ }
195
+
196
+ public Vector2 MaximumSize
197
+ {
198
+ get => Flow . MaximumSize ;
199
+ set => Flow . MaximumSize = value ;
200
+ }
201
+
202
+ public new bool Masking
203
+ {
204
+ get => base . Masking ;
205
+ set => base . Masking = value ;
206
+ }
207
+
208
+ public FillDirection Direction
209
+ {
210
+ get => Flow . Direction ;
211
+ set => Flow . Direction = value ;
212
+ }
213
+
214
+ public IEnumerable < Drawable > Children => Flow . Children ;
215
+
137
216
[ Resolved ]
138
217
internal LocalisationManager Localisation { get ; private set ; }
139
218
219
+ protected readonly FillFlowContainer Flow ;
140
220
private readonly Bindable < LocalisationParameters > localisationParameters = new Bindable < LocalisationParameters > ( ) ;
141
221
142
222
public TextFlowContainer ( Action < SpriteText > defaultCreationParameters = null )
143
223
{
144
224
this . defaultCreationParameters = defaultCreationParameters ;
225
+
226
+ InternalChild = Flow = CreateFlow ( ) . With ( f => f . AutoSizeAxes = Axes . Both ) ;
227
+ Flow . OnLayoutInvalidated += ( ) => layout . Invalidate ( ) ;
145
228
}
146
229
230
+ [ Pure ]
231
+ protected virtual FillFlowContainer CreateFlow ( ) => new InnerFlow ( ) ;
232
+
147
233
protected override void LoadAsyncComplete ( )
148
234
{
149
235
base . LoadAsyncComplete ( ) ;
@@ -160,12 +246,6 @@ protected override void LoadComplete()
160
246
( ( IBindable < LocalisationParameters > ) localisationParameters ) . BindTo ( Localisation . CurrentParameters ) ;
161
247
}
162
248
163
- protected override void InvalidateLayout ( )
164
- {
165
- base . InvalidateLayout ( ) ;
166
- layout . Invalidate ( ) ;
167
- }
168
-
169
249
protected override void Update ( )
170
250
{
171
251
base . Update ( ) ;
@@ -174,24 +254,6 @@ protected override void Update()
174
254
RecreateAllParts ( ) ;
175
255
}
176
256
177
- public override IEnumerable < Drawable > FlowingChildren
178
- {
179
- get
180
- {
181
- if ( ( TextAnchor & ( Anchor . x2 | Anchor . y2 ) ) == 0 )
182
- return base . FlowingChildren ;
183
-
184
- var childArray = base . FlowingChildren . ToArray ( ) ;
185
-
186
- if ( ( TextAnchor & Anchor . x2 ) > 0 )
187
- reverseHorizontal ( childArray ) ;
188
- if ( ( TextAnchor & Anchor . y2 ) > 0 )
189
- reverseVertical ( childArray ) ;
190
-
191
- return childArray ;
192
- }
193
- }
194
-
195
257
protected override void UpdateAfterChildren ( )
196
258
{
197
259
if ( ! layout . IsValid )
@@ -279,14 +341,9 @@ protected internal virtual TextChunk<TSpriteText> CreateChunkFor<TSpriteText>(Lo
279
341
280
342
internal void ApplyDefaultCreationParameters ( SpriteText spriteText ) => defaultCreationParameters ? . Invoke ( spriteText ) ;
281
343
282
- public override void Add ( Drawable drawable )
344
+ public void Clear ( bool disposeChildren = true )
283
345
{
284
- throw new InvalidOperationException ( $ "Use { nameof ( AddText ) } to add text to a { nameof ( TextFlowContainer ) } .") ;
285
- }
286
-
287
- public override void Clear ( bool disposeChildren )
288
- {
289
- base . Clear ( disposeChildren ) ;
346
+ Flow . Clear ( disposeChildren ) ;
290
347
parts . Clear ( ) ;
291
348
}
292
349
@@ -322,10 +379,10 @@ protected virtual void RecreateAllParts()
322
379
// manual parts need to be manually removed before clearing contents,
323
380
// to avoid accidentally disposing of them in the process.
324
381
foreach ( var manualPart in parts . OfType < TextPartManual > ( ) )
325
- RemoveRange ( manualPart . Drawables , false ) ;
382
+ Flow . RemoveRange ( manualPart . Drawables , false ) ;
326
383
327
384
// make sure not to clear the list of parts by accident.
328
- base . Clear ( true ) ;
385
+ Flow . Clear ( true ) ;
329
386
330
387
foreach ( var part in parts )
331
388
recreatePart ( part ) ;
@@ -337,33 +394,7 @@ private void recreatePart(ITextPart part)
337
394
{
338
395
part . RecreateDrawablesFor ( this ) ;
339
396
foreach ( var drawable in part . Drawables )
340
- base . Add ( drawable ) ;
341
- }
342
-
343
- private void reverseHorizontal ( Drawable [ ] children )
344
- {
345
- int reverseStartIndex = 0 ;
346
-
347
- // Inverse the order of all children when displaying backwards, stopping at newline boundaries
348
- for ( int i = 0 ; i < children . Length ; i ++ )
349
- {
350
- if ( ! ( children [ i ] is NewLineContainer ) )
351
- continue ;
352
-
353
- Array . Reverse ( children , reverseStartIndex , i - reverseStartIndex ) ;
354
- reverseStartIndex = i + 1 ;
355
- }
356
-
357
- // Extra loop for the last newline boundary (or all children if there are no newlines)
358
- Array . Reverse ( children , reverseStartIndex , children . Length - reverseStartIndex ) ;
359
- }
360
-
361
- private void reverseVertical ( Drawable [ ] children )
362
- {
363
- // A vertical reverse reverses the order of the newline sections, but not the order within the newline sections
364
- // For code clarity this is done by reversing the entire array, and then reversing within the newline sections to restore horizontal order
365
- Array . Reverse ( children ) ;
366
- reverseHorizontal ( children ) ;
397
+ Flow . Add ( drawable ) ;
367
398
}
368
399
369
400
private readonly Cached layout = new Cached ( ) ;
@@ -373,11 +404,8 @@ private void computeLayout()
373
404
var childrenByLine = new List < List < Drawable > > ( ) ;
374
405
var curLine = new List < Drawable > ( ) ;
375
406
376
- foreach ( var c in Children )
407
+ foreach ( var c in Flow . Children )
377
408
{
378
- c . Anchor = TextAnchor ;
379
- c . Origin = TextAnchor ;
380
-
381
409
if ( c is NewLineContainer nlc )
382
410
{
383
411
curLine . Add ( nlc ) ;
@@ -411,6 +439,10 @@ private void computeLayout()
411
439
float currentLineHeight = 0f ;
412
440
float lineSpacingValue = lastLineHeight * LineSpacing ;
413
441
442
+ // Compute the offset of this line from the right
443
+ Drawable lastTextPartInLine = ( line [ ^ 1 ] is NewLineContainer && line . Count >= 2 ) ? line [ ^ 2 ] : line [ ^ 1 ] ;
444
+ float lineOffsetFromRight = Flow . ChildSize . X - ( lastTextPartInLine . X + lastTextPartInLine . DrawWidth ) ;
445
+
414
446
foreach ( Drawable c in line )
415
447
{
416
448
if ( c is NewLineContainer nlc )
@@ -431,6 +463,11 @@ private void computeLayout()
431
463
if ( c . Height > currentLineHeight )
432
464
currentLineHeight = c . Height ;
433
465
466
+ if ( ( TextAnchor & Anchor . x1 ) != 0 )
467
+ c . X += lineOffsetFromRight / 2 ;
468
+ else if ( ( TextAnchor & Anchor . x2 ) != 0 )
469
+ c . X += lineOffsetFromRight ;
470
+
434
471
isFirstChild = false ;
435
472
}
436
473
@@ -441,7 +478,10 @@ private void computeLayout()
441
478
}
442
479
}
443
480
444
- protected override bool ForceNewRow ( Drawable child ) => child is NewLineContainer ;
481
+ protected partial class InnerFlow : FillFlowContainer
482
+ {
483
+ protected override bool ForceNewRow ( Drawable child ) => child is NewLineContainer ;
484
+ }
445
485
446
486
public partial class NewLineContainer : Container
447
487
{
0 commit comments