From 1e207c0150f2482caa4eb017e0ba97e1c22baf7b Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Wed, 6 Apr 2016 19:39:10 -0700 Subject: [PATCH] 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. --- packages/flutter/lib/src/rendering/block.dart | 443 +++++------------- .../flutter/test/rendering/block_test.dart | 34 -- .../flutter/test/rendering/list_test.dart | 40 ++ 3 files changed, 148 insertions(+), 369 deletions(-) create mode 100644 packages/flutter/test/rendering/list_test.dart diff --git a/packages/flutter/lib/src/rendering/block.dart b/packages/flutter/lib/src/rendering/block.dart index 2db5e6e15fa..7c900af043c 100644 --- a/packages/flutter/lib/src/rendering/block.dart +++ b/packages/flutter/lib/src/rendering/block.dart @@ -4,8 +4,6 @@ import 'dart:math' as math; -import 'package:vector_math/vector_math_64.dart'; - import 'box.dart'; import 'object.dart'; import 'viewport.dart'; @@ -25,17 +23,15 @@ typedef double _Constrainer(double value); /// children. Because blocks expand in the main axis, blocks must be given /// unlimited space in the main axis, typically by being contained in a /// viewport with a scrolling direction that matches the block's main axis. -abstract class RenderBlockBase extends RenderBox +class RenderBlock extends RenderBox with ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin implements HasMainAxis { - RenderBlockBase({ + RenderBlock({ List children, - Axis mainAxis: Axis.vertical, - double itemExtent, - double minExtent: 0.0 - }) : _mainAxis = mainAxis, _itemExtent = itemExtent, _minExtent = minExtent { + Axis mainAxis: Axis.vertical + }) : _mainAxis = mainAxis { addAll(children); } @@ -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) { switch (_mainAxis) { case Axis.horizontal: - return new BoxConstraints.tightFor(height: constraints.maxHeight, width: itemExtent); + return new BoxConstraints.tightFor(height: constraints.maxHeight); case Axis.vertical: - return new BoxConstraints.tightFor(width: constraints.maxWidth, height: itemExtent); + return new BoxConstraints.tightFor(width: constraints.maxWidth); } } double get _mainAxisExtent { RenderBox child = lastChild; if (child == null) - return minExtent; + return 0.0; BoxParentData parentData = child.parentData; - return isVertical ? - math.max(minExtent, parentData.offset.dy + child.size.height) : - math.max(minExtent, parentData.offset.dx + child.size.width); + switch (mainAxis) { + case Axis.horizontal: + return parentData.offset.dx + child.size.width; + case Axis.vertical: + return parentData.offset.dy + child.size.height; + } } @override 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); double position = 0.0; RenderBox child = firstChild; while (child != null) { child.layout(innerConstraints, parentUsesSize: true); final BlockParentData childParentData = child.parentData; - childParentData.offset = isVertical ? new Offset(0.0, position) : new Offset(position, 0.0); - position += isVertical ? child.size.height : child.size.width; + switch (mainAxis) { + 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); child = childParentData.nextSibling; } - size = isVertical ? - constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent)) : - constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight)); + switch (mainAxis) { + case Axis.horizontal: + size = constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight)); + break; + case Axis.vertical: + size = constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent)); + break; + } + assert(!size.isInfinite); } @@ -122,21 +132,18 @@ abstract class RenderBlockBase extends RenderBox super.debugFillDescription(description); description.add('mainAxis: $mainAxis'); } -} - -/// A block layout with a concrete set of children. -class RenderBlock extends RenderBlockBase { - - RenderBlock({ - List 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 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; while (child != null) { extent = math.max(extent, childSize(child, innerConstraints)); @@ -151,64 +158,78 @@ class RenderBlock extends RenderBlockBase { BoxConstraints innerConstraints = _getInnerConstraints(constraints); RenderBox child = firstChild; while (child != null) { - double childExtent = isVertical ? - child.getMinIntrinsicHeight(innerConstraints) : - child.getMinIntrinsicWidth(innerConstraints); - extent += childExtent; + switch (mainAxis) { + case Axis.horizontal: + extent += child.getMinIntrinsicWidth(innerConstraints); + break; + case Axis.vertical: + extent += child.getMinIntrinsicHeight(innerConstraints); + break; + } final BlockParentData childParentData = child.parentData; child = childParentData.nextSibling; } - return constrainer(math.max(extent, minExtent)); + return constrainer(extent); } @override double getMinIntrinsicWidth(BoxConstraints constraints) { assert(constraints.debugAssertIsNormalized); - if (isVertical) { - return _getIntrinsicCrossAxis( - constraints, - (RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints), - constraints.constrainWidth - ); + switch (mainAxis) { + case Axis.horizontal: + return _getIntrinsicMainAxis(constraints, constraints.constrainWidth); + case Axis.vertical: + return _getIntrinsicCrossAxis( + constraints, + (RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints), + constraints.constrainWidth + ); } - return _getIntrinsicMainAxis(constraints, constraints.constrainWidth); } @override double getMaxIntrinsicWidth(BoxConstraints constraints) { assert(constraints.debugAssertIsNormalized); - if (isVertical) { - return _getIntrinsicCrossAxis( - constraints, - (RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints), - constraints.constrainWidth - ); + switch (mainAxis) { + case Axis.horizontal: + return _getIntrinsicMainAxis(constraints, constraints.constrainWidth); + case Axis.vertical: + return _getIntrinsicCrossAxis( + constraints, + (RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints), + constraints.constrainWidth + ); } - return _getIntrinsicMainAxis(constraints, constraints.constrainWidth); } @override double getMinIntrinsicHeight(BoxConstraints constraints) { assert(constraints.debugAssertIsNormalized); - if (isVertical) - return _getIntrinsicMainAxis(constraints, constraints.constrainHeight); - return _getIntrinsicCrossAxis( - constraints, - (RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints), - constraints.constrainHeight - ); + switch (mainAxis) { + case Axis.horizontal: + return _getIntrinsicCrossAxis( + constraints, + (RenderBox child, BoxConstraints innerConstraints) => child.getMinIntrinsicWidth(innerConstraints), + constraints.constrainHeight + ); + case Axis.vertical: + return _getIntrinsicMainAxis(constraints, constraints.constrainHeight); + } } @override double getMaxIntrinsicHeight(BoxConstraints constraints) { assert(constraints.debugAssertIsNormalized); - if (isVertical) - return _getIntrinsicMainAxis(constraints, constraints.constrainHeight); - return _getIntrinsicCrossAxis( - constraints, - (RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints), - constraints.constrainHeight - ); + switch (mainAxis) { + case Axis.horizontal: + return _getIntrinsicCrossAxis( + constraints, + (RenderBox child, BoxConstraints innerConstraints) => child.getMaxIntrinsicWidth(innerConstraints), + constraints.constrainHeight + ); + case Axis.vertical: + return _getIntrinsicMainAxis(constraints, constraints.constrainHeight); + } } @override @@ -216,14 +237,6 @@ class RenderBlock extends RenderBlockBase { 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 void paint(PaintingContext context, Offset offset) { defaultPaint(context, offset); @@ -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 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 description) { - super.debugFillDescription(description); - description.add('startOffset: $startOffset'); - } -} diff --git a/packages/flutter/test/rendering/block_test.dart b/packages/flutter/test/rendering/block_test.dart index 724fb52bdb1..69281b1fae9 100644 --- a/packages/flutter/test/rendering/block_test.dart +++ b/packages/flutter/test/rendering/block_test.dart @@ -2,17 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/material.dart'; 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('block intrinsics', () { RenderParagraph paragraph = new RenderParagraph( @@ -58,30 +50,4 @@ void main() { expect(testBlock.getMinIntrinsicHeight(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); - }); } diff --git a/packages/flutter/test/rendering/list_test.dart b/packages/flutter/test/rendering/list_test.dart new file mode 100644 index 00000000000..ff24b304897 --- /dev/null +++ b/packages/flutter/test/rendering/list_test.dart @@ -0,0 +1,40 @@ +// 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); + }); +}