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