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 {
toolBarRow.add(new Flexible(
child: new Padding(
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)
......
......@@ -345,7 +345,12 @@ class _Tab extends StatelessWidget {
Widget _buildLabelText() {
assert(label.text != null);
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) {
......@@ -371,15 +376,15 @@ class _Tab extends StatelessWidget {
labelContent = _buildLabelIcon(context);
} else {
labelContent = new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
child: _buildLabelIcon(context),
margin: const EdgeInsets.only(bottom: 10.0)
),
_buildLabelText()
],
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center
]
);
}
......
......@@ -2,20 +2,42 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'box.dart';
import 'object.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
class RenderParagraph extends RenderBox {
/// Creates a paragraph render object.
///
/// The [text], [overflow], and [softWrap] arguments must not be null.
RenderParagraph(TextSpan text, {
TextAlign textAlign
}) : _textPainter = new TextPainter(text: text, textAlign: textAlign) {
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.debugAssertValid());
assert(overflow != null);
assert(softWrap != null);
}
final TextPainter _textPainter;
......@@ -27,6 +49,8 @@ class RenderParagraph extends RenderBox {
if (_textPainter.text == value)
return;
_textPainter.text = value;
_overflowPainter = null;
_overflowShader = null;
markNeedsLayout();
}
......@@ -39,10 +63,34 @@ class RenderParagraph extends RenderBox {
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) {
assert(constraints != null);
assert(constraints.debugAssertIsValid());
_textPainter.layout(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
_textPainter.layout(minWidth: constraints.minWidth, maxWidth: _softWrap ? constraints.maxWidth : double.INFINITY);
}
@override
......@@ -95,10 +143,49 @@ class RenderParagraph extends RenderBox {
span?.recognizer?.addPointer(event);
}
bool _hasVisualOverflow = false;
TextPainter _overflowPainter;
ui.Shader _overflowShader;
@override
void performLayout() {
_layoutText(constraints);
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
......@@ -114,7 +201,31 @@ class RenderParagraph extends RenderBox {
// If you remove this call, make sure that changing the textAlign still
// works properly.
_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
......
......@@ -45,6 +45,7 @@ export 'package:flutter/rendering.dart' show
RenderObjectPainter,
ShaderCallback,
SingleChildLayoutDelegate,
TextOverflow,
ValueChanged,
ViewportAnchor,
ViewportDimensions,
......@@ -1904,8 +1905,16 @@ class RichText extends LeafRenderObjectWidget {
/// Creates a paragraph of rich text.
///
/// 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(softWrap != null);
assert(overflow != null);
}
/// The text to display in this widget.
......@@ -1914,16 +1923,30 @@ class RichText extends LeafRenderObjectWidget {
/// How the text should be aligned horizontally.
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
RenderParagraph createRenderObject(BuildContext context) {
return new RenderParagraph(text, textAlign: textAlign);
return new RenderParagraph(text,
textAlign: textAlign,
softWrap: softWrap,
overflow: overflow
);
}
@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
renderObject
..text = text
..textAlign = textAlign;
..textAlign = textAlign
..softWrap = softWrap
..overflow = overflow;
}
}
......@@ -1937,16 +1960,24 @@ class DefaultTextStyle extends InheritedWidget {
Key key,
this.style,
this.textAlign,
this.softWrap: true,
this.overflow: TextOverflow.clip,
Widget child
}) : super(key: key, child: child) {
assert(style != null);
assert(softWrap != null);
assert(overflow != null);
assert(child != null);
}
/// 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.
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].
///
......@@ -1959,6 +1990,8 @@ class DefaultTextStyle extends InheritedWidget {
BuildContext context,
TextStyle style,
TextAlign textAlign,
bool softWrap,
TextOverflow overflow,
Widget child
}) {
DefaultTextStyle parent = DefaultTextStyle.of(context);
......@@ -1966,6 +1999,8 @@ class DefaultTextStyle extends InheritedWidget {
key: key,
style: parent.style.merge(style),
textAlign: textAlign ?? parent.textAlign,
softWrap: softWrap ?? parent.softWrap,
overflow: overflow ?? parent.overflow,
child: child
);
}
......@@ -1976,6 +2011,14 @@ class DefaultTextStyle extends InheritedWidget {
/// How the text should be aligned horizontally.
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.
///
/// If no such instance exists, returns an instance created by
......@@ -2019,7 +2062,13 @@ class Text extends StatelessWidget {
///
/// If the [style] argument is null, the text will use the style from the
/// 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);
}
......@@ -2036,21 +2085,24 @@ class Text extends StatelessWidget {
/// How the text should be aligned horizontally.
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
Widget build(BuildContext context) {
DefaultTextStyle defaultTextStyle;
DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style;
if (style == null || style.inherit) {
defaultTextStyle ??= DefaultTextStyle.of(context);
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
}
TextAlign effectiveTextAlign = textAlign;
if (effectiveTextAlign == null) {
defaultTextStyle ??= DefaultTextStyle.of(context);
effectiveTextAlign = defaultTextStyle.textAlign;
}
return new RichText(
textAlign: effectiveTextAlign,
textAlign: textAlign ?? defaultTextStyle.textAlign,
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
text: new TextSpan(
style: effectiveTextStyle,
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