Skip to content

Instantly share code, notes, and snippets.

@kekland
Created February 2, 2023 13:46
Show Gist options
  • Save kekland/c2b37533c92a348d8595a39583215fd1 to your computer and use it in GitHub Desktop.
Save kekland/c2b37533c92a348d8595a39583215fd1 to your computer and use it in GitHub Desktop.
Reverse ellipse text
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
double _width = 300.0;
void _increaseWidth() {
setState(() => _width += 10.0);
}
void _decreaseWidth() {
setState(() => _width -= 10.0);
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
constraints: BoxConstraints(
minWidth: 0.0,
maxWidth: _width,
maxHeight: 100.0,
),
color: Colors.green,
child: Center(
child: ReverseEllipsisWidget(
text: 'hello world world world long world.pdf',
style: Theme.of(context).textTheme.bodyLarge!,
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: _decreaseWidth,
),
IconButton(
icon: const Icon(Icons.add),
onPressed: _increaseWidth,
),
],
),
],
),
);
}
}
class ReverseEllipsisWidget extends StatelessWidget {
const ReverseEllipsisWidget(
{required this.text, required this.style, super.key});
final String text;
final TextStyle style;
String _reverseString(String v) => v.split('').reversed.join('');
String _reverseStringAndInsertBreakingSpaces(String v) =>
v.split('').reversed.join('\u200b');
String _computeTextForReverseEllipsis(
BuildContext context,
String text,
BoxConstraints constraints,
) {
// Insert breakable spaces between characters.
// See https://github.com/flutter/flutter/issues/61081#issuecomment-1103330522
final reversedText = _reverseStringAndInsertBreakingSpaces(text);
final textDirection = Directionality.of(context);
final painter = TextPainter(
text: TextSpan(
text: reversedText,
style: style,
),
textDirection: textDirection,
);
// Check if the text can fit in 1 line
painter.layout(maxWidth: constraints.maxWidth);
if (painter.computeLineMetrics().length == 1) {
return text;
}
// If not, we need to cut the text. Prepare the ellipsis painter
final ellipsisPainter = TextPainter(
text: TextSpan(text: '...', style: style),
textDirection: textDirection,
);
ellipsisPainter.layout();
// Compute how much space do we have for the text line including the
// ellipsis
final maxWidth = constraints.maxWidth;
final maxWidthForText = maxWidth - ellipsisPainter.width;
painter.layout(maxWidth: maxWidthForText);
// Get the number of characters that fit into one line
final boundary = painter.getLineBoundary(
const TextPosition(offset: 0),
);
// Cut the text and reverse it back
final cutText = _reverseString(
reversedText.substring(boundary.start, boundary.end),
);
return '...$cutText';
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final cutText =
_computeTextForReverseEllipsis(context, text, constraints);
return Text(cutText, style: style, maxLines: 1);
},
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment