Commit 024cc2ca authored by Adam Barth's avatar Adam Barth

Merge pull request #1485 from abarth/align_overflow_box

Add an alignment property to OverflowBox
parents 4dc19e3f fa03df2d
......@@ -7,146 +7,6 @@ import 'object.dart';
export 'package:flutter/src/painting/box_painter.dart';
/// A render object that imposes different constraints on its child than it gets
/// from its parent, possibly allowing the child to overflow the parent.
///
/// A render overflow box proxies most functions in the render box protocol to
/// its child, except that when laying out its child, it passes constraints
/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
/// just passing the parent's constraints in. Specifically, it overrides any of
/// the equivalent fields on the constraints given by the parent with the
/// constraints given by these fields for each such field that is not null. It
/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
/// ignoring the child's dimensions.
///
/// For example, if you wanted a box to always render 50 pixels high, regardless
/// of where it was rendered, you would wrap it in a RenderOverflow with
/// minHeight and maxHeight set to 50.0. Generally speaking, to avoid confusing
/// behaviour around hit testing, a RenderOverflowBox should usually be wrapped
/// in a RenderClipRect.
///
/// The child is positioned at the top left of the box. To position a smaller
/// child inside a larger parent, use [RenderPositionedBox] and
/// [RenderConstrainedBox] rather than RenderOverflowBox.
class RenderOverflowBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
RenderOverflowBox({
RenderBox child,
double minWidth,
double maxWidth,
double minHeight,
double maxHeight
}) : _minWidth = minWidth, _maxWidth = maxWidth, _minHeight = minHeight, _maxHeight = maxHeight {
this.child = child;
}
/// The minimum width constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get minWidth => _minWidth;
double _minWidth;
void set minWidth (double value) {
if (_minWidth == value)
return;
_minWidth = value;
markNeedsLayout();
}
/// The maximum width constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get maxWidth => _maxWidth;
double _maxWidth;
void set maxWidth (double value) {
if (_maxWidth == value)
return;
_maxWidth = value;
markNeedsLayout();
}
/// The minimum height constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get minHeight => _minHeight;
double _minHeight;
void set minHeight (double value) {
if (_minHeight == value)
return;
_minHeight = value;
markNeedsLayout();
}
/// The maximum height constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get maxHeight => _maxHeight;
double _maxHeight;
void set maxHeight (double value) {
if (_maxHeight == value)
return;
_maxHeight = value;
markNeedsLayout();
}
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
return new BoxConstraints(
minWidth: _minWidth ?? constraints.minWidth,
maxWidth: _maxWidth ?? constraints.maxWidth,
minHeight: _minHeight ?? constraints.minHeight,
maxHeight: _maxHeight ?? constraints.maxHeight
);
}
double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainWidth();
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainWidth();
}
double getMinIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainHeight();
}
double getMaxIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.constrainHeight();
}
double computeDistanceToActualBaseline(TextBaseline baseline) {
if (child != null)
return child.getDistanceToActualBaseline(baseline);
return super.computeDistanceToActualBaseline(baseline);
}
bool get sizedByParent => true;
void performResize() {
size = constraints.biggest;
}
void performLayout() {
if (child != null)
child.layout(_getInnerConstraints(constraints));
}
bool hitTestChildren(HitTestResult result, { Point position }) {
return child?.hitTest(result, position: position) ?? false;
}
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);
}
void debugDescribeSettings(List<String> settings) {
super.debugDescribeSettings(settings);
settings.add('minWidth: ${minWidth ?? "use parent minWidth constraint"}');
settings.add('maxWidth: ${maxWidth ?? "use parent maxWidth constraint"}');
settings.add('minHeight: ${minHeight ?? "use parent minHeight constraint"}');
settings.add('maxHeight: ${maxHeight ?? "use parent maxHeight constraint"}');
}
}
/// A render box that's a specific size but passes its original constraints through to its child, which will probably overflow
class RenderSizedOverflowBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
RenderSizedOverflowBox({
......
......@@ -356,6 +356,158 @@ class RenderPositionedBox extends RenderShiftedBox {
}
}
/// A render object that imposes different constraints on its child than it gets
/// from its parent, possibly allowing the child to overflow the parent.
///
/// A render overflow box proxies most functions in the render box protocol to
/// its child, except that when laying out its child, it passes constraints
/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
/// just passing the parent's constraints in. Specifically, it overrides any of
/// the equivalent fields on the constraints given by the parent with the
/// constraints given by these fields for each such field that is not null. It
/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
/// ignoring the child's dimensions.
///
/// For example, if you wanted a box to always render 50 pixels high, regardless
/// of where it was rendered, you would wrap it in a RenderOverflow with
/// minHeight and maxHeight set to 50.0. Generally speaking, to avoid confusing
/// behaviour around hit testing, a RenderOverflowBox should usually be wrapped
/// in a RenderClipRect.
///
/// The child is positioned at the top left of the box. To position a smaller
/// child inside a larger parent, use [RenderPositionedBox] and
/// [RenderConstrainedBox] rather than RenderOverflowBox.
class RenderOverflowBox extends RenderShiftedBox {
RenderOverflowBox({
RenderBox child,
double minWidth,
double maxWidth,
double minHeight,
double maxHeight,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5)
}) : _minWidth = minWidth,
_maxWidth = maxWidth,
_minHeight = minHeight,
_maxHeight = maxHeight,
_alignment = alignment,
super(child);
/// The minimum width constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get minWidth => _minWidth;
double _minWidth;
void set minWidth (double value) {
if (_minWidth == value)
return;
_minWidth = value;
markNeedsLayout();
}
/// The maximum width constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get maxWidth => _maxWidth;
double _maxWidth;
void set maxWidth (double value) {
if (_maxWidth == value)
return;
_maxWidth = value;
markNeedsLayout();
}
/// The minimum height constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get minHeight => _minHeight;
double _minHeight;
void set minHeight (double value) {
if (_minHeight == value)
return;
_minHeight = value;
markNeedsLayout();
}
/// The maximum height constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
double get maxHeight => _maxHeight;
double _maxHeight;
void set maxHeight (double value) {
if (_maxHeight == value)
return;
_maxHeight = value;
markNeedsLayout();
}
/// How to align the child.
///
/// The x and y values of the alignment control the horizontal and vertical
/// alignment, respectively. An x value of 0.0 means that the left edge of
/// the child is aligned with the left edge of the parent whereas an x value
/// of 1.0 means that the right edge of the child is aligned with the right
/// edge of the parent. Other values interpolate (and extrapolate) linearly.
/// For example, a value of 0.5 means that the center of the child is aligned
/// with the center of the parent.
FractionalOffset get alignment => _alignment;
FractionalOffset _alignment;
void set alignment (FractionalOffset newAlignment) {
assert(newAlignment != null && newAlignment.dx != null && newAlignment.dy != null);
if (_alignment == newAlignment)
return;
_alignment = newAlignment;
markNeedsLayout();
}
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
return new BoxConstraints(
minWidth: _minWidth ?? constraints.minWidth,
maxWidth: _maxWidth ?? constraints.maxWidth,
minHeight: _minHeight ?? constraints.minHeight,
maxHeight: _maxHeight ?? constraints.maxHeight
);
}
double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.minWidth;
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.minWidth;
}
double getMinIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.minHeight;
}
double getMaxIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized);
return constraints.minHeight;
}
bool get sizedByParent => true;
void performResize() {
size = constraints.biggest;
}
void performLayout() {
if (child != null) {
child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
final BoxParentData childParentData = child.parentData;
childParentData.offset = _alignment.alongOffset(size - child.size);
}
}
void debugDescribeSettings(List<String> settings) {
super.debugDescribeSettings(settings);
settings.add('minWidth: ${minWidth ?? "use parent minWidth constraint"}');
settings.add('maxWidth: ${maxWidth ?? "use parent maxWidth constraint"}');
settings.add('minHeight: ${minHeight ?? "use parent minHeight constraint"}');
settings.add('maxHeight: ${maxHeight ?? "use parent maxHeight constraint"}');
settings.add('alignment: $alignment');
}
}
/// A delegate for computing the layout of a render object with a single child.
class OneChildLayoutDelegate {
/// Returns the size of this object given the incoming constraints.
......
......@@ -597,8 +597,15 @@ class FractionallySizedBox extends OneChildRenderObjectWidget {
///
/// See [RenderOverflowBox] for details.
class OverflowBox extends OneChildRenderObjectWidget {
OverflowBox({ Key key, this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, Widget child })
: super(key: key, child: child);
OverflowBox({
Key key,
this.minWidth,
this.maxWidth,
this.minHeight,
this.maxHeight,
this.alignment: const FractionalOffset(0.5, 0.5),
Widget child
}) : super(key: key, child: child);
/// The minimum width constraint to give the child. Set this to null (the
/// default) to use the constraint from the parent instead.
......@@ -616,18 +623,32 @@ class OverflowBox extends OneChildRenderObjectWidget {
/// default) to use the constraint from the parent instead.
final double maxHeight;
/// How to align the child.
///
/// The x and y values of the alignment control the horizontal and vertical
/// alignment, respectively. An x value of 0.0 means that the left edge of
/// the child is aligned with the left edge of the parent whereas an x value
/// of 1.0 means that the right edge of the child is aligned with the right
/// edge of the parent. Other values interpolate (and extrapolate) linearly.
/// For example, a value of 0.5 means that the center of the child is aligned
/// with the center of the parent.
final FractionalOffset alignment;
RenderOverflowBox createRenderObject() => new RenderOverflowBox(
minWidth: minWidth,
maxWidth: maxWidth,
minHeight: minHeight,
maxHeight: maxHeight
maxHeight: maxHeight,
alignment: alignment
);
void updateRenderObject(RenderOverflowBox renderObject, OverflowBox oldWidget) {
renderObject.minWidth = minWidth;
renderObject.maxWidth = maxWidth;
renderObject.minHeight = minHeight;
renderObject.maxHeight = maxHeight;
renderObject
..minWidth = minWidth
..maxWidth = maxWidth
..minHeight = minHeight
..maxHeight = maxHeight
..alignment = alignment;
}
}
......@@ -2047,7 +2068,7 @@ class Semantics extends OneChildRenderObjectWidget {
/// If 'container' is true, this Widget will introduce a new node in
/// the semantics tree. Otherwise, the semantics will be merged with
/// the semantics of any ancestors.
///
///
/// The 'container' flag is implicitly set to true on the immediate
/// semantics-providing descendants of a node where multiple
/// children have semantics or have descendants providing semantics.
......@@ -2080,7 +2101,7 @@ class Semantics extends OneChildRenderObjectWidget {
super.debugFillDescription(description);
description.add('container: $container');
if (checked != null);
description.add('checked: $checked');
description.add('checked: $checked');
if (label != null);
description.add('label: "$label"');
}
......
......@@ -30,7 +30,8 @@ void main() {
child: new RenderCustomPaint(
child: child = new RenderOverflowBox(
child: text = new RenderParagraph(new PlainTextSpan('Hello World')),
maxHeight: height1 / 2.0
maxHeight: height1 / 2.0,
alignment: const FractionalOffset(0.0, 0.0)
),
painter: new TestCallbackPainter(
onPaint: () {
......
......@@ -17,6 +17,7 @@ void main() {
maxWidth: 100.0,
minHeight: 0.0,
maxHeight: 100.0,
alignment: const FractionalOffset(0.0, 0.0),
child: new Center(
child: new FractionallySizedBox(
width: 0.5,
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:test/test.dart';
void main() {
test('OverflowBox control test', () {
testWidgets((WidgetTester tester) {
GlobalKey inner = new GlobalKey();
tester.pumpWidget(new Align(
alignment: const FractionalOffset(1.0, 1.0),
child: new SizedBox(
width: 10.0,
height: 20.0,
child: new OverflowBox(
minWidth: 0.0,
maxWidth: 100.0,
minHeight: 0.0,
maxHeight: 50.0,
child: new Container(
key: inner
)
)
)
));
RenderBox box = inner.currentContext.findRenderObject();
expect(box.localToGlobal(Point.origin), equals(const Point(745.0, 565.0)));
expect(box.size, equals(const Size(100.0, 50.0)));
});
});
}
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