Files
iiEsaywebUIapp/lib/shared/widgets/middle_ellipsis_text.dart

122 lines
3.8 KiB
Dart
Raw Normal View History

import 'package:flutter/widgets.dart';
/// A single-line text widget that truncates the middle of long strings
/// with an ellipsis (e.g., "prefix…suffix") so both ends remain visible.
class MiddleEllipsisText extends StatelessWidget {
final String text;
final TextStyle? style;
final TextAlign? textAlign;
final String ellipsis;
final String? semanticsLabel;
const MiddleEllipsisText(
this.text, {
super.key,
this.style,
this.textAlign,
this.ellipsis = '',
this.semanticsLabel,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
2025-09-24 12:00:49 +05:30
final TextStyle effectiveStyle = DefaultTextStyle.of(
context,
).style.merge(style);
final TextDirection direction = Directionality.of(context);
final double maxWidth = constraints.maxWidth;
// Measure full text width first.
final fullSpan = TextSpan(text: text, style: effectiveStyle);
final fullPainter = TextPainter(
text: fullSpan,
textDirection: direction,
maxLines: 1,
)..layout(minWidth: 0, maxWidth: double.infinity);
if (fullPainter.width <= maxWidth) {
return Text(
text,
style: effectiveStyle,
maxLines: 1,
overflow: TextOverflow.clip,
textAlign: textAlign,
semanticsLabel: semanticsLabel,
);
}
// Pre-measure ellipsis width (used implicitly during search).
final ellipsisSpan = TextSpan(text: ellipsis, style: effectiveStyle);
final ellipsisPainter = TextPainter(
text: ellipsisSpan,
textDirection: direction,
maxLines: 1,
)..layout(minWidth: 0, maxWidth: double.infinity);
final double _ = ellipsisPainter.width; // hint width; not used directly
// Binary search the maximum number of visible characters (k), split
// between start and end. For a given k, we use ceil(k/2) from start
// and floor(k/2) from end.
int low = 0;
int high = text.length; // exclusive upper bound in practice
int bestK = 0;
String bestStart = '';
String bestEnd = '';
while (low <= high) {
final int k = (low + high) >> 1; // candidate visible char count
final int leftCount = (k + 1) >> 1; // ceil(k/2)
final int rightCount = k - leftCount; // floor(k/2)
final String start = text.substring(0, leftCount);
final String end = rightCount == 0
? ''
: text.substring(text.length - rightCount);
2025-09-24 12:00:49 +05:30
final trialSpan = TextSpan(
text: '$start$ellipsis$end',
style: effectiveStyle,
);
final trialPainter = TextPainter(
text: trialSpan,
textDirection: direction,
maxLines: 1,
)..layout(minWidth: 0, maxWidth: double.infinity);
if (trialPainter.width <= maxWidth) {
bestK = k;
bestStart = start;
bestEnd = end;
low = k + 1; // try to fit more
} else {
high = k - 1; // need fewer characters
}
}
if (bestK == 0) {
return Text(
ellipsis,
style: effectiveStyle,
maxLines: 1,
overflow: TextOverflow.clip,
textAlign: textAlign,
semanticsLabel: semanticsLabel ?? text,
);
}
final String display = '$bestStart$ellipsis$bestEnd';
return Text(
display,
style: effectiveStyle,
maxLines: 1,
overflow: TextOverflow.clip,
textAlign: textAlign,
semanticsLabel: semanticsLabel ?? text,
);
},
);
}
}