Unverified Commit 9f1e76e9 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Clean up baseline logic in material library. (#17922)

parent 587a1b77
...@@ -1855,7 +1855,7 @@ class _RenderChip extends RenderBox { ...@@ -1855,7 +1855,7 @@ class _RenderChip extends RenderBox {
@override @override
double computeDistanceToActualBaseline(TextBaseline baseline) { double computeDistanceToActualBaseline(TextBaseline baseline) {
// The baseline of this widget is the baseline of the label. // The baseline of this widget is the baseline of the label.
return label.computeDistanceToActualBaseline(baseline); return label.getDistanceToActualBaseline(baseline);
} }
Size _layoutLabel(double iconSizes, Size size) { Size _layoutLabel(double iconSizes, Size size) {
......
...@@ -537,10 +537,15 @@ class _RenderDecorationLayout { ...@@ -537,10 +537,15 @@ class _RenderDecorationLayout {
// The workhorse: layout and paint a _Decorator widget's _Decoration. // The workhorse: layout and paint a _Decorator widget's _Decoration.
class _RenderDecoration extends RenderBox { class _RenderDecoration extends RenderBox {
_RenderDecoration({ _RenderDecoration({
_Decoration decoration, @required _Decoration decoration,
TextDirection textDirection, @required TextDirection textDirection,
}) : _decoration = decoration, @required TextBaseline textBaseline,
_textDirection = textDirection; }) : assert(decoration != null),
assert(textDirection != null),
assert(textBaseline != null),
_decoration = decoration,
_textDirection = textDirection,
_textBaseline = textBaseline;
final Map<_DecorationSlot, RenderBox> slotToChild = <_DecorationSlot, RenderBox>{}; final Map<_DecorationSlot, RenderBox> slotToChild = <_DecorationSlot, RenderBox>{};
final Map<RenderBox, _DecorationSlot> childToSlot = <RenderBox, _DecorationSlot>{}; final Map<RenderBox, _DecorationSlot> childToSlot = <RenderBox, _DecorationSlot>{};
...@@ -654,6 +659,7 @@ class _RenderDecoration extends RenderBox { ...@@ -654,6 +659,7 @@ class _RenderDecoration extends RenderBox {
_Decoration get decoration => _decoration; _Decoration get decoration => _decoration;
_Decoration _decoration; _Decoration _decoration;
set decoration(_Decoration value) { set decoration(_Decoration value) {
assert(value != null);
if (_decoration == value) if (_decoration == value)
return; return;
_decoration = value; _decoration = value;
...@@ -663,12 +669,23 @@ class _RenderDecoration extends RenderBox { ...@@ -663,12 +669,23 @@ class _RenderDecoration extends RenderBox {
TextDirection get textDirection => _textDirection; TextDirection get textDirection => _textDirection;
TextDirection _textDirection; TextDirection _textDirection;
set textDirection(TextDirection value) { set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value) if (_textDirection == value)
return; return;
_textDirection = value; _textDirection = value;
markNeedsLayout(); markNeedsLayout();
} }
TextBaseline get textBaseline => _textBaseline;
TextBaseline _textBaseline;
set textBaseline(TextBaseline value) {
assert(value != null);
if (_textBaseline == value)
return;
_textBaseline = value;
markNeedsLayout();
}
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
super.attach(owner); super.attach(owner);
...@@ -748,7 +765,7 @@ class _RenderDecoration extends RenderBox { ...@@ -748,7 +765,7 @@ class _RenderDecoration extends RenderBox {
if (box == null) if (box == null)
return; return;
box.layout(boxConstraints, parentUsesSize: true); box.layout(boxConstraints, parentUsesSize: true);
final double baseline = box.getDistanceToBaseline(TextBaseline.alphabetic); final double baseline = box.getDistanceToBaseline(textBaseline);
assert(baseline != null && baseline >= 0.0); assert(baseline != null && baseline >= 0.0);
boxToBaseline[box] = baseline; boxToBaseline[box] = baseline;
aboveBaseline = math.max(baseline, aboveBaseline); aboveBaseline = math.max(baseline, aboveBaseline);
...@@ -913,7 +930,15 @@ class _RenderDecoration extends RenderBox { ...@@ -913,7 +930,15 @@ class _RenderDecoration extends RenderBox {
width: overallWidth - _boxSize(icon).width, width: overallWidth - _boxSize(icon).width,
); );
container.layout(containerConstraints, parentUsesSize: true); container.layout(containerConstraints, parentUsesSize: true);
final double x = textDirection == TextDirection.rtl ? 0.0 : _boxSize(icon).width; double x;
switch (textDirection) {
case TextDirection.rtl:
x = 0.0;
break;
case TextDirection.ltr:
x = _boxSize(icon).width;
break;
}
_boxParentData(container).offset = new Offset(x, 0.0); _boxParentData(container).offset = new Offset(x, 0.0);
} }
...@@ -938,7 +963,15 @@ class _RenderDecoration extends RenderBox { ...@@ -938,7 +963,15 @@ class _RenderDecoration extends RenderBox {
: layout.outlineBaseline; : layout.outlineBaseline;
if (icon != null) { if (icon != null) {
final double x = textDirection == TextDirection.rtl ? overallWidth - icon.size.width : 0.0; double x;
switch (textDirection) {
case TextDirection.rtl:
x = overallWidth - icon.size.width;
break;
case TextDirection.ltr:
x = 0.0;
break;
}
centerLayout(icon, x); centerLayout(icon, x);
} }
...@@ -1004,9 +1037,14 @@ class _RenderDecoration extends RenderBox { ...@@ -1004,9 +1037,14 @@ class _RenderDecoration extends RenderBox {
} }
if (label != null) { if (label != null) {
decoration.borderGap.start = textDirection == TextDirection.rtl switch (textDirection) {
? _boxParentData(label).offset.dx + label.size.width case TextDirection.rtl:
: _boxParentData(label).offset.dx; decoration.borderGap.start = _boxParentData(label).offset.dx + label.size.width;
break;
case TextDirection.ltr:
decoration.borderGap.start = _boxParentData(label).offset.dx;
break;
}
decoration.borderGap.extent = label.size.width * 0.75; decoration.borderGap.extent = label.size.width * 0.75;
} else { } else {
decoration.borderGap.start = null; decoration.borderGap.start = null;
...@@ -1039,9 +1077,15 @@ class _RenderDecoration extends RenderBox { ...@@ -1039,9 +1077,15 @@ class _RenderDecoration extends RenderBox {
final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline; final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline;
final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top; final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
final double scale = lerpDouble(1.0, 0.75, t); final double scale = lerpDouble(1.0, 0.75, t);
final double dx = textDirection == TextDirection.rtl double dx;
? labelOffset.dx + label.size.width * (1.0 - scale) // origin is on the right switch (textDirection) {
: labelOffset.dx; // origin on the left case TextDirection.rtl:
dx = labelOffset.dx + label.size.width * (1.0 - scale); // origin is on the right
break;
case TextDirection.ltr:
dx = labelOffset.dx; // origin on the left
break;
}
final double dy = lerpDouble(0.0, floatingY - labelOffset.dy, t); final double dy = lerpDouble(0.0, floatingY - labelOffset.dy, t);
_labelTransform = new Matrix4.identity() _labelTransform = new Matrix4.identity()
..translate(dx, labelOffset.dy + dy) ..translate(dx, labelOffset.dy + dy)
...@@ -1237,10 +1281,17 @@ class _RenderDecorationElement extends RenderObjectElement { ...@@ -1237,10 +1281,17 @@ class _RenderDecorationElement extends RenderObjectElement {
class _Decorator extends RenderObjectWidget { class _Decorator extends RenderObjectWidget {
const _Decorator({ const _Decorator({
Key key, Key key,
this.decoration, @required this.decoration,
}) : super(key: key); @required this.textDirection,
@required this.textBaseline,
}) : assert(decoration != null),
assert(textDirection != null),
assert(textBaseline != null),
super(key: key);
final _Decoration decoration; final _Decoration decoration;
final TextDirection textDirection;
final TextBaseline textBaseline;
@override @override
_RenderDecorationElement createElement() => new _RenderDecorationElement(this); _RenderDecorationElement createElement() => new _RenderDecorationElement(this);
...@@ -1249,7 +1300,8 @@ class _Decorator extends RenderObjectWidget { ...@@ -1249,7 +1300,8 @@ class _Decorator extends RenderObjectWidget {
_RenderDecoration createRenderObject(BuildContext context) { _RenderDecoration createRenderObject(BuildContext context) {
return new _RenderDecoration( return new _RenderDecoration(
decoration: decoration, decoration: decoration,
textDirection: Directionality.of(context), textDirection: textDirection,
textBaseline: textBaseline,
); );
} }
...@@ -1257,7 +1309,8 @@ class _Decorator extends RenderObjectWidget { ...@@ -1257,7 +1309,8 @@ class _Decorator extends RenderObjectWidget {
void updateRenderObject(BuildContext context, _RenderDecoration renderObject) { void updateRenderObject(BuildContext context, _RenderDecoration renderObject) {
renderObject renderObject
..decoration = decoration ..decoration = decoration
..textDirection = Directionality.of(context); ..textDirection = textDirection
..textBaseline = textBaseline;
} }
} }
...@@ -1310,6 +1363,9 @@ class InputDecorator extends StatefulWidget { ...@@ -1310,6 +1363,9 @@ class InputDecorator extends StatefulWidget {
/// ///
/// If null, `baseStyle` defaults to the `subhead` style from the /// If null, `baseStyle` defaults to the `subhead` style from the
/// current [Theme], see [ThemeData.textTheme]. /// current [Theme], see [ThemeData.textTheme].
///
/// The [TextStyle.textBaseline] of the [baseStyle] is used to determine
/// the baseline used for text alignment.
final TextStyle baseStyle; final TextStyle baseStyle;
/// How the text in the decoration should be aligned horizontally. /// How the text in the decoration should be aligned horizontally.
...@@ -1539,6 +1595,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1539,6 +1595,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final TextStyle inlineStyle = _getInlineStyle(themeData); final TextStyle inlineStyle = _getInlineStyle(themeData);
final TextBaseline textBaseline = inlineStyle.textBaseline;
final TextStyle hintStyle = inlineStyle.merge(decoration.hintStyle); final TextStyle hintStyle = inlineStyle.merge(decoration.hintStyle);
final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity( final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity(
...@@ -1713,6 +1770,8 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1713,6 +1770,8 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
counter: counter, counter: counter,
container: container, container: container,
), ),
textDirection: textDirection,
textBaseline: textBaseline,
); );
} }
} }
......
...@@ -248,7 +248,7 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> { ...@@ -248,7 +248,7 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
duration: kThemeChangeDuration, duration: kThemeChangeDuration,
child: new Baseline( child: new Baseline(
baseline: widget.height - _kBaselineOffsetFromBottom, baseline: widget.height - _kBaselineOffsetFromBottom,
baselineType: TextBaseline.alphabetic, baselineType: style.textBaseline,
child: buildChild(), child: buildChild(),
) )
); );
......
...@@ -1599,9 +1599,12 @@ abstract class RenderBox extends RenderObject { ...@@ -1599,9 +1599,12 @@ abstract class RenderBox extends RenderObject {
/// Only call this function after calling [layout] on this box. You /// Only call this function after calling [layout] on this box. You
/// are only allowed to call this from the parent of this box during /// are only allowed to call this from the parent of this box during
/// that parent's [performLayout] or [paint] functions. /// that parent's [performLayout] or [paint] functions.
///
/// When implementing a [RenderBox] subclass, to override the baseline
/// computation, override [computeDistanceToActualBaseline].
double getDistanceToBaseline(TextBaseline baseline, { bool onlyReal: false }) { double getDistanceToBaseline(TextBaseline baseline, { bool onlyReal: false }) {
assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
assert(!debugNeedsLayout); assert(!debugNeedsLayout);
assert(!_debugDoingBaseline);
assert(() { assert(() {
final RenderObject parent = this.parent; final RenderObject parent = this.parent;
if (owner.debugDoingLayout) if (owner.debugDoingLayout)
...@@ -1628,7 +1631,7 @@ abstract class RenderBox extends RenderObject { ...@@ -1628,7 +1631,7 @@ abstract class RenderBox extends RenderObject {
@protected @protected
@mustCallSuper @mustCallSuper
double getDistanceToActualBaseline(TextBaseline baseline) { double getDistanceToActualBaseline(TextBaseline baseline) {
assert(_debugDoingBaseline); assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
_cachedBaselines ??= <TextBaseline, double>{}; _cachedBaselines ??= <TextBaseline, double>{};
_cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline)); _cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
return _cachedBaselines[baseline]; return _cachedBaselines[baseline];
...@@ -1638,17 +1641,29 @@ abstract class RenderBox extends RenderObject { ...@@ -1638,17 +1641,29 @@ abstract class RenderBox extends RenderObject {
/// the y-coordinate of the first given baseline in the box's contents, if /// the y-coordinate of the first given baseline in the box's contents, if
/// any, or null otherwise. /// any, or null otherwise.
/// ///
/// Do not call this function directly. Instead, call [getDistanceToBaseline] /// Do not call this function directly. If you need to know the baseline of a
/// if you need to know the baseline of a child from an invocation of /// child from an invocation of [performLayout] or [paint], call
/// [performLayout] or [paint] and call [getDistanceToActualBaseline] if you /// [getDistanceToBaseline].
/// are implementing [computeDistanceToActualBaseline] and need to defer to a
/// child.
/// ///
/// Subclasses should override this method to supply the distances to their /// Subclasses should override this method to supply the distances to their
/// baselines. /// baselines. When implementing this method, there are generally three
/// strategies:
///
/// * For classes that use the [ContainerRenderObjectMixin] child model,
/// consider mixing in the [RenderBoxContainerDefaultsMixin] class and
/// using
/// [RenderBoxContainerDefaultsMixin.defaultComputeDistanceToFirstActualBaseline].
///
/// * For classes that define a particular baseline themselves, return that
/// value directly.
///
/// * For classes that have a child to which they wish to defer the
/// computation, call [getDistanceToActualBaseline] on the child (not
/// [computeDistanceToActualBaseline], the internal implementation, and not
/// [getDistanceToBaseline], the public entry point for this API).
@protected @protected
double computeDistanceToActualBaseline(TextBaseline baseline) { double computeDistanceToActualBaseline(TextBaseline baseline) {
assert(_debugDoingBaseline); assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
return null; return null;
} }
......
...@@ -1124,7 +1124,7 @@ class RenderBaseline extends RenderShiftedBox { ...@@ -1124,7 +1124,7 @@ class RenderBaseline extends RenderShiftedBox {
RenderBaseline({ RenderBaseline({
RenderBox child, RenderBox child,
@required double baseline, @required double baseline,
@required TextBaseline baselineType @required TextBaseline baselineType,
}) : assert(baseline != null), }) : assert(baseline != null),
assert(baselineType != null), assert(baselineType != null),
_baseline = baseline, _baseline = baseline,
......
...@@ -101,7 +101,7 @@ class Table extends RenderObjectWidget { ...@@ -101,7 +101,7 @@ class Table extends RenderObjectWidget {
this.textDirection, this.textDirection,
this.border, this.border,
this.defaultVerticalAlignment: TableCellVerticalAlignment.top, this.defaultVerticalAlignment: TableCellVerticalAlignment.top,
this.textBaseline this.textBaseline,
}) : assert(children != null), }) : assert(children != null),
assert(defaultColumnWidth != null), assert(defaultColumnWidth != null),
assert(defaultVerticalAlignment != null), assert(defaultVerticalAlignment != null),
...@@ -213,7 +213,7 @@ class Table extends RenderObjectWidget { ...@@ -213,7 +213,7 @@ class Table extends RenderObjectWidget {
rowDecorations: _rowDecorations, rowDecorations: _rowDecorations,
configuration: createLocalImageConfiguration(context), configuration: createLocalImageConfiguration(context),
defaultVerticalAlignment: defaultVerticalAlignment, defaultVerticalAlignment: defaultVerticalAlignment,
textBaseline: textBaseline textBaseline: textBaseline,
); );
} }
......
...@@ -65,18 +65,11 @@ Widget _wrapForChip({ ...@@ -65,18 +65,11 @@ Widget _wrapForChip({
double textScaleFactor: 1.0, double textScaleFactor: 1.0,
}) { }) {
return new MaterialApp( return new MaterialApp(
home: new Localizations( home: new Directionality(
locale: const Locale('en', 'US'), textDirection: textDirection,
delegates: const <LocalizationsDelegate<dynamic>>[ child: new MediaQuery(
DefaultWidgetsLocalizations.delegate, data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
DefaultMaterialLocalizations.delegate, child: new Material(child: child),
],
child: new Directionality(
textDirection: textDirection,
child: new MediaQuery(
data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
child: new Material(child: child),
),
), ),
), ),
); );
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart';
void main() { void main() {
testWidgets('Baseline - control test', (WidgetTester tester) async { testWidgets('Baseline - control test', (WidgetTester tester) async {
...@@ -41,4 +41,108 @@ void main() { ...@@ -41,4 +41,108 @@ void main() {
expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size, expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size,
within<Size>(from: const Size(100.0, 200.0), distance: 0.001)); within<Size>(from: const Size(100.0, 200.0), distance: 0.001));
}); });
testWidgets('Chip caches baseline', (WidgetTester tester) async {
int calls = 0;
await tester.pumpWidget(
new MaterialApp(
home: new Material(
child: new Baseline(
baseline: 100.0,
baselineType: TextBaseline.alphabetic,
child: new Chip(
label: new BaselineDetector(() {
calls += 1;
}),
),
),
),
),
);
expect(calls, 1);
await tester.pump();
expect(calls, 1);
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
await tester.pump();
expect(calls, 2);
});
testWidgets('ListTile caches baseline', (WidgetTester tester) async {
int calls = 0;
await tester.pumpWidget(
new MaterialApp(
home: new Material(
child: new Baseline(
baseline: 100.0,
baselineType: TextBaseline.alphabetic,
child: new ListTile(
title: new BaselineDetector(() {
calls += 1;
}),
),
),
),
),
);
expect(calls, 1);
await tester.pump();
expect(calls, 1);
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
await tester.pump();
expect(calls, 2);
});
}
class BaselineDetector extends LeafRenderObjectWidget {
const BaselineDetector(this.callback);
final VoidCallback callback;
@override
RenderBaselineDetector createRenderObject(BuildContext context) => new RenderBaselineDetector(callback);
@override
void updateRenderObject(BuildContext context, RenderBaselineDetector renderObject) {
renderObject.callback = callback;
}
}
class RenderBaselineDetector extends RenderBox {
RenderBaselineDetector(this.callback);
VoidCallback callback;
@override
bool get sizedByParent => true;
@override
double computeMinIntrinsicWidth(double height) => 0.0;
@override
double computeMaxIntrinsicWidth(double height) => 0.0;
@override
double computeMinIntrinsicHeight(double width) => 0.0;
@override
double computeMaxIntrinsicHeight(double width) => 0.0;
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
if (callback != null)
callback();
return 0.0;
}
void dirty() {
markNeedsLayout();
}
@override
void performResize() {
size = constraints.smallest;
}
@override
void paint(PaintingContext context, Offset offset) { }
} }
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