Commit 1e207c01 authored by Adam Barth's avatar Adam Barth

Remove RenderBlockViewport

Previously this was used by MixedViewport, but now we don't need it because

LazyBlockViewport has replaced MixedViewport.



I've also taken this opportunity to modernize RenderBlock.
parent f8dca576
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:vector_math/vector_math_64.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
import 'viewport.dart'; import 'viewport.dart';
...@@ -25,17 +23,15 @@ typedef double _Constrainer(double value); ...@@ -25,17 +23,15 @@ typedef double _Constrainer(double value);
/// children. Because blocks expand in the main axis, blocks must be given /// children. Because blocks expand in the main axis, blocks must be given
/// unlimited space in the main axis, typically by being contained in a /// unlimited space in the main axis, typically by being contained in a
/// viewport with a scrolling direction that matches the block's main axis. /// viewport with a scrolling direction that matches the block's main axis.
abstract class RenderBlockBase extends RenderBox class RenderBlock extends RenderBox
with ContainerRenderObjectMixin<RenderBox, BlockParentData>, with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData>
implements HasMainAxis { implements HasMainAxis {
RenderBlockBase({ RenderBlock({
List<RenderBox> children, List<RenderBox> children,
Axis mainAxis: Axis.vertical, Axis mainAxis: Axis.vertical
double itemExtent, }) : _mainAxis = mainAxis {
double minExtent: 0.0
}) : _mainAxis = mainAxis, _itemExtent = itemExtent, _minExtent = minExtent {
addAll(children); addAll(children);
} }
...@@ -56,64 +52,78 @@ abstract class RenderBlockBase extends RenderBox ...@@ -56,64 +52,78 @@ abstract class RenderBlockBase extends RenderBox
} }
} }
/// If non-null, forces children to be exactly this large in the main axis.
double get itemExtent => _itemExtent;
double _itemExtent;
void set itemExtent(double value) {
if (value != _itemExtent) {
_itemExtent = value;
markNeedsLayout();
}
}
/// Forces the block to be at least this large in the main-axis.
double get minExtent => _minExtent;
double _minExtent;
void set minExtent(double value) {
if (value != _minExtent) {
_minExtent = value;
markNeedsLayout();
}
}
/// Whether the main axis is vertical.
bool get isVertical => _mainAxis == Axis.vertical;
BoxConstraints _getInnerConstraints(BoxConstraints constraints) { BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
switch (_mainAxis) { switch (_mainAxis) {
case Axis.horizontal: case Axis.horizontal:
return new BoxConstraints.tightFor(height: constraints.maxHeight, width: itemExtent); return new BoxConstraints.tightFor(height: constraints.maxHeight);
case Axis.vertical: case Axis.vertical:
return new BoxConstraints.tightFor(width: constraints.maxWidth, height: itemExtent); return new BoxConstraints.tightFor(width: constraints.maxWidth);
} }
} }
double get _mainAxisExtent { double get _mainAxisExtent {
RenderBox child = lastChild; RenderBox child = lastChild;
if (child == null) if (child == null)
return minExtent; return 0.0;
BoxParentData parentData = child.parentData; BoxParentData parentData = child.parentData;
return isVertical ? switch (mainAxis) {
math.max(minExtent, parentData.offset.dy + child.size.height) : case Axis.horizontal:
math.max(minExtent, parentData.offset.dx + child.size.width); return parentData.offset.dx + child.size.width;
case Axis.vertical:
return parentData.offset.dy + child.size.height;
}
} }
@override @override
void performLayout() { void performLayout() {
assert(() {
switch (mainAxis) {
case Axis.horizontal:
if (constraints.maxWidth.isInfinite)
return true;
break;
case Axis.vertical:
if (constraints.maxHeight.isInfinite)
return true;
break;
}
throw new FlutterError(
'RenderBlock must have unlimited space along its main axis.\n'
'RenderBlock does not clip or resize its children, so it must be '
'placed in a parent that does not constrain the block\'s main '
'axis. You probably want to put the RenderBlock inside a '
'RenderViewport with a matching main axis.'
);
return false;
});
BoxConstraints innerConstraints = _getInnerConstraints(constraints); BoxConstraints innerConstraints = _getInnerConstraints(constraints);
double position = 0.0; double position = 0.0;
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
child.layout(innerConstraints, parentUsesSize: true); child.layout(innerConstraints, parentUsesSize: true);
final BlockParentData childParentData = child.parentData; final BlockParentData childParentData = child.parentData;
childParentData.offset = isVertical ? new Offset(0.0, position) : new Offset(position, 0.0); switch (mainAxis) {
position += isVertical ? child.size.height : child.size.width; case Axis.horizontal:
childParentData.offset = new Offset(position, 0.0);
position += child.size.width;
break;
case Axis.vertical:
childParentData.offset = new Offset(0.0, position);
position += child.size.height;
break;
}
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
child = childParentData.nextSibling; child = childParentData.nextSibling;
} }
size = isVertical ? switch (mainAxis) {
constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent)) : case Axis.horizontal:
constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight)); size = constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight));
break;
case Axis.vertical:
size = constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent));
break;
}
assert(!size.isInfinite); assert(!size.isInfinite);
} }
...@@ -122,21 +132,18 @@ abstract class RenderBlockBase extends RenderBox ...@@ -122,21 +132,18 @@ abstract class RenderBlockBase extends RenderBox
super.debugFillDescription(description); super.debugFillDescription(description);
description.add('mainAxis: $mainAxis'); description.add('mainAxis: $mainAxis');
} }
}
/// A block layout with a concrete set of children.
class RenderBlock extends RenderBlockBase {
RenderBlock({
List<RenderBox> children,
Axis mainAxis: Axis.vertical,
double itemExtent,
double minExtent: 0.0
}) : super(children: children, mainAxis: mainAxis, itemExtent: itemExtent, minExtent: minExtent);
double _getIntrinsicCrossAxis(BoxConstraints constraints, _ChildSizingFunction childSize, _Constrainer constrainer) { double _getIntrinsicCrossAxis(BoxConstraints constraints, _ChildSizingFunction childSize, _Constrainer constrainer) {
double extent = 0.0; double extent = 0.0;
BoxConstraints innerConstraints = isVertical ? constraints.widthConstraints() : constraints.heightConstraints(); BoxConstraints innerConstraints;
switch (mainAxis) {
case Axis.horizontal:
innerConstraints = constraints.heightConstraints();
break;
case Axis.vertical:
innerConstraints = constraints.widthConstraints();
break;
}
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
extent = math.max(extent, childSize(child, innerConstraints)); extent = math.max(extent, childSize(child, innerConstraints));
...@@ -151,64 +158,78 @@ class RenderBlock extends RenderBlockBase { ...@@ -151,64 +158,78 @@ class RenderBlock extends RenderBlockBase {
BoxConstraints innerConstraints = _getInnerConstraints(constraints); BoxConstraints innerConstraints = _getInnerConstraints(constraints);
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
double childExtent = isVertical ? switch (mainAxis) {
child.getMinIntrinsicHeight(innerConstraints) : case Axis.horizontal:
child.getMinIntrinsicWidth(innerConstraints); extent += child.getMinIntrinsicWidth(innerConstraints);
extent += childExtent; break;
case Axis.vertical:
extent += child.getMinIntrinsicHeight(innerConstraints);
break;
}
final BlockParentData childParentData = child.parentData; final BlockParentData childParentData = child.parentData;
child = childParentData.nextSibling; child = childParentData.nextSibling;
} }
return constrainer(math.max(extent, minExtent)); return constrainer(extent);
} }
@override @override
double getMinIntrinsicWidth(BoxConstraints constraints) { double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized); assert(constraints.debugAssertIsNormalized);
if (isVertical) { switch (mainAxis) {
return _getIntrinsicCrossAxis( case Axis.horizontal:
constraints, return _getIntrinsicMainAxis(constraints, constraints.constrainWidth);
(RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints), case Axis.vertical:
constraints.constrainWidth return _getIntrinsicCrossAxis(
); constraints,
(RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints),
constraints.constrainWidth
);
} }
return _getIntrinsicMainAxis(constraints, constraints.constrainWidth);
} }
@override @override
double getMaxIntrinsicWidth(BoxConstraints constraints) { double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized); assert(constraints.debugAssertIsNormalized);
if (isVertical) { switch (mainAxis) {
return _getIntrinsicCrossAxis( case Axis.horizontal:
constraints, return _getIntrinsicMainAxis(constraints, constraints.constrainWidth);
(RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints), case Axis.vertical:
constraints.constrainWidth return _getIntrinsicCrossAxis(
); constraints,
(RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints),
constraints.constrainWidth
);
} }
return _getIntrinsicMainAxis(constraints, constraints.constrainWidth);
} }
@override @override
double getMinIntrinsicHeight(BoxConstraints constraints) { double getMinIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized); assert(constraints.debugAssertIsNormalized);
if (isVertical) switch (mainAxis) {
return _getIntrinsicMainAxis(constraints, constraints.constrainHeight); case Axis.horizontal:
return _getIntrinsicCrossAxis( return _getIntrinsicCrossAxis(
constraints, constraints,
(RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints), (RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints),
constraints.constrainHeight constraints.constrainHeight
); );
case Axis.vertical:
return _getIntrinsicMainAxis(constraints, constraints.constrainHeight);
}
} }
@override @override
double getMaxIntrinsicHeight(BoxConstraints constraints) { double getMaxIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized); assert(constraints.debugAssertIsNormalized);
if (isVertical) switch (mainAxis) {
return _getIntrinsicMainAxis(constraints, constraints.constrainHeight); case Axis.horizontal:
return _getIntrinsicCrossAxis( return _getIntrinsicCrossAxis(
constraints, constraints,
(RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints), (RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints),
constraints.constrainHeight constraints.constrainHeight
); );
case Axis.vertical:
return _getIntrinsicMainAxis(constraints, constraints.constrainHeight);
}
} }
@override @override
...@@ -216,14 +237,6 @@ class RenderBlock extends RenderBlockBase { ...@@ -216,14 +237,6 @@ class RenderBlock extends RenderBlockBase {
return defaultComputeDistanceToFirstActualBaseline(baseline); return defaultComputeDistanceToFirstActualBaseline(baseline);
} }
@override
void performLayout() {
assert((isVertical ? constraints.maxHeight >= double.INFINITY : constraints.maxWidth >= double.INFINITY) &&
'RenderBlock does not clip or resize its children, so it must be placed in a parent that does not constrain '
'the block\'s main direction. You probably want to put the RenderBlock inside a RenderViewport.' is String);
super.performLayout();
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset); defaultPaint(context, offset);
...@@ -235,243 +248,3 @@ class RenderBlock extends RenderBlockBase { ...@@ -235,243 +248,3 @@ class RenderBlock extends RenderBlockBase {
} }
} }
/// A block layout whose children depend on its layout.
///
/// This class invokes a callbacks for layout and intrinsic dimensions. The main
/// [callback] (constructor argument and property) is expected to modify the
/// element's child list. The regular block layout algorithm is then applied to
/// the children. The intrinsic dimension callbacks are called to determine
/// intrinsic dimensions; if no value can be returned, they should not be set
/// or, if set, should return null.
class RenderBlockViewport extends RenderBlockBase {
RenderBlockViewport({
LayoutCallback callback,
VoidCallback postLayoutCallback,
ExtentCallback totalExtentCallback,
ExtentCallback maxCrossAxisDimensionCallback,
ExtentCallback minCrossAxisDimensionCallback,
RenderObjectPainter overlayPainter,
Axis mainAxis: Axis.vertical,
double itemExtent,
double minExtent: 0.0,
double startOffset: 0.0,
List<RenderBox> children
}) : _callback = callback,
_totalExtentCallback = totalExtentCallback,
_maxCrossAxisExtentCallback = maxCrossAxisDimensionCallback,
_minCrossAxisExtentCallback = minCrossAxisDimensionCallback,
_overlayPainter = overlayPainter,
_startOffset = startOffset,
super(children: children, mainAxis: mainAxis, itemExtent: itemExtent, minExtent: minExtent);
bool _inCallback = false;
@override
bool get isRepaintBoundary => true;
/// Called during [layout] to determine the block's children.
///
/// Typically the callback will mutate the child list appropriately, for
/// example so the child list contains only visible children.
LayoutCallback get callback => _callback;
LayoutCallback _callback;
void set callback(LayoutCallback value) {
assert(!_inCallback);
if (value == _callback)
return;
_callback = value;
markNeedsLayout();
}
/// Called during after [layout].
///
/// This callback cannot mutate the tree. To mutate the tree during
/// layout, use [callback].
VoidCallback postLayoutCallback;
/// Returns the total main-axis extent of all the children that could be included by [callback] in one go.
ExtentCallback get totalExtentCallback => _totalExtentCallback;
ExtentCallback _totalExtentCallback;
void set totalExtentCallback(ExtentCallback value) {
assert(!_inCallback);
if (value == _totalExtentCallback)
return;
_totalExtentCallback = value;
markNeedsLayout();
}
/// Returns the minimum cross-axis extent across all the children that could be included by [callback] in one go.
ExtentCallback get minCrossAxisExtentCallback => _minCrossAxisExtentCallback;
ExtentCallback _minCrossAxisExtentCallback;
void set minCrossAxisExtentCallback(ExtentCallback value) {
assert(!_inCallback);
if (value == _minCrossAxisExtentCallback)
return;
_minCrossAxisExtentCallback = value;
markNeedsLayout();
}
/// Returns the maximum cross-axis extent across all the children that could be included by [callback] in one go.
ExtentCallback get maxCrossAxisExtentCallback => _maxCrossAxisExtentCallback;
ExtentCallback _maxCrossAxisExtentCallback;
void set maxCrossAxisExtentCallback(ExtentCallback value) {
assert(!_inCallback);
if (value == _maxCrossAxisExtentCallback)
return;
_maxCrossAxisExtentCallback = value;
markNeedsLayout();
}
RenderObjectPainter get overlayPainter => _overlayPainter;
RenderObjectPainter _overlayPainter;
void set overlayPainter(RenderObjectPainter value) {
if (_overlayPainter == value)
return;
if (attached)
_overlayPainter?.detach();
_overlayPainter = value;
if (attached)
_overlayPainter?.attach(this);
markNeedsPaint();
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_overlayPainter?.attach(this);
}
@override
void detach() {
super.detach();
_overlayPainter?.detach();
}
/// The offset at which to paint the first child.
///
/// Note: you can modify this property from within [callback], if necessary.
double get startOffset => _startOffset;
double _startOffset;
void set startOffset(double value) {
if (value != _startOffset) {
_startOffset = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
}
double _getIntrinsicDimension(BoxConstraints constraints, ExtentCallback intrinsicCallback, _Constrainer constrainer) {
assert(!_inCallback);
double result;
if (intrinsicCallback == null) {
assert(() {
if (!RenderObject.debugCheckingIntrinsics)
throw new UnsupportedError('$runtimeType does not support returning intrinsic dimensions if the relevant callbacks have not been specified.');
return true;
});
return constrainer(0.0);
}
try {
_inCallback = true;
result = intrinsicCallback(constraints);
result = constrainer(result ?? 0.0);
} finally {
_inCallback = false;
}
return result;
}
@override
double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
if (isVertical)
return _getIntrinsicDimension(constraints, minCrossAxisExtentCallback, constraints.constrainWidth);
return constraints.constrainWidth(minExtent);
}
@override
double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
if (isVertical)
return _getIntrinsicDimension(constraints, maxCrossAxisExtentCallback, constraints.constrainWidth);
return _getIntrinsicDimension(constraints, totalExtentCallback, new BoxConstraints(minWidth: minExtent).enforce(constraints).constrainWidth);
}
@override
double getMinIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
if (!isVertical)
return _getIntrinsicDimension(constraints, minCrossAxisExtentCallback, constraints.constrainHeight);
return constraints.constrainHeight(0.0);
}
@override
double getMaxIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
if (!isVertical)
return _getIntrinsicDimension(constraints, maxCrossAxisExtentCallback, constraints.constrainHeight);
return _getIntrinsicDimension(constraints, totalExtentCallback, new BoxConstraints(minHeight: minExtent).enforce(constraints).constrainHeight);
}
// We don't override computeDistanceToActualBaseline(), because we
// want the default behavior (returning null). Otherwise, as you
// scroll the RenderBlockViewport, it would shift in its parent if
// the parent was baseline-aligned, which makes no sense.
@override
void performLayout() {
if (_callback != null) {
try {
_inCallback = true;
invokeLayoutCallback(_callback);
} finally {
_inCallback = false;
}
}
super.performLayout();
if (postLayoutCallback != null)
postLayoutCallback();
}
void _paintContents(PaintingContext context, Offset offset) {
if (isVertical)
defaultPaint(context, offset.translate(0.0, startOffset));
else
defaultPaint(context, offset.translate(startOffset, 0.0));
overlayPainter?.paint(context, offset);
}
@override
void paint(PaintingContext context, Offset offset) {
context.pushClipRect(needsCompositing, offset, Point.origin & size, _paintContents);
}
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
if (isVertical)
transform.translate(0.0, startOffset);
else
transform.translate(startOffset, 0.0);
super.applyPaintTransform(child, transform);
}
@override
Rect describeApproximatePaintClip(RenderObject child) => Point.origin & size;
@override
bool hitTestChildren(HitTestResult result, { Point position }) {
if (isVertical)
return defaultHitTestChildren(result, position: position + new Offset(0.0, -startOffset));
else
return defaultHitTestChildren(result, position: position + new Offset(-startOffset, 0.0));
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('startOffset: $startOffset');
}
}
...@@ -2,17 +2,9 @@ ...@@ -2,17 +2,9 @@
// 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 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'rendering_tester.dart';
class TestBlockPainter extends RenderObjectPainter {
@override
void paint(PaintingContext context, Offset offset) { }
}
void main() { void main() {
test('block intrinsics', () { test('block intrinsics', () {
RenderParagraph paragraph = new RenderParagraph( RenderParagraph paragraph = new RenderParagraph(
...@@ -58,30 +50,4 @@ void main() { ...@@ -58,30 +50,4 @@ void main() {
expect(testBlock.getMinIntrinsicHeight(empty), equals(0.0)); expect(testBlock.getMinIntrinsicHeight(empty), equals(0.0));
expect(testBlock.getMaxIntrinsicHeight(empty), equals(0.0)); expect(testBlock.getMaxIntrinsicHeight(empty), equals(0.0));
}); });
test('overlay painters can attach and detach', () {
TestBlockPainter first = new TestBlockPainter();
TestBlockPainter second = new TestBlockPainter();
RenderBlockViewport block = new RenderBlockViewport(overlayPainter: first);
// The first painter isn't attached because we haven't attached block.
expect(first.renderObject, isNull);
expect(second.renderObject, isNull);
block.overlayPainter = second;
expect(first.renderObject, isNull);
expect(second.renderObject, isNull);
layout(block);
expect(first.renderObject, isNull);
expect(second.renderObject, equals(block));
block.overlayPainter = first;
expect(first.renderObject, equals(block));
expect(second.renderObject, isNull);
});
} }
// 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/rendering.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
class TestBlockPainter extends RenderObjectPainter {
@override
void paint(PaintingContext context, Offset offset) { }
}
void main() {
test('overlay painters can attach and detach', () {
TestBlockPainter first = new TestBlockPainter();
TestBlockPainter second = new TestBlockPainter();
RenderList list = new RenderList(overlayPainter: first);
// The first painter isn't attached because we haven't attached block.
expect(first.renderObject, isNull);
expect(second.renderObject, isNull);
list.overlayPainter = second;
expect(first.renderObject, isNull);
expect(second.renderObject, isNull);
layout(list);
expect(first.renderObject, isNull);
expect(second.renderObject, equals(list));
list.overlayPainter = first;
expect(first.renderObject, equals(list));
expect(second.renderObject, isNull);
});
}
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