1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.foundation.demos.text
18 
19 import android.annotation.SuppressLint
20 import androidx.compose.animation.animateColor
21 import androidx.compose.animation.core.RepeatMode
22 import androidx.compose.animation.core.infiniteRepeatable
23 import androidx.compose.animation.core.rememberInfiniteTransition
24 import androidx.compose.animation.core.tween
25 import androidx.compose.foundation.Canvas
26 import androidx.compose.foundation.background
27 import androidx.compose.foundation.border
28 import androidx.compose.foundation.layout.Arrangement
29 import androidx.compose.foundation.layout.Box
30 import androidx.compose.foundation.layout.Column
31 import androidx.compose.foundation.layout.ColumnScope
32 import androidx.compose.foundation.layout.Row
33 import androidx.compose.foundation.layout.Spacer
34 import androidx.compose.foundation.layout.fillMaxHeight
35 import androidx.compose.foundation.layout.fillMaxSize
36 import androidx.compose.foundation.layout.fillMaxWidth
37 import androidx.compose.foundation.layout.height
38 import androidx.compose.foundation.layout.heightIn
39 import androidx.compose.foundation.layout.size
40 import androidx.compose.foundation.layout.width
41 import androidx.compose.foundation.layout.widthIn
42 import androidx.compose.foundation.lazy.LazyColumn
43 import androidx.compose.foundation.rememberScrollState
44 import androidx.compose.foundation.selection.selectable
45 import androidx.compose.foundation.selection.selectableGroup
46 import androidx.compose.foundation.text.BasicText
47 import androidx.compose.foundation.text.InlineTextContent
48 import androidx.compose.foundation.text.TextAutoSize
49 import androidx.compose.foundation.text.appendInlineContent
50 import androidx.compose.foundation.verticalScroll
51 import androidx.compose.material.ExperimentalMaterialApi
52 import androidx.compose.material.Icon
53 import androidx.compose.material.IconButton
54 import androidx.compose.material.ListItem
55 import androidx.compose.material.RadioButton
56 import androidx.compose.material.Slider
57 import androidx.compose.material.Switch
58 import androidx.compose.material.Text
59 import androidx.compose.material.icons.Icons
60 import androidx.compose.material.icons.filled.KeyboardArrowDown
61 import androidx.compose.material.icons.filled.KeyboardArrowUp
62 import androidx.compose.runtime.Composable
63 import androidx.compose.runtime.CompositionLocalProvider
64 import androidx.compose.runtime.getValue
65 import androidx.compose.runtime.mutableFloatStateOf
66 import androidx.compose.runtime.mutableStateOf
67 import androidx.compose.runtime.remember
68 import androidx.compose.runtime.setValue
69 import androidx.compose.ui.Alignment
70 import androidx.compose.ui.Modifier
71 import androidx.compose.ui.geometry.Offset
72 import androidx.compose.ui.graphics.Color
73 import androidx.compose.ui.graphics.Shadow
74 import androidx.compose.ui.graphics.graphicsLayer
75 import androidx.compose.ui.platform.LocalLayoutDirection
76 import androidx.compose.ui.semantics.Role
77 import androidx.compose.ui.text.AnnotatedString
78 import androidx.compose.ui.text.ExperimentalTextApi
79 import androidx.compose.ui.text.ParagraphStyle
80 import androidx.compose.ui.text.Placeholder
81 import androidx.compose.ui.text.PlaceholderVerticalAlign
82 import androidx.compose.ui.text.SpanStyle
83 import androidx.compose.ui.text.TextStyle
84 import androidx.compose.ui.text.buildAnnotatedString
85 import androidx.compose.ui.text.drawText
86 import androidx.compose.ui.text.font.FontFamily
87 import androidx.compose.ui.text.font.FontStyle
88 import androidx.compose.ui.text.font.FontWeight
89 import androidx.compose.ui.text.intl.LocaleList
90 import androidx.compose.ui.text.rememberTextMeasurer
91 import androidx.compose.ui.text.samples.BaselineShiftSample
92 import androidx.compose.ui.text.samples.FontFamilyCursiveSample
93 import androidx.compose.ui.text.samples.FontFamilyMonospaceSample
94 import androidx.compose.ui.text.samples.FontFamilySansSerifSample
95 import androidx.compose.ui.text.samples.FontFamilySerifSample
96 import androidx.compose.ui.text.samples.ParagraphStyleAnnotatedStringsSample
97 import androidx.compose.ui.text.samples.ParagraphStyleSample
98 import androidx.compose.ui.text.samples.TextDecorationCombinedSample
99 import androidx.compose.ui.text.samples.TextDecorationLineThroughSample
100 import androidx.compose.ui.text.samples.TextDecorationUnderlineSample
101 import androidx.compose.ui.text.samples.TextOverflowClipSample
102 import androidx.compose.ui.text.samples.TextOverflowEllipsisSample
103 import androidx.compose.ui.text.samples.TextOverflowVisibleFixedSizeSample
104 import androidx.compose.ui.text.samples.TextOverflowVisibleMinHeightSample
105 import androidx.compose.ui.text.samples.TextStyleSample
106 import androidx.compose.ui.text.style.Hyphens
107 import androidx.compose.ui.text.style.TextAlign
108 import androidx.compose.ui.text.style.TextOverflow
109 import androidx.compose.ui.text.withStyle
110 import androidx.compose.ui.tooling.preview.Preview
111 import androidx.compose.ui.unit.Dp
112 import androidx.compose.ui.unit.LayoutDirection
113 import androidx.compose.ui.unit.TextUnit
114 import androidx.compose.ui.unit.dp
115 import androidx.compose.ui.unit.em
116 import androidx.compose.ui.unit.sp
117 import androidx.compose.ui.window.Popup
118 import androidx.compose.ui.window.PopupProperties
119 
120 private const val longText =
121     "This is a very-very long string that wraps into a few lines " + "given the width restrictions."
122 const val displayText = "Text Demo"
123 const val displayTextChinese = "文本演示"
124 const val displayTextArabic = "\u0639\u0631\u0636\u0020\u0627\u0644\u0646\u0635"
125 const val displayTextHindi = "पाठ डेमो"
126 const val displayTextBidi = "Text \u0639\u0631\u0636"
127 
128 val fontSize4 = 16.sp
129 val fontSize6 = 20.sp
130 val fontSize8 = 25.sp
131 val fontSize10 = 30.sp
132 
133 @SuppressLint("PrimitiveInCollection")
134 private val overflowOptions =
135     listOf(
136         TextOverflow.Clip,
137         TextOverflow.Visible,
138         TextOverflow.StartEllipsis,
139         TextOverflow.MiddleEllipsis,
140         TextOverflow.Ellipsis
141     )
142 private val boolOptions = listOf(true, false)
143 @SuppressLint("PrimitiveInCollection")
144 private val textAlignments =
145     listOf(
146         TextAlign.Left,
147         TextAlign.Start,
148         TextAlign.Center,
149         TextAlign.Right,
150         TextAlign.End,
151         TextAlign.Justify
152     )
153 
154 @Preview
155 @Composable
TextDemonull156 fun TextDemo() {
157     LazyColumn {
158         item {
159             TagLine(tag = "color, fontSize, fontWeight and fontStyle")
160             TextDemoBasic()
161         }
162         item {
163             TagLine(
164                 tag =
165                     "color, fontSize, fontWeight, fontFamily, fontStyle, letterSpacing, " +
166                         "background, decoration"
167             )
168             TextDemoComplexStyling()
169         }
170         item {
171             TagLine(tag = "Chinese, Arabic, and Hindi")
172             TextDemoLanguage()
173         }
174         item {
175             TagLine(tag = "FontFamily generic names")
176             TextDemoFontFamily()
177         }
178         item {
179             TagLine(tag = "FontFamily default values")
180             TextDemoFontFamilyDefaultValues()
181         }
182         item {
183             TagLine(tag = "decoration, decorationColor and decorationStyle")
184             TextDemoTextDecoration()
185         }
186         item {
187             TagLine(tag = "letterSpacing")
188             TextDemoLetterSpacing()
189         }
190         item {
191             TagLine(tag = "baselineShift")
192             TextDemoBaselineShift()
193         }
194         item {
195             TagLine(tag = "lineHeight")
196             TextDemoHeight()
197         }
198         item {
199             TagLine(tag = "background")
200             TextDemoBackground()
201         }
202         item {
203             TagLine(tag = "Locale: Japanese, Simplified and Traditional Chinese")
204             TextDemoLocale()
205         }
206         item {
207             TagLine(tag = "textAlign and textDirection")
208             TextDemoTextAlign()
209         }
210         item {
211             TagLine(tag = "softWrap: on and off")
212             TextDemoSoftWrap()
213         }
214         item {
215             TagLine(tag = "shadow")
216             TextDemoShadowEffect()
217         }
218         item {
219             TagLine(tag = "fontSizeScale")
220             TextDemoFontSizeScale()
221         }
222         item {
223             TagLine(tag = "complex paragraph styling")
224             TextDemoParagraphStyling()
225         }
226 
227         item {
228             TagLine(tag = "textOverflow: Clip, Ellipsis, Visible")
229             TextDemoTextOverflow()
230         }
231         item {
232             TagLine(tag = "inline content")
233             TextDemoInlineContent()
234         }
235     }
236 }
237 
238 @Composable
TagLinenull239 fun TagLine(tag: String) {
240     Text(
241         style = TextStyle(fontSize = fontSize8),
242         text =
243             buildAnnotatedString {
244                 append("\n")
245                 withStyle(style = SpanStyle(color = Color(0xFFAAAAAA), fontSize = fontSize6)) {
246                     append(tag)
247                 }
248             }
249     )
250 }
251 
252 @Composable
SecondTagLinenull253 fun SecondTagLine(tag: String) {
254     Text(
255         text =
256             buildAnnotatedString {
257                 withStyle(style = SpanStyle(color = Color(0xFFAAAAAA), fontSize = fontSize4)) {
258                     append(tag)
259                 }
260             }
261     )
262 }
263 
264 @Composable
TextDemoBasicnull265 fun TextDemoBasic() {
266     // This group of text composables show different color, fontSize, fontWeight and fontStyle in
267     // English.
268     Text(
269         text =
270             buildAnnotatedString {
271                 withStyle(
272                     SpanStyle(
273                         color = Color(0xFFFF0000),
274                         fontSize = fontSize6,
275                         fontWeight = FontWeight.W200,
276                         fontStyle = FontStyle.Italic
277                     )
278                 ) {
279                     append("$displayText   ")
280                 }
281 
282                 withStyle(
283                     SpanStyle(
284                         color = Color(0xFF00FF00),
285                         fontSize = fontSize8,
286                         fontWeight = FontWeight.W500,
287                         fontStyle = FontStyle.Normal
288                     )
289                 ) {
290                     append("$displayText   ")
291                 }
292 
293                 withStyle(
294                     SpanStyle(
295                         color = Color(0xFF0000FF),
296                         fontSize = fontSize10,
297                         fontWeight = FontWeight.W800,
298                         fontStyle = FontStyle.Normal
299                     )
300                 ) {
301                     append(displayText)
302                 }
303             }
304     )
305 }
306 
307 @Composable
TextDemoComplexStylingnull308 fun TextDemoComplexStyling() {
309     TextStyleSample()
310 }
311 
312 @Composable
TextDemoLanguagenull313 fun TextDemoLanguage() {
314     // This group of text composables show different color, fontSize, fontWeight and fontStyle in
315     // Chinese, Arabic, and Hindi.
316     Text(
317         text =
318             buildAnnotatedString {
319                 withStyle(
320                     style =
321                         SpanStyle(
322                             color = Color(0xFFFF0000),
323                             fontSize = fontSize6,
324                             fontWeight = FontWeight.W200,
325                             fontStyle = FontStyle.Italic
326                         )
327                 ) {
328                     append("$displayTextChinese   ")
329                 }
330 
331                 withStyle(
332                     style =
333                         SpanStyle(
334                             color = Color(0xFF00FF00),
335                             fontSize = fontSize8,
336                             fontWeight = FontWeight.W500,
337                             fontStyle = FontStyle.Normal
338                         )
339                 ) {
340                     append("$displayTextArabic   ")
341                 }
342 
343                 withStyle(
344                     style =
345                         SpanStyle(
346                             color = Color(0xFF0000FF),
347                             fontSize = fontSize10,
348                             fontWeight = FontWeight.W800,
349                             fontStyle = FontStyle.Normal
350                         )
351                 ) {
352                     append(displayTextHindi)
353                 }
354             }
355     )
356 }
357 
358 @Composable
TextDemoFontFamilynull359 fun TextDemoFontFamily() {
360     // This group of text composables show different fontFamilies in English.
361     Text(
362         buildAnnotatedString {
363             withStyle(style = SpanStyle(fontSize = fontSize8, fontFamily = FontFamily.SansSerif)) {
364                 append("$displayText sans-serif\n")
365             }
366 
367             withStyle(style = SpanStyle(fontSize = fontSize8, fontFamily = FontFamily.Serif)) {
368                 append("$displayText serif\n")
369             }
370 
371             withStyle(style = SpanStyle(fontSize = fontSize8, fontFamily = FontFamily.Monospace)) {
372                 append("$displayText monospace")
373             }
374         }
375     )
376 }
377 
378 @Composable
TextDemoFontFamilyDefaultValuesnull379 fun TextDemoFontFamilyDefaultValues() {
380     // This group of text composables show the default font families in English.
381     FontFamilySerifSample()
382     FontFamilySansSerifSample()
383     FontFamilyMonospaceSample()
384     FontFamilyCursiveSample()
385 }
386 
387 @Composable
TextDemoTextDecorationnull388 fun TextDemoTextDecoration() {
389     // This group of text composables show different decoration, decorationColor and
390     // decorationStyle.
391     TextDecorationLineThroughSample()
392     TextDecorationUnderlineSample()
393     TextDecorationCombinedSample()
394 }
395 
396 @Composable
TextDemoLetterSpacingnull397 fun TextDemoLetterSpacing() {
398     // This group of text composables show different letterSpacing.
399     Text(
400         text =
401             buildAnnotatedString {
402                 withStyle(style = SpanStyle(fontSize = fontSize8)) { append("$displayText   ") }
403                 withStyle(style = SpanStyle(fontSize = fontSize8, letterSpacing = 0.5.em)) {
404                     append(displayText)
405                 }
406             }
407     )
408 }
409 
410 @Composable
TextDemoBaselineShiftnull411 fun TextDemoBaselineShift() {
412     BaselineShiftSample()
413 }
414 
415 @Composable
TextDemoHeightnull416 fun TextDemoHeight() {
417     // This group of text composables show different height.
418     Row(Modifier.fillMaxWidth()) {
419         Text(text = "$displayText\n$displayText   ", style = TextStyle(fontSize = fontSize8))
420         Text(
421             text = "$displayText\n$displayText   ",
422             style = TextStyle(fontSize = fontSize8, lineHeight = 50.sp)
423         )
424     }
425 }
426 
427 @Composable
TextDemoBackgroundnull428 fun TextDemoBackground() {
429     // This group of text composables show different background.
430     Text(
431         text =
432             buildAnnotatedString {
433                 withStyle(style = SpanStyle(background = Color(0xFFFF0000))) {
434                     append("$displayText   ")
435                 }
436 
437                 withStyle(style = SpanStyle(background = Color(0xFF00FF00))) {
438                     append("$displayText   ")
439                 }
440 
441                 withStyle(style = SpanStyle(background = Color(0xFF0000FF))) { append(displayText) }
442             },
443         style = TextStyle(fontSize = fontSize8)
444     )
445 }
446 
447 @Composable
TextDemoLocalenull448 fun TextDemoLocale() {
449     // This group of text composables show different Locales of the same Unicode codepoint.
450     val text = "\u82B1"
451     Text(
452         text =
453             buildAnnotatedString {
454                 withStyle(style = SpanStyle(localeList = LocaleList("ja-JP"))) {
455                     append("$text   ")
456                 }
457 
458                 withStyle(style = SpanStyle(localeList = LocaleList("zh-CN"))) {
459                     append("$text   ")
460                 }
461 
462                 withStyle(style = SpanStyle(localeList = LocaleList("zh-TW"))) { append(text) }
463             },
464         style = TextStyle(fontSize = fontSize8)
465     )
466 }
467 
468 @Composable
TextDemoTextAlignnull469 fun TextDemoTextAlign() {
470     // This group of text composables show different TextAligns: LEFT, RIGHT, CENTER, JUSTIFY, START
471     // for
472     // LTR and RTL, END for LTR and RTL.
473     var text = ""
474     for (i in 1..10) {
475         text = "$text$displayText "
476     }
477     Column(Modifier.fillMaxHeight()) {
478         SecondTagLine(tag = "textAlign = TextAlign.Left")
479         Text(
480             modifier = Modifier.fillMaxWidth(),
481             text = displayText,
482             style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Left)
483         )
484 
485         SecondTagLine(tag = "textAlign = TextAlign.Right")
486         Text(
487             modifier = Modifier.fillMaxWidth(),
488             text = displayText,
489             style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Right)
490         )
491 
492         SecondTagLine(tag = "textAlign = TextAlign.Center")
493         Text(
494             modifier = Modifier.fillMaxWidth(),
495             text = displayText,
496             style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Center)
497         )
498 
499         SecondTagLine(tag = "textAlign = default and TextAlign.Justify")
500         Text(
501             modifier = Modifier.fillMaxWidth(),
502             text = text,
503             style = TextStyle(fontSize = fontSize8, color = Color(0xFFFF0000))
504         )
505         Text(
506             modifier = Modifier.fillMaxWidth(),
507             text = text,
508             style =
509                 TextStyle(
510                     fontSize = fontSize8,
511                     color = Color(0xFF0000FF),
512                     textAlign = TextAlign.Justify
513                 )
514         )
515 
516         SecondTagLine(tag = "textAlign = TextAlign.Start for Ltr")
517         Text(
518             modifier = Modifier.fillMaxWidth(),
519             text = displayText,
520             style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Start)
521         )
522         SecondTagLine(tag = "textAlign = TextAlign.Start for Rtl")
523         Text(
524             modifier = Modifier.fillMaxWidth(),
525             text = displayTextArabic,
526             style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Start)
527         )
528         SecondTagLine(tag = "textAlign = TextAlign.End for Ltr")
529         Text(
530             modifier = Modifier.fillMaxWidth(),
531             text = displayText,
532             style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.End)
533         )
534         SecondTagLine(tag = "textAlign = TextAlign.End for Rtl")
535         Text(
536             modifier = Modifier.fillMaxWidth(),
537             text = displayTextArabic,
538             style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.End)
539         )
540     }
541 }
542 
543 @Composable
TextDemoSoftWrapnull544 fun TextDemoSoftWrap() {
545     // This group of text composables show difference between softWrap is true and false.
546     var text = ""
547     for (i in 1..10) {
548         text = "$text$displayText"
549     }
550     val textStyle = TextStyle(fontSize = fontSize8, color = Color(0xFFFF0000))
551 
552     Column(Modifier.fillMaxHeight()) {
553         Text(text = text, style = textStyle)
554         Text(text = text, style = textStyle, softWrap = false)
555     }
556 }
557 
558 @Composable
TextDemoHyphensnull559 fun TextDemoHyphens() {
560     val text = "Transformation"
561     val textStyleHyphensOn =
562         TextStyle(fontSize = fontSize8, color = Color.Red, hyphens = Hyphens.Auto)
563     val textStyleHyphensOff =
564         TextStyle(fontSize = fontSize8, color = Color.Blue, hyphens = Hyphens.None)
565     Column {
566         var width by remember { mutableFloatStateOf(30f) }
567         Slider(value = width, onValueChange = { width = it }, valueRange = 20f..400f)
568         Column(Modifier.width(width.dp)) {
569             Text(text = text, style = textStyleHyphensOn)
570             Text(text = text, style = textStyleHyphensOff)
571         }
572     }
573 }
574 
575 @Composable
TextDemoShadowEffectnull576 fun TextDemoShadowEffect() {
577     val shadow = Shadow(Color(0xFFE0A0A0), Offset(5f, 5f), blurRadius = 5.0f)
578     Text(
579         style = TextStyle(fontSize = fontSize8),
580         text =
581             buildAnnotatedString {
582                 append("text with ")
583                 withStyle(style = SpanStyle(shadow = shadow)) { append("shadow!") }
584             }
585     )
586 }
587 
588 @Composable
TextDemoFontSizeScalenull589 fun TextDemoFontSizeScale() {
590     Text(
591         style = TextStyle(fontSize = fontSize8),
592         text =
593             buildAnnotatedString {
594                 for (i in 4..12 step 4) {
595                     val scale = i * 0.1f
596                     withStyle(style = SpanStyle(fontSize = scale.em)) {
597                         append("fontSizeScale=$scale\n")
598                     }
599                 }
600             }
601     )
602 }
603 
604 @Composable
TextDemoParagraphStylingnull605 fun TextDemoParagraphStyling() {
606     ParagraphStyleSample()
607     ParagraphStyleAnnotatedStringsSample()
608 }
609 
610 @Composable
TextDemoTextOverflownull611 fun TextDemoTextOverflow() {
612     SecondTagLine(tag = "overflow = TextOverflow.Clip")
613     TextOverflowClipSample()
614     SecondTagLine(tag = "overflow = TextOverflow.Ellipsis")
615     TextOverflowEllipsisSample()
616     SecondTagLine(tag = "overflow = TextOverflow.Visible with fixed size")
617     TextOverflowVisibleFixedSizeSample()
618     Spacer(modifier = Modifier.size(30.dp))
619     SecondTagLine(tag = "overflow = TextOverflow.Visible with fixed width and min height")
620     TextOverflowVisibleMinHeightSample()
621 }
622 
623 @Composable
TextOverflowVisibleInPopupDemonull624 fun TextOverflowVisibleInPopupDemo() {
625     Popup(alignment = Alignment.Center, properties = PopupProperties(clippingEnabled = false)) {
626         val text = "Line\n".repeat(10)
627         Box(Modifier.background(Color.Magenta).size(100.dp)) {
628             Text(text, fontSize = fontSize6, overflow = TextOverflow.Visible)
629         }
630     }
631 }
632 
633 @OptIn(ExperimentalTextApi::class)
634 @Composable
TextOverflowVisibleInDrawTextnull635 fun TextOverflowVisibleInDrawText() {
636     val textMeasurer = rememberTextMeasurer()
637     val text = "Line\n".repeat(10)
638     Box(Modifier.fillMaxSize()) {
639         Canvas(
640             Modifier.graphicsLayer().align(Alignment.Center).background(Color.Green).size(100.dp)
641         ) {
642             drawText(
643                 textMeasurer = textMeasurer,
644                 text = text,
645                 style = TextStyle(fontSize = fontSize6),
646                 overflow = TextOverflow.Visible
647             )
648         }
649     }
650 }
651 
652 @Composable
TextOverflowDemonull653 fun TextOverflowDemo() {
654     Column(
655         verticalArrangement = Arrangement.spacedBy(10.dp),
656         modifier = Modifier.verticalScroll(rememberScrollState())
657     ) {
658         var singleParagraph by remember { mutableStateOf(boolOptions[0]) }
659         var selectedOverflow by remember { mutableStateOf(overflowOptions[0]) }
660         var singleLinePerPar by remember { mutableStateOf(boolOptions[1]) }
661         var width by remember { mutableFloatStateOf(250f) }
662         var height by remember { mutableFloatStateOf(50f) }
663         var letterSpacing by remember { mutableFloatStateOf(0f) }
664         var textAlign by remember { mutableStateOf(TextAlign.Left) }
665         var softWrap by remember { mutableStateOf(true) }
666 
667         TextOverflowDemo(
668             singleParagraph,
669             selectedOverflow,
670             singleLinePerPar,
671             width.dp,
672             height.dp,
673             letterSpacing.sp,
674             textAlign,
675             softWrap
676         )
677 
678         Row(Modifier.fillMaxWidth()) {
679             Column(Modifier.selectableGroup().weight(1f)) {
680                 Text("TextOverflow", fontWeight = FontWeight.Bold)
681                 overflowOptions.forEach {
682                     Row(
683                         Modifier.fillMaxWidth()
684                             .selectable(
685                                 selected = (it == selectedOverflow),
686                                 onClick = { selectedOverflow = it },
687                                 role = Role.RadioButton
688                             ),
689                         verticalAlignment = Alignment.CenterVertically
690                     ) {
691                         RadioButton(
692                             selected = (it == selectedOverflow),
693                             onClick = null // null recommended for accessibility with screenreaders
694                         )
695                         Text(text = it.toString())
696                     }
697                 }
698             }
699             Column(Modifier.selectableGroup().weight(1f)) {
700                 Text("Paragraph", fontWeight = FontWeight.Bold)
701                 boolOptions.forEach {
702                     Row(
703                         Modifier.fillMaxWidth()
704                             .selectable(
705                                 selected = (it == singleParagraph),
706                                 onClick = { singleParagraph = it },
707                                 role = Role.RadioButton
708                             ),
709                         verticalAlignment = Alignment.CenterVertically
710                     ) {
711                         RadioButton(
712                             selected = (it == singleParagraph),
713                             onClick = null // null recommended for accessibility with screenreaders
714                         )
715                         Text(text = if (it) "Single" else "Multi")
716                     }
717                 }
718             }
719             Column(Modifier.selectableGroup().weight(1f)) {
720                 Text("Single line", fontWeight = FontWeight.Bold)
721                 boolOptions.forEach {
722                     Row(
723                         Modifier.fillMaxWidth()
724                             .selectable(
725                                 selected = (it == singleLinePerPar),
726                                 onClick = { singleLinePerPar = it },
727                                 role = Role.RadioButton
728                             ),
729                         verticalAlignment = Alignment.CenterVertically
730                     ) {
731                         RadioButton(selected = (it == singleLinePerPar), onClick = null)
732                         Text(text = it.toString())
733                     }
734                 }
735             }
736         }
737         Column {
738             Text("Width " + "%.1f".format(width) + "dp")
739             Slider(width, { width = it }, valueRange = 30f..300f)
740         }
741         Column {
742             Text("Height " + "%.1f".format(height) + "dp")
743             Slider(height, { height = it }, valueRange = 5f..300f)
744         }
745         Column {
746             Text("Letter spacing " + "%.1f".format(letterSpacing) + "sp")
747             Slider(letterSpacing, { letterSpacing = it }, valueRange = -4f..8f, steps = 11)
748         }
749         Row(Modifier.fillMaxWidth()) {
750             Column(Modifier.weight(1f)) {
751                 Text("Text Align", fontWeight = FontWeight.Bold)
752                 textAlignments.forEach {
753                     Row(
754                         Modifier.fillMaxWidth()
755                             .selectable(
756                                 selected = (it == textAlign),
757                                 onClick = { textAlign = it },
758                                 role = Role.RadioButton
759                             ),
760                         verticalAlignment = Alignment.CenterVertically
761                     ) {
762                         RadioButton(selected = (it == textAlign), onClick = null)
763                         Text(text = it.toString())
764                     }
765                 }
766             }
767             Column(Modifier.weight(1f)) {
768                 Text("Soft wrap", fontWeight = FontWeight.Bold)
769                 boolOptions.forEach {
770                     Row(
771                         Modifier.fillMaxWidth()
772                             .selectable(
773                                 selected = (it == softWrap),
774                                 onClick = { softWrap = it },
775                                 role = Role.RadioButton
776                             ),
777                         verticalAlignment = Alignment.CenterVertically
778                     ) {
779                         RadioButton(selected = (it == softWrap), onClick = null)
780                         Text(text = it.toString())
781                     }
782                 }
783             }
784         }
785     }
786 }
787 
788 @Composable
TextOverflowDemonull789 private fun ColumnScope.TextOverflowDemo(
790     singleParagraph: Boolean,
791     textOverflow: TextOverflow,
792     singeLine: Boolean,
793     width: Dp,
794     height: Dp,
795     letterSpacing: TextUnit,
796     textAlign: TextAlign,
797     softWrap: Boolean
798 ) {
799     Box(Modifier.weight(1f).fillMaxWidth()) {
800         val text =
801             if (singleParagraph) {
802                 AnnotatedString(longText)
803             } else {
804                 buildAnnotatedString {
805                     append(longText)
806                     withStyle(ParagraphStyle(textAlign = TextAlign.End)) {
807                         append("This is a second paragraph.")
808                     }
809                 }
810             }
811         val textStyle =
812             TextStyle(fontSize = fontSize6, letterSpacing = letterSpacing, textAlign = textAlign)
813         BasicText(
814             text = text,
815             modifier =
816                 Modifier.align(Alignment.Center)
817                     .background(Color.Magenta)
818                     .widthIn(max = width)
819                     .heightIn(max = height),
820             style = textStyle,
821             overflow = textOverflow,
822             maxLines = if (singeLine) 1 else Int.MAX_VALUE,
823             softWrap = softWrap
824         )
825     }
826 }
827 
828 @Composable
TextDemoInlineContentnull829 fun TextDemoInlineContent() {
830     val inlineContentId = "box"
831     val inlineTextContent =
832         InlineTextContent(
833             placeholder =
834                 Placeholder(
835                     width = 5.em,
836                     height = 1.em,
837                     placeholderVerticalAlign = PlaceholderVerticalAlign.AboveBaseline
838                 )
839         ) {
840             val colorAnimation = rememberInfiniteTransition()
841             val color by
842                 colorAnimation.animateColor(
843                     initialValue = Color.Red,
844                     targetValue = Color.Blue,
845                     animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse)
846                 )
847             Box(modifier = Modifier.fillMaxSize().background(color))
848         }
849 
850     Text(
851         text =
852             buildAnnotatedString {
853                 append("Here is a wide inline composable ")
854                 appendInlineContent(inlineContentId)
855                 append(" that is repeatedly changing its color.")
856             },
857         inlineContent = mapOf(inlineContentId to inlineTextContent),
858         modifier = Modifier.fillMaxWidth()
859     )
860 
861     SecondTagLine(tag = "RTL Layout")
862     CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
863         Text(
864             text =
865                 buildAnnotatedString {
866                     append("Here is a wide inline composable ")
867                     appendInlineContent(inlineContentId)
868                     append(" that is repeatedly changing its color.")
869                 },
870             inlineContent = mapOf(inlineContentId to inlineTextContent),
871             modifier = Modifier.fillMaxWidth()
872         )
873     }
874 
875     SecondTagLine(tag = "Bidi Text - LTR/RTL")
876     Text(
877         text =
878             buildAnnotatedString {
879                 append("$displayText   ")
880                 appendInlineContent(inlineContentId)
881                 append("$displayTextArabic   ")
882             },
883         inlineContent = mapOf(inlineContentId to inlineTextContent),
884         modifier = Modifier.fillMaxWidth()
885     )
886 
887     SecondTagLine(tag = "Bidi Text - RTL/LTR")
888     Text(
889         text =
890             buildAnnotatedString {
891                 append("$displayTextArabic   ")
892                 appendInlineContent(inlineContentId)
893                 append("$displayText   ")
894             },
895         inlineContent = mapOf(inlineContentId to inlineTextContent),
896         modifier = Modifier.fillMaxWidth()
897     )
898 }
899 
900 @OptIn(ExperimentalMaterialApi::class)
901 @Composable
EllipsizeDemonull902 fun EllipsizeDemo() {
903     var softWrap by remember { mutableStateOf(true) }
904     var ellipsis by remember { mutableStateOf(true) }
905     var withSpans by remember { mutableStateOf(false) }
906     val lineHeight = remember { mutableStateOf(16.sp) }
907     val heightRestriction = remember { mutableStateOf(45.dp) }
908 
909     Column {
910         ListItem(
911             Modifier.selectable(softWrap) { softWrap = !softWrap },
912             trailing = { Switch(softWrap, null) }
913         ) {
914             Text("Soft wrap")
915         }
916         ListItem(
917             Modifier.selectable(ellipsis) { ellipsis = !ellipsis },
918             trailing = { Switch(ellipsis, null) }
919         ) {
920             Text("Ellipsis")
921         }
922         ListItem(
923             Modifier.selectable(withSpans) { withSpans = !withSpans },
924             trailing = { Switch(withSpans, null) },
925             secondaryText = { Text("Text with spans") }
926         ) {
927             Text("Spans")
928         }
929 
930         Row(horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.fillMaxWidth()) {
931             Column(horizontalAlignment = Alignment.CenterHorizontally) {
932                 IconButton({
933                     heightRestriction.value = (heightRestriction.value + 5.dp).coerceAtMost(300.dp)
934                 }) {
935                     Icon(Icons.Default.KeyboardArrowUp, "Increase height")
936                 }
937                 Text("Max height ${heightRestriction.value}")
938                 IconButton({
939                     heightRestriction.value = (heightRestriction.value - 5.dp).coerceAtLeast(0.dp)
940                 }) {
941                     Icon(Icons.Default.KeyboardArrowDown, "Decrease height")
942                 }
943             }
944 
945             Column(horizontalAlignment = Alignment.CenterHorizontally) {
946                 IconButton({
947                     lineHeight.value = ((lineHeight.value.value + 2f)).coerceAtMost(100f).sp
948                 }) {
949                     Icon(Icons.Default.KeyboardArrowUp, "Increase line height")
950                 }
951                 Text("Line height ${lineHeight.value.value.toInt().sp}")
952                 IconButton({
953                     lineHeight.value = ((lineHeight.value.value - 2f)).coerceAtLeast(5f).sp
954                 }) {
955                     Icon(Icons.Default.KeyboardArrowDown, "Decrease line height")
956                 }
957             }
958         }
959 
960         val fontSize = 16.sp
961         val text =
962             "This is a very-very " +
963                 "long text that has a limited height and width to test how it's ellipsized." +
964                 " This is a second sentence of the text."
965         val textWithSpans = buildAnnotatedString {
966             withStyle(SpanStyle(fontSize = fontSize / 2)) {
967                 append("This is a very-very long text that has ")
968             }
969             withStyle(SpanStyle(fontSize = fontSize * 2)) { append("a limited height") }
970             append(" and width to test how it's ellipsized. This is a second sentence of the text.")
971         }
972         Text(
973             text = if (withSpans) textWithSpans else AnnotatedString(text),
974             fontSize = fontSize,
975             lineHeight = lineHeight.value,
976             modifier =
977                 Modifier.background(Color.Magenta)
978                     .width(200.dp)
979                     .heightIn(max = heightRestriction.value),
980             softWrap = softWrap,
981             overflow = if (ellipsis) TextOverflow.Ellipsis else TextOverflow.Clip
982         )
983     }
984 }
985 
986 @Composable
AutoSizeTextDemonull987 fun AutoSizeTextDemo() {
988     val text = "This is a sample string!"
989     Column(Modifier.fillMaxWidth()) {
990         // This text will be sized according to default values of AutoSize.StepBased
991         BasicText(
992             text,
993             modifier = Modifier.border(1.dp, Color.Red).height(40.dp),
994             autoSize = TextAutoSize.StepBased()
995         )
996         // This text can either have a font size of 10, 20, 30, 40, 50 or 60 sp
997         BasicText(
998             text,
999             autoSize =
1000                 TextAutoSize.StepBased(minFontSize = 10.sp, maxFontSize = 60.sp, stepSize = 10.sp)
1001         )
1002     }
1003 }
1004