Commit 36d437d6 authored by Adam Barth's avatar Adam Barth

Add support for TextOverflow.ellipsis (#3818)

Fixes #417
parent 4d8f38e5
...@@ -209,7 +209,13 @@ class AppBar extends StatelessWidget { ...@@ -209,7 +209,13 @@ class AppBar extends StatelessWidget {
toolBarRow.add(new Flexible( toolBarRow.add(new Flexible(
child: new Padding( child: new Padding(
padding: new EdgeInsets.only(left: 8.0), padding: new EdgeInsets.only(left: 8.0),
child: title != null ? new DefaultTextStyle(style: centerStyle, child: title) : null child: title != null ?
new DefaultTextStyle(
style: centerStyle,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: title
) : null
) )
)); ));
if (actions != null) if (actions != null)
......
...@@ -345,7 +345,12 @@ class _Tab extends StatelessWidget { ...@@ -345,7 +345,12 @@ class _Tab extends StatelessWidget {
Widget _buildLabelText() { Widget _buildLabelText() {
assert(label.text != null); assert(label.text != null);
TextStyle style = new TextStyle(color: color); TextStyle style = new TextStyle(color: color);
return new Text(label.text, style: style); return new Text(
label.text,
style: style,
softWrap: false,
overflow: TextOverflow.fade
);
} }
Widget _buildLabelIcon(BuildContext context) { Widget _buildLabelIcon(BuildContext context) {
...@@ -371,15 +376,15 @@ class _Tab extends StatelessWidget { ...@@ -371,15 +376,15 @@ class _Tab extends StatelessWidget {
labelContent = _buildLabelIcon(context); labelContent = _buildLabelIcon(context);
} else { } else {
labelContent = new Column( labelContent = new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
new Container( new Container(
child: _buildLabelIcon(context), child: _buildLabelIcon(context),
margin: const EdgeInsets.only(bottom: 10.0) margin: const EdgeInsets.only(bottom: 10.0)
), ),
_buildLabelText() _buildLabelText()
], ]
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center
); );
} }
......
...@@ -2,20 +2,42 @@ ...@@ -2,20 +2,42 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
import 'semantics.dart'; import 'semantics.dart';
/// How overflowing text should be handled.
enum TextOverflow {
/// Clip the overflowing text to fix its container.
clip,
/// Fade the overflowing text to transparent.
fade,
/// Use an ellipsis to indicate that the text has overflowed.
ellipsis,
}
/// A render object that displays a paragraph of text /// A render object that displays a paragraph of text
class RenderParagraph extends RenderBox { class RenderParagraph extends RenderBox {
/// Creates a paragraph render object.
///
/// The [text], [overflow], and [softWrap] arguments must not be null.
RenderParagraph(TextSpan text, { RenderParagraph(TextSpan text, {
TextAlign textAlign TextAlign textAlign,
}) : _textPainter = new TextPainter(text: text, textAlign: textAlign) { TextOverflow overflow: TextOverflow.clip,
bool softWrap: true
}) : _softWrap = softWrap,
_overflow = overflow,
_textPainter = new TextPainter(text: text, textAlign: textAlign) {
assert(text != null); assert(text != null);
assert(text.debugAssertValid()); assert(text.debugAssertValid());
assert(overflow != null);
assert(softWrap != null);
} }
final TextPainter _textPainter; final TextPainter _textPainter;
...@@ -27,6 +49,8 @@ class RenderParagraph extends RenderBox { ...@@ -27,6 +49,8 @@ class RenderParagraph extends RenderBox {
if (_textPainter.text == value) if (_textPainter.text == value)
return; return;
_textPainter.text = value; _textPainter.text = value;
_overflowPainter = null;
_overflowShader = null;
markNeedsLayout(); markNeedsLayout();
} }
...@@ -39,10 +63,34 @@ class RenderParagraph extends RenderBox { ...@@ -39,10 +63,34 @@ class RenderParagraph extends RenderBox {
markNeedsPaint(); markNeedsPaint();
} }
/// Whether the text should break at soft line breaks.
///
/// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
bool get softWrap => _softWrap;
bool _softWrap;
void set softWrap(bool value) {
assert(value != null);
if (_softWrap == value)
return;
_softWrap = value;
markNeedsLayout();
}
/// How visual overflow should be handled.
TextOverflow get overflow => _overflow;
TextOverflow _overflow;
void set overflow(TextOverflow value) {
assert(value != null);
if (_overflow == value)
return;
_overflow = value;
markNeedsPaint();
}
void _layoutText(BoxConstraints constraints) { void _layoutText(BoxConstraints constraints) {
assert(constraints != null); assert(constraints != null);
assert(constraints.debugAssertIsValid()); assert(constraints.debugAssertIsValid());
_textPainter.layout(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); _textPainter.layout(minWidth: constraints.minWidth, maxWidth: _softWrap ? constraints.maxWidth : double.INFINITY);
} }
@override @override
...@@ -95,10 +143,49 @@ class RenderParagraph extends RenderBox { ...@@ -95,10 +143,49 @@ class RenderParagraph extends RenderBox {
span?.recognizer?.addPointer(event); span?.recognizer?.addPointer(event);
} }
bool _hasVisualOverflow = false;
TextPainter _overflowPainter;
ui.Shader _overflowShader;
@override @override
void performLayout() { void performLayout() {
_layoutText(constraints); _layoutText(constraints);
size = constraints.constrain(_textPainter.size); size = constraints.constrain(_textPainter.size);
final bool didOverflowWidth = size.width < _textPainter.width;
// TODO(abarth): We're only measuring the sizes of the line boxes here. If
// the glyphs draw outside the line boxes, we might think that there isn't
// visual overflow when there actually is visual overflow. This can become
// a problem if we start having horizontal overflow and introduce a clip
// that affects the actual (but undetected) vertical overflow.
_hasVisualOverflow = didOverflowWidth || size.height < _textPainter.height;
if (didOverflowWidth) {
switch (_overflow) {
case TextOverflow.clip:
_overflowPainter = null;
_overflowShader = null;
break;
case TextOverflow.fade:
case TextOverflow.ellipsis:
_overflowPainter ??= new TextPainter(
text: new TextSpan(style: _textPainter.text.style, text: '\u2026')
)..layout();
final double overflowUnit = _overflowPainter.width;
double fadeEnd = size.width;
if (_overflow == TextOverflow.ellipsis)
fadeEnd -= overflowUnit / 2.0;
final double fadeStart = fadeEnd - _overflowPainter.width;
// TODO(abarth): This shader has an LTR bias.
_overflowShader = new ui.Gradient.linear(
<Point>[new Point(fadeStart, 0.0), new Point(fadeEnd, 0.0)],
<Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)]
);
break;
}
} else {
_overflowPainter = null;
_overflowShader = null;
}
} }
@override @override
...@@ -114,7 +201,31 @@ class RenderParagraph extends RenderBox { ...@@ -114,7 +201,31 @@ class RenderParagraph extends RenderBox {
// If you remove this call, make sure that changing the textAlign still // If you remove this call, make sure that changing the textAlign still
// works properly. // works properly.
_layoutText(constraints); _layoutText(constraints);
_textPainter.paint(context.canvas, offset); final Canvas canvas = context.canvas;
if (_hasVisualOverflow) {
final Rect bounds = offset & size;
if (_overflowPainter != null)
canvas.saveLayer(bounds, new Paint());
else
canvas.save();
canvas.clipRect(bounds);
}
_textPainter.paint(canvas, offset);
if (_hasVisualOverflow) {
if (_overflowShader != null) {
canvas.translate(offset.dx, offset.dy);
Paint paint = new Paint()
..transferMode = TransferMode.modulate
..shader = _overflowShader;
canvas.drawRect(Point.origin & size, paint);
if (_overflow == TextOverflow.ellipsis) {
// TODO(abarth): This paint offset has an LTR bias.
Offset ellipseOffset = new Offset(size.width - _overflowPainter.width, 0.0);
_overflowPainter.paint(canvas, ellipseOffset);
}
}
canvas.restore();
}
} }
@override @override
......
...@@ -45,6 +45,7 @@ export 'package:flutter/rendering.dart' show ...@@ -45,6 +45,7 @@ export 'package:flutter/rendering.dart' show
RenderObjectPainter, RenderObjectPainter,
ShaderCallback, ShaderCallback,
SingleChildLayoutDelegate, SingleChildLayoutDelegate,
TextOverflow,
ValueChanged, ValueChanged,
ViewportAnchor, ViewportAnchor,
ViewportDimensions, ViewportDimensions,
...@@ -1904,8 +1905,16 @@ class RichText extends LeafRenderObjectWidget { ...@@ -1904,8 +1905,16 @@ class RichText extends LeafRenderObjectWidget {
/// Creates a paragraph of rich text. /// Creates a paragraph of rich text.
/// ///
/// The [text] argument is required to be non-null. /// The [text] argument is required to be non-null.
RichText({ Key key, this.text, this.textAlign }) : super(key: key) { RichText({
Key key,
this.text,
this.textAlign,
this.softWrap: true,
this.overflow: TextOverflow.clip
}) : super(key: key) {
assert(text != null); assert(text != null);
assert(softWrap != null);
assert(overflow != null);
} }
/// The text to display in this widget. /// The text to display in this widget.
...@@ -1914,16 +1923,30 @@ class RichText extends LeafRenderObjectWidget { ...@@ -1914,16 +1923,30 @@ class RichText extends LeafRenderObjectWidget {
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
final TextAlign textAlign; final TextAlign textAlign;
/// Whether the text should break at soft line breaks.
///
/// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
final bool softWrap;
/// How visual overflow should be handled.
final TextOverflow overflow;
@override @override
RenderParagraph createRenderObject(BuildContext context) { RenderParagraph createRenderObject(BuildContext context) {
return new RenderParagraph(text, textAlign: textAlign); return new RenderParagraph(text,
textAlign: textAlign,
softWrap: softWrap,
overflow: overflow
);
} }
@override @override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) { void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
renderObject renderObject
..text = text ..text = text
..textAlign = textAlign; ..textAlign = textAlign
..softWrap = softWrap
..overflow = overflow;
} }
} }
...@@ -1937,16 +1960,24 @@ class DefaultTextStyle extends InheritedWidget { ...@@ -1937,16 +1960,24 @@ class DefaultTextStyle extends InheritedWidget {
Key key, Key key,
this.style, this.style,
this.textAlign, this.textAlign,
this.softWrap: true,
this.overflow: TextOverflow.clip,
Widget child Widget child
}) : super(key: key, child: child) { }) : super(key: key, child: child) {
assert(style != null); assert(style != null);
assert(softWrap != null);
assert(overflow != null);
assert(child != null); assert(child != null);
} }
/// A const-constructible default text style that provides fallback values. /// A const-constructible default text style that provides fallback values.
/// ///
/// Returned from [of] when the given [BuildContext] doesn't have an enclosing default text style. /// Returned from [of] when the given [BuildContext] doesn't have an enclosing default text style.
const DefaultTextStyle.fallback() : style = const TextStyle(), textAlign = null; const DefaultTextStyle.fallback()
: style = const TextStyle(),
textAlign = null,
softWrap = true,
overflow = TextOverflow.clip;
/// Creates a default text style that inherits from the given [BuildContext]. /// Creates a default text style that inherits from the given [BuildContext].
/// ///
...@@ -1959,6 +1990,8 @@ class DefaultTextStyle extends InheritedWidget { ...@@ -1959,6 +1990,8 @@ class DefaultTextStyle extends InheritedWidget {
BuildContext context, BuildContext context,
TextStyle style, TextStyle style,
TextAlign textAlign, TextAlign textAlign,
bool softWrap,
TextOverflow overflow,
Widget child Widget child
}) { }) {
DefaultTextStyle parent = DefaultTextStyle.of(context); DefaultTextStyle parent = DefaultTextStyle.of(context);
...@@ -1966,6 +1999,8 @@ class DefaultTextStyle extends InheritedWidget { ...@@ -1966,6 +1999,8 @@ class DefaultTextStyle extends InheritedWidget {
key: key, key: key,
style: parent.style.merge(style), style: parent.style.merge(style),
textAlign: textAlign ?? parent.textAlign, textAlign: textAlign ?? parent.textAlign,
softWrap: softWrap ?? parent.softWrap,
overflow: overflow ?? parent.overflow,
child: child child: child
); );
} }
...@@ -1976,6 +2011,14 @@ class DefaultTextStyle extends InheritedWidget { ...@@ -1976,6 +2011,14 @@ class DefaultTextStyle extends InheritedWidget {
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
final TextAlign textAlign; final TextAlign textAlign;
/// Whether the text should break at soft line breaks.
///
/// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
final bool softWrap;
/// How visual overflow should be handled.
final TextOverflow overflow;
/// The closest instance of this class that encloses the given context. /// The closest instance of this class that encloses the given context.
/// ///
/// If no such instance exists, returns an instance created by /// If no such instance exists, returns an instance created by
...@@ -2019,7 +2062,13 @@ class Text extends StatelessWidget { ...@@ -2019,7 +2062,13 @@ class Text extends StatelessWidget {
/// ///
/// If the [style] argument is null, the text will use the style from the /// If the [style] argument is null, the text will use the style from the
/// closest enclosing [DefaultTextStyle]. /// closest enclosing [DefaultTextStyle].
Text(this.data, { Key key, this.style, this.textAlign }) : super(key: key) { Text(this.data, {
Key key,
this.style,
this.textAlign,
this.softWrap,
this.overflow
}) : super(key: key) {
assert(data != null); assert(data != null);
} }
...@@ -2036,21 +2085,24 @@ class Text extends StatelessWidget { ...@@ -2036,21 +2085,24 @@ class Text extends StatelessWidget {
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
final TextAlign textAlign; final TextAlign textAlign;
/// Whether the text should break at soft line breaks.
///
/// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
final bool softWrap;
/// How visual overflow should be handled.
final TextOverflow overflow;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
DefaultTextStyle defaultTextStyle; DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style; TextStyle effectiveTextStyle = style;
if (style == null || style.inherit) { if (style == null || style.inherit)
defaultTextStyle ??= DefaultTextStyle.of(context);
effectiveTextStyle = defaultTextStyle.style.merge(style); effectiveTextStyle = defaultTextStyle.style.merge(style);
}
TextAlign effectiveTextAlign = textAlign;
if (effectiveTextAlign == null) {
defaultTextStyle ??= DefaultTextStyle.of(context);
effectiveTextAlign = defaultTextStyle.textAlign;
}
return new RichText( return new RichText(
textAlign: effectiveTextAlign, textAlign: textAlign ?? defaultTextStyle.textAlign,
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
text: new TextSpan( text: new TextSpan(
style: effectiveTextStyle, style: effectiveTextStyle,
text: data text: data
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment