From 885a1482f8697c71299a4565dc84268eaa96ef82 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 3 Dec 2021 10:39:11 -0800 Subject: [PATCH] Revert "Mixin for slotted RenderObjectWidgets and RenderBox (#94077)" (#94620) This reverts commit 988959dad9b6f07086d5a0903b1fb17ce3d00188. --- ...ti_child_render_object_widget_mixin.0.dart | 289 ---------------- ...ild_render_object_widget_mixin.0_test.dart | 41 --- packages/flutter/lib/src/material/chip.dart | 197 +++++++++-- .../lib/src/material/input_decorator.dart | 312 +++++++++++++++--- .../flutter/lib/src/material/list_tile.dart | 208 ++++++++++-- .../flutter/lib/src/rendering/object.dart | 5 - .../flutter/lib/src/widgets/framework.dart | 13 +- .../widgets/slotted_render_object_widget.dart | 271 --------------- packages/flutter/lib/widgets.dart | 1 - packages/flutter/test/material/chip_test.dart | 2 +- .../slotted_render_object_widget_test.dart | 292 ---------------- 11 files changed, 629 insertions(+), 1002 deletions(-) delete mode 100644 examples/api/lib/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0.dart delete mode 100644 examples/api/test/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0_test.dart delete mode 100644 packages/flutter/lib/src/widgets/slotted_render_object_widget.dart delete mode 100644 packages/flutter/test/widgets/slotted_render_object_widget_test.dart diff --git a/examples/api/lib/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0.dart b/examples/api/lib/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0.dart deleted file mode 100644 index 217544b3b6..0000000000 --- a/examples/api/lib/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0.dart +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2014 The Flutter 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/material.dart'; -import 'package:flutter/rendering.dart'; - -/// Slots used for the children of [Diagonal] and [RenderDiagonal]. -enum DiagonalSlot { - topLeft, - bottomRight, -} - -/// A widget that demonstrates the usage of [SlottedMultiChildRenderObjectWidgetMixin] -/// by providing slots for two children that will be arranged diagonally. -class Diagonal extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin { - const Diagonal({ - Key? key, - this.topLeft, - this.bottomRight, - this.backgroundColor, - }) : super(key: key); - - final Widget? topLeft; - final Widget? bottomRight; - final Color? backgroundColor; - - @override - Iterable get slots => DiagonalSlot.values; - - @override - Widget? childForSlot(DiagonalSlot slot) { - switch (slot) { - case DiagonalSlot.topLeft: - return topLeft; - case DiagonalSlot.bottomRight: - return bottomRight; - } - } - - // The [createRenderObject] and [updateRenderObject] methods configure the - // [RenderObject] backing this widget with the configuration of the widget. - // They do not need to do anything with the children of the widget, though. - // The children of the widget are automatically configured on the - // [RenderObject] by [SlottedRenderObjectElement.mount] and - // [SlottedRenderObjectElement.update]. - - @override - SlottedContainerRenderObjectMixin createRenderObject( - BuildContext context, - ) { - return RenderDiagonal( - backgroundColor: backgroundColor, - ); - } - - @override - void updateRenderObject( - BuildContext context, - SlottedContainerRenderObjectMixin renderObject, - ) { - (renderObject as RenderDiagonal).backgroundColor = backgroundColor; - } -} - -/// A render object that demonstrates the usage of [SlottedContainerRenderObjectMixin] -/// by providing slots for two children that will be arranged diagonally. -class RenderDiagonal extends RenderBox with SlottedContainerRenderObjectMixin, DebugOverflowIndicatorMixin { - RenderDiagonal({Color? backgroundColor}) : _backgroundColor = backgroundColor; - - // Getters and setters to configure the [RenderObject] with the configuration - // of the [Widget]. These mostly contain boilerplate code, but depending on - // where the configuration value is used, the setter has to call - // [markNeedsLayout], [markNeedsPaint], or [markNeedsSemanticsUpdate]. - Color? get backgroundColor => _backgroundColor; - Color? _backgroundColor; - set backgroundColor(Color? value) { - assert(value != null); - if (_backgroundColor == value) { - return; - } - _backgroundColor = value; - markNeedsPaint(); - } - - // Getters to simplify accessing the slotted children. - RenderBox? get _topLeft => childForSlot(DiagonalSlot.topLeft); - RenderBox? get _bottomRight => childForSlot(DiagonalSlot.bottomRight); - - // The size this render object would have if the incoming constraints were - // unconstrained; calculated during performLayout used during paint for an - // assertion that checks for unintended overflow. - late Size _childrenSize; - - // Returns children in hit test order. - @override - Iterable get children sync* { - if (_topLeft != null) { - yield _topLeft!; - } - if (_bottomRight != null) { - yield _bottomRight!; - } - } - - // LAYOUT - - @override - void performLayout() { - // Children are allowed to be as big as they want (= unconstrained). - const BoxConstraints childConstraints = BoxConstraints(); - - // Lay out the top left child and position it at offset zero. - Size topLeftSize = Size.zero; - final RenderBox? topLeft = _topLeft; - if (topLeft != null) { - topLeft.layout(childConstraints, parentUsesSize: true); - _positionChild(topLeft, Offset.zero); - topLeftSize = topLeft.size; - } - - // Lay out the bottom right child and position it at the bottom right corner - // of the top left child. - Size bottomRightSize = Size.zero; - final RenderBox? bottomRight = _bottomRight; - if (bottomRight != null) { - bottomRight.layout(childConstraints, parentUsesSize: true); - _positionChild( - bottomRight, - Offset(topLeftSize.width, topLeftSize.height), - ); - bottomRightSize = bottomRight.size; - } - - // Calculate the overall size and constrain it to the given constraints. - // Any overflow is marked (in debug mode) during paint. - _childrenSize = Size( - topLeftSize.width + bottomRightSize.width, - topLeftSize.height + bottomRightSize.height, - ); - size = constraints.constrain(_childrenSize); - } - - void _positionChild(RenderBox child, Offset offset) { - (child.parentData! as BoxParentData).offset = offset; - } - - // PAINT - - @override - void paint(PaintingContext context, Offset offset) { - // Paint the background. - if (backgroundColor != null) { - context.canvas.drawRect( - offset & size, - Paint() - ..color = backgroundColor!, - ); - } - - void paintChild(RenderBox child, PaintingContext context, Offset offset) { - final BoxParentData childParentData = child.parentData! as BoxParentData; - context.paintChild(child, childParentData.offset + offset); - } - - // Paint the children at the offset calculated during layout. - final RenderBox? topLeft = _topLeft; - if (topLeft != null) { - paintChild(topLeft, context, offset); - } - final RenderBox? bottomRight = _bottomRight; - if (bottomRight != null) { - paintChild(bottomRight, context, offset); - } - - // Paint an overflow indicator in debug mode if the children want to be - // larger than the incoming constraints allow. - assert(() { - paintOverflowIndicator( - context, - offset, - Offset.zero & size, - Offset.zero & _childrenSize, - ); - return true; - }()); - } - - // HIT TEST - - @override - bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { - for (final RenderBox child in children) { - final BoxParentData parentData = child.parentData! as BoxParentData; - final bool isHit = result.addWithPaintOffset( - offset: parentData.offset, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) { - assert(transformed == position - parentData.offset); - return child.hitTest(result, position: transformed); - }, - ); - if (isHit) { - return true; - } - } - return false; - } - - // INTRINSICS - - // Incoming height/width are ignored as children are always laid out unconstrained. - - @override - double computeMinIntrinsicWidth(double height) { - final double topLeftWidth = _topLeft?.getMinIntrinsicWidth(double.infinity) ?? 0; - final double bottomRightWith = _bottomRight?.getMinIntrinsicWidth(double.infinity) ?? 0; - return topLeftWidth + bottomRightWith; - } - - @override - double computeMaxIntrinsicWidth(double height) { - final double topLeftWidth = _topLeft?.getMaxIntrinsicWidth(double.infinity) ?? 0; - final double bottomRightWith = _bottomRight?.getMaxIntrinsicWidth(double.infinity) ?? 0; - return topLeftWidth + bottomRightWith; } - - @override - double computeMinIntrinsicHeight(double width) { - final double topLeftHeight = _topLeft?.getMinIntrinsicHeight(double.infinity) ?? 0; - final double bottomRightHeight = _bottomRight?.getMinIntrinsicHeight(double.infinity) ?? 0; - return topLeftHeight + bottomRightHeight; - } - - @override - double computeMaxIntrinsicHeight(double width) { - final double topLeftHeight = _topLeft?.getMaxIntrinsicHeight(double.infinity) ?? 0; - final double bottomRightHeight = _bottomRight?.getMaxIntrinsicHeight(double.infinity) ?? 0; - return topLeftHeight + bottomRightHeight; - } - - @override - Size computeDryLayout(BoxConstraints constraints) { - const BoxConstraints childConstraints = BoxConstraints(); - final Size topLeftSize = _topLeft?.computeDryLayout(childConstraints) ?? Size.zero; - final Size bottomRightSize = _bottomRight?.computeDryLayout(childConstraints) ?? Size.zero; - return constraints.constrain(Size( - topLeftSize.width + bottomRightSize.width, - topLeftSize.height + bottomRightSize.height, - )); - } -} - -class ExampleWidget extends StatelessWidget { - const ExampleWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: const Text('Slotted RenderObject Example')), - body: Center( - child: Diagonal( - topLeft: Container( - color: Colors.green, - height: 100, - width: 200, - child: const Center( - child: Text('topLeft'), - ), - ), - bottomRight: Container( - color: Colors.yellow, - height: 60, - width: 30, - child: const Center( - child: Text('bottomRight'), - ), - ), - backgroundColor: Colors.blue, - ), - ), - ), - ); - } -} - -void main() { - runApp(const ExampleWidget()); -} diff --git a/examples/api/test/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0_test.dart b/examples/api/test/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0_test.dart deleted file mode 100644 index 150d9e57fd..0000000000 --- a/examples/api/test/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0_test.dart +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2014 The Flutter 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/widgets.dart'; -import 'package:flutter_api_samples/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0.dart' as example; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('shows two widgets arranged diagonally', (WidgetTester tester) async { - await tester.pumpWidget( - const example.ExampleWidget(), - ); - - expect(find.text('topLeft'), findsOneWidget); - expect(find.text('bottomRight'), findsOneWidget); - - expect( - tester.getBottomRight(findContainerWithText('topLeft')), - tester.getTopLeft(findContainerWithText('bottomRight')), - ); - - expect( - tester.getSize(findContainerWithText('topLeft')), - const Size(200, 100), - ); - expect( - tester.getSize(findContainerWithText('bottomRight')), - const Size(30, 60), - ); - - expect( - tester.getSize(find.byType(example.Diagonal)), - const Size(200 + 30, 100 + 60), - ); - }); -} - -Finder findContainerWithText(String text) { - return find.ancestor(of: find.text(text), matching: find.byType(Container)); -} diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index b720b85681..82ff6c2fbb 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -2059,7 +2059,7 @@ class _RenderChipRedirectingHitDetection extends RenderConstrainedBox { } } -class _ChipRenderWidget extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<_ChipSlot> { +class _ChipRenderWidget extends RenderObjectWidget { const _ChipRenderWidget({ Key? key, required this.theme, @@ -2083,19 +2083,7 @@ class _ChipRenderWidget extends RenderObjectWidget with SlottedMultiChildRenderO final ShapeBorder? avatarBorder; @override - Iterable<_ChipSlot> get slots => _ChipSlot.values; - - @override - Widget? childForSlot(_ChipSlot slot) { - switch (slot) { - case _ChipSlot.label: - return theme.label; - case _ChipSlot.avatar: - return theme.avatar; - case _ChipSlot.deleteIcon: - return theme.deleteIcon; - } - } + _RenderChipElement createElement() => _RenderChipElement(this); @override void updateRenderObject(BuildContext context, _RenderChip renderObject) { @@ -2112,7 +2100,7 @@ class _ChipRenderWidget extends RenderObjectWidget with SlottedMultiChildRenderO } @override - SlottedContainerRenderObjectMixin<_ChipSlot> createRenderObject(BuildContext context) { + RenderObject createRenderObject(BuildContext context) { return _RenderChip( theme: theme, textDirection: Directionality.of(context), @@ -2133,6 +2121,105 @@ enum _ChipSlot { deleteIcon, } +class _RenderChipElement extends RenderObjectElement { + _RenderChipElement(_ChipRenderWidget chip) : super(chip); + + final Map<_ChipSlot, Element> slotToChild = <_ChipSlot, Element>{}; + + @override + _ChipRenderWidget get widget => super.widget as _ChipRenderWidget; + + @override + _RenderChip get renderObject => super.renderObject as _RenderChip; + + @override + void visitChildren(ElementVisitor visitor) { + slotToChild.values.forEach(visitor); + } + + @override + void forgetChild(Element child) { + assert(slotToChild.containsValue(child)); + assert(child.slot is _ChipSlot); + assert(slotToChild.containsKey(child.slot)); + slotToChild.remove(child.slot); + super.forgetChild(child); + } + + void _mountChild(Widget widget, _ChipSlot slot) { + final Element? oldChild = slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); + if (oldChild != null) { + slotToChild.remove(slot); + } + if (newChild != null) { + slotToChild[slot] = newChild; + } + } + + @override + void mount(Element? parent, Object? newSlot) { + super.mount(parent, newSlot); + _mountChild(widget.theme.avatar, _ChipSlot.avatar); + _mountChild(widget.theme.deleteIcon, _ChipSlot.deleteIcon); + _mountChild(widget.theme.label, _ChipSlot.label); + } + + void _updateChild(Widget widget, _ChipSlot slot) { + final Element? oldChild = slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); + if (oldChild != null) { + slotToChild.remove(slot); + } + if (newChild != null) { + slotToChild[slot] = newChild; + } + } + + @override + void update(_ChipRenderWidget newWidget) { + super.update(newWidget); + assert(widget == newWidget); + _updateChild(widget.theme.label, _ChipSlot.label); + _updateChild(widget.theme.avatar, _ChipSlot.avatar); + _updateChild(widget.theme.deleteIcon, _ChipSlot.deleteIcon); + } + + void _updateRenderObject(RenderObject? child, _ChipSlot slot) { + switch (slot) { + case _ChipSlot.avatar: + renderObject.avatar = child as RenderBox?; + break; + case _ChipSlot.label: + renderObject.label = child as RenderBox?; + break; + case _ChipSlot.deleteIcon: + renderObject.deleteIcon = child as RenderBox?; + break; + } + } + + @override + void insertRenderObjectChild(RenderObject child, _ChipSlot slot) { + assert(child is RenderBox); + _updateRenderObject(child, slot); + assert(renderObject.children.keys.contains(slot)); + } + + @override + void removeRenderObjectChild(RenderObject child, _ChipSlot slot) { + assert(child is RenderBox); + assert(renderObject.children[slot] == child); + _updateRenderObject(null, slot); + assert(!renderObject.children.keys.contains(slot)); + } + + @override + void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) { + assert(false, 'not reachable'); + } +} + @immutable class _ChipRenderTheme { const _ChipRenderTheme({ @@ -2199,7 +2286,7 @@ class _ChipRenderTheme { } } -class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_ChipSlot> { +class _RenderChip extends RenderBox { _RenderChip({ required _ChipRenderTheme theme, required TextDirection textDirection, @@ -2220,6 +2307,8 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip enableAnimation.addListener(markNeedsPaint); } + final Map<_ChipSlot, RenderBox> children = <_ChipSlot, RenderBox>{}; + bool? value; bool? isEnabled; late Rect _deleteButtonRect; @@ -2230,9 +2319,35 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip Animation enableAnimation; ShapeBorder? avatarBorder; - RenderBox? get avatar => childForSlot(_ChipSlot.avatar); - RenderBox? get deleteIcon => childForSlot(_ChipSlot.deleteIcon); - RenderBox? get label => childForSlot(_ChipSlot.label); + RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _ChipSlot slot) { + if (oldChild != null) { + dropChild(oldChild); + children.remove(slot); + } + if (newChild != null) { + children[slot] = newChild; + adoptChild(newChild); + } + return newChild; + } + + RenderBox? _avatar; + RenderBox? get avatar => _avatar; + set avatar(RenderBox? value) { + _avatar = _updateChild(_avatar, value, _ChipSlot.avatar); + } + + RenderBox? _deleteIcon; + RenderBox? get deleteIcon => _deleteIcon; + set deleteIcon(RenderBox? value) { + _deleteIcon = _updateChild(_deleteIcon, value, _ChipSlot.deleteIcon); + } + + RenderBox? _label; + RenderBox? get label => _label; + set label(RenderBox? value) { + _label = _updateChild(_label, value, _ChipSlot.label); + } _ChipRenderTheme get theme => _theme; _ChipRenderTheme _theme; @@ -2255,8 +2370,7 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip } // The returned list is ordered for hit testing. - @override - Iterable get children sync* { + Iterable get _children sync* { if (avatar != null) { yield avatar!; } @@ -2271,6 +2385,47 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip bool get isDrawingCheckmark => theme.showCheckmark && !checkmarkAnimation.isDismissed; bool get deleteIconShowing => !deleteDrawerAnimation.isDismissed; + @override + void attach(PipelineOwner owner) { + super.attach(owner); + for (final RenderBox child in _children) { + child.attach(owner); + } + } + + @override + void detach() { + super.detach(); + for (final RenderBox child in _children) { + child.detach(); + } + } + + @override + void redepthChildren() { + _children.forEach(redepthChild); + } + + @override + void visitChildren(RenderObjectVisitor visitor) { + _children.forEach(visitor); + } + + @override + List debugDescribeChildren() { + final List value = []; + void add(RenderBox? child, String name) { + if (child != null) { + value.add(child.toDiagnosticsNode(name: name)); + } + } + + add(avatar, 'avatar'); + add(label, 'label'); + add(deleteIcon, 'deleteIcon'); + return value; + } + @override bool get sizedByParent => false; diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 7e9d5e938a..ba9d7ece9b 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -682,7 +682,7 @@ class _RenderDecorationLayout { } // The workhorse: layout and paint a _Decorator widget's _Decoration. -class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin<_DecorationSlot> { +class _RenderDecoration extends RenderBox { _RenderDecoration({ required _Decoration decoration, required TextDirection textDirection, @@ -702,22 +702,88 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin _expands = expands; static const double subtextGap = 8.0; + final Map<_DecorationSlot, RenderBox> children = <_DecorationSlot, RenderBox>{}; - RenderBox? get icon => childForSlot(_DecorationSlot.icon); - RenderBox? get input => childForSlot(_DecorationSlot.input); - RenderBox? get label => childForSlot(_DecorationSlot.label); - RenderBox? get hint => childForSlot(_DecorationSlot.hint); - RenderBox? get prefix => childForSlot(_DecorationSlot.prefix); - RenderBox? get suffix => childForSlot(_DecorationSlot.suffix); - RenderBox? get prefixIcon => childForSlot(_DecorationSlot.prefixIcon); - RenderBox? get suffixIcon => childForSlot(_DecorationSlot.suffixIcon); - RenderBox? get helperError => childForSlot(_DecorationSlot.helperError); - RenderBox? get counter => childForSlot(_DecorationSlot.counter); - RenderBox? get container => childForSlot(_DecorationSlot.container); + RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _DecorationSlot slot) { + if (oldChild != null) { + dropChild(oldChild); + children.remove(slot); + } + if (newChild != null) { + children[slot] = newChild; + adoptChild(newChild); + } + return newChild; + } + + RenderBox? _icon; + RenderBox? get icon => _icon; + set icon(RenderBox? value) { + _icon = _updateChild(_icon, value, _DecorationSlot.icon); + } + + RenderBox? _input; + RenderBox? get input => _input; + set input(RenderBox? value) { + _input = _updateChild(_input, value, _DecorationSlot.input); + } + + RenderBox? _label; + RenderBox? get label => _label; + set label(RenderBox? value) { + _label = _updateChild(_label, value, _DecorationSlot.label); + } + + RenderBox? _hint; + RenderBox? get hint => _hint; + set hint(RenderBox? value) { + _hint = _updateChild(_hint, value, _DecorationSlot.hint); + } + + RenderBox? _prefix; + RenderBox? get prefix => _prefix; + set prefix(RenderBox? value) { + _prefix = _updateChild(_prefix, value, _DecorationSlot.prefix); + } + + RenderBox? _suffix; + RenderBox? get suffix => _suffix; + set suffix(RenderBox? value) { + _suffix = _updateChild(_suffix, value, _DecorationSlot.suffix); + } + + RenderBox? _prefixIcon; + RenderBox? get prefixIcon => _prefixIcon; + set prefixIcon(RenderBox? value) { + _prefixIcon = _updateChild(_prefixIcon, value, _DecorationSlot.prefixIcon); + } + + RenderBox? _suffixIcon; + RenderBox? get suffixIcon => _suffixIcon; + set suffixIcon(RenderBox? value) { + _suffixIcon = _updateChild(_suffixIcon, value, _DecorationSlot.suffixIcon); + } + + RenderBox? _helperError; + RenderBox? get helperError => _helperError; + set helperError(RenderBox? value) { + _helperError = _updateChild(_helperError, value, _DecorationSlot.helperError); + } + + RenderBox? _counter; + RenderBox? get counter => _counter; + set counter(RenderBox? value) { + _counter = _updateChild(_counter, value, _DecorationSlot.counter); + } + + RenderBox? _container; + RenderBox? get container => _container; + set container(RenderBox? value) { + _container = _updateChild(_container, value, _DecorationSlot.container); + } // The returned list is ordered for hit testing. - @override - Iterable get children sync* { + Iterable get _children sync* { if (icon != null) yield icon!; if (input != null) @@ -816,6 +882,30 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin return !decoration.isCollapsed && decoration.border!.isOutline; } + @override + void attach(PipelineOwner owner) { + super.attach(owner); + for (final RenderBox child in _children) + child.attach(owner); + } + + @override + void detach() { + super.detach(); + for (final RenderBox child in _children) + child.detach(); + } + + @override + void redepthChildren() { + _children.forEach(redepthChild); + } + + @override + void visitChildren(RenderObjectVisitor visitor) { + _children.forEach(visitor); + } + @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { if (icon != null) @@ -850,6 +940,27 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin visitor(counter!); } + @override + List debugDescribeChildren() { + final List value = []; + void add(RenderBox? child, String name) { + if (child != null) + value.add(child.toDiagnosticsNode(name: name)); + } + add(icon, 'icon'); + add(input, 'input'); + add(label, 'label'); + add(hint, 'hint'); + add(prefix, 'prefix'); + add(suffix, 'suffix'); + add(prefixIcon, 'prefixIcon'); + add(suffixIcon, 'suffixIcon'); + add(helperError, 'helperError'); + add(counter, 'counter'); + add(container, 'container'); + return value; + } + @override bool get sizedByParent => false; @@ -1527,7 +1638,7 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin @override bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { assert(position != null); - for (final RenderBox child in children) { + for (final RenderBox child in _children) { // The label must be handled specially since we've transformed it. final Offset offset = _boxParentData(child).offset; final bool isHit = result.addWithPaintOffset( @@ -1556,7 +1667,146 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin } } -class _Decorator extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<_DecorationSlot> { +class _DecorationElement extends RenderObjectElement { + _DecorationElement(_Decorator widget) : super(widget); + + final Map<_DecorationSlot, Element> slotToChild = <_DecorationSlot, Element>{}; + + @override + _Decorator get widget => super.widget as _Decorator; + + @override + _RenderDecoration get renderObject => super.renderObject as _RenderDecoration; + + @override + void visitChildren(ElementVisitor visitor) { + slotToChild.values.forEach(visitor); + } + + @override + void forgetChild(Element child) { + assert(slotToChild.containsValue(child)); + assert(child.slot is _DecorationSlot); + assert(slotToChild.containsKey(child.slot)); + slotToChild.remove(child.slot); + super.forgetChild(child); + } + + void _mountChild(Widget? widget, _DecorationSlot slot) { + final Element? oldChild = slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); + if (oldChild != null) { + slotToChild.remove(slot); + } + if (newChild != null) { + slotToChild[slot] = newChild; + } + } + + @override + void mount(Element? parent, Object? newSlot) { + super.mount(parent, newSlot); + _mountChild(widget.decoration.icon, _DecorationSlot.icon); + _mountChild(widget.decoration.input, _DecorationSlot.input); + _mountChild(widget.decoration.label, _DecorationSlot.label); + _mountChild(widget.decoration.hint, _DecorationSlot.hint); + _mountChild(widget.decoration.prefix, _DecorationSlot.prefix); + _mountChild(widget.decoration.suffix, _DecorationSlot.suffix); + _mountChild(widget.decoration.prefixIcon, _DecorationSlot.prefixIcon); + _mountChild(widget.decoration.suffixIcon, _DecorationSlot.suffixIcon); + _mountChild(widget.decoration.helperError, _DecorationSlot.helperError); + _mountChild(widget.decoration.counter, _DecorationSlot.counter); + _mountChild(widget.decoration.container, _DecorationSlot.container); + } + + void _updateChild(Widget? widget, _DecorationSlot slot) { + final Element? oldChild = slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); + if (oldChild != null) { + slotToChild.remove(slot); + } + if (newChild != null) { + slotToChild[slot] = newChild; + } + } + + @override + void update(_Decorator newWidget) { + super.update(newWidget); + assert(widget == newWidget); + _updateChild(widget.decoration.icon, _DecorationSlot.icon); + _updateChild(widget.decoration.input, _DecorationSlot.input); + _updateChild(widget.decoration.label, _DecorationSlot.label); + _updateChild(widget.decoration.hint, _DecorationSlot.hint); + _updateChild(widget.decoration.prefix, _DecorationSlot.prefix); + _updateChild(widget.decoration.suffix, _DecorationSlot.suffix); + _updateChild(widget.decoration.prefixIcon, _DecorationSlot.prefixIcon); + _updateChild(widget.decoration.suffixIcon, _DecorationSlot.suffixIcon); + _updateChild(widget.decoration.helperError, _DecorationSlot.helperError); + _updateChild(widget.decoration.counter, _DecorationSlot.counter); + _updateChild(widget.decoration.container, _DecorationSlot.container); + } + + void _updateRenderObject(RenderBox? child, _DecorationSlot slot) { + switch (slot) { + case _DecorationSlot.icon: + renderObject.icon = child; + break; + case _DecorationSlot.input: + renderObject.input = child; + break; + case _DecorationSlot.label: + renderObject.label = child; + break; + case _DecorationSlot.hint: + renderObject.hint = child; + break; + case _DecorationSlot.prefix: + renderObject.prefix = child; + break; + case _DecorationSlot.suffix: + renderObject.suffix = child; + break; + case _DecorationSlot.prefixIcon: + renderObject.prefixIcon = child; + break; + case _DecorationSlot.suffixIcon: + renderObject.suffixIcon = child; + break; + case _DecorationSlot.helperError: + renderObject.helperError = child; + break; + case _DecorationSlot.counter: + renderObject.counter = child; + break; + case _DecorationSlot.container: + renderObject.container = child; + break; + } + } + + @override + void insertRenderObjectChild(RenderObject child, _DecorationSlot slot) { + assert(child is RenderBox); + _updateRenderObject(child as RenderBox, slot); + assert(renderObject.children.keys.contains(slot)); + } + + @override + void removeRenderObjectChild(RenderObject child, _DecorationSlot slot) { + assert(child is RenderBox); + assert(renderObject.children[slot] == child); + _updateRenderObject(null, slot); + assert(!renderObject.children.keys.contains(slot)); + } + + @override + void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) { + assert(false, 'not reachable'); + } +} + +class _Decorator extends RenderObjectWidget { const _Decorator({ Key? key, required this.textAlignVertical, @@ -1579,35 +1829,7 @@ class _Decorator extends RenderObjectWidget with SlottedMultiChildRenderObjectWi final bool expands; @override - Iterable<_DecorationSlot> get slots => _DecorationSlot.values; - - @override - Widget? childForSlot(_DecorationSlot slot) { - switch (slot) { - case _DecorationSlot.icon: - return decoration.icon; - case _DecorationSlot.input: - return decoration.input; - case _DecorationSlot.label: - return decoration.label; - case _DecorationSlot.hint: - return decoration.hint; - case _DecorationSlot.prefix: - return decoration.prefix; - case _DecorationSlot.suffix: - return decoration.suffix; - case _DecorationSlot.prefixIcon: - return decoration.prefixIcon; - case _DecorationSlot.suffixIcon: - return decoration.suffixIcon; - case _DecorationSlot.helperError: - return decoration.helperError; - case _DecorationSlot.counter: - return decoration.counter; - case _DecorationSlot.container: - return decoration.container; - } - } + _DecorationElement createElement() => _DecorationElement(this); @override _RenderDecoration createRenderObject(BuildContext context) { diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index 87a3c11301..1c68b55c9c 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -1266,7 +1266,7 @@ enum _ListTileSlot { trailing, } -class _ListTile extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<_ListTileSlot> { +class _ListTile extends RenderObjectWidget { const _ListTile({ Key? key, this.leading, @@ -1307,21 +1307,7 @@ class _ListTile extends RenderObjectWidget with SlottedMultiChildRenderObjectWid final double minLeadingWidth; @override - Iterable<_ListTileSlot> get slots => _ListTileSlot.values; - - @override - Widget? childForSlot(_ListTileSlot slot) { - switch (slot) { - case _ListTileSlot.leading: - return leading; - case _ListTileSlot.title: - return title; - case _ListTileSlot.subtitle: - return subtitle; - case _ListTileSlot.trailing: - return trailing; - } - } + _ListTileElement createElement() => _ListTileElement(this); @override _RenderListTile createRenderObject(BuildContext context) { @@ -1353,7 +1339,111 @@ class _ListTile extends RenderObjectWidget with SlottedMultiChildRenderObjectWid } } -class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_ListTileSlot> { +class _ListTileElement extends RenderObjectElement { + _ListTileElement(_ListTile widget) : super(widget); + + final Map<_ListTileSlot, Element> slotToChild = <_ListTileSlot, Element>{}; + + @override + _ListTile get widget => super.widget as _ListTile; + + @override + _RenderListTile get renderObject => super.renderObject as _RenderListTile; + + @override + void visitChildren(ElementVisitor visitor) { + slotToChild.values.forEach(visitor); + } + + @override + void forgetChild(Element child) { + assert(slotToChild.containsValue(child)); + assert(child.slot is _ListTileSlot); + assert(slotToChild.containsKey(child.slot)); + slotToChild.remove(child.slot); + super.forgetChild(child); + } + + void _mountChild(Widget? widget, _ListTileSlot slot) { + final Element? oldChild = slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); + if (oldChild != null) { + slotToChild.remove(slot); + } + if (newChild != null) { + slotToChild[slot] = newChild; + } + } + + @override + void mount(Element? parent, Object? newSlot) { + super.mount(parent, newSlot); + _mountChild(widget.leading, _ListTileSlot.leading); + _mountChild(widget.title, _ListTileSlot.title); + _mountChild(widget.subtitle, _ListTileSlot.subtitle); + _mountChild(widget.trailing, _ListTileSlot.trailing); + } + + void _updateChild(Widget? widget, _ListTileSlot slot) { + final Element? oldChild = slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); + if (oldChild != null) { + slotToChild.remove(slot); + } + if (newChild != null) { + slotToChild[slot] = newChild; + } + } + + @override + void update(_ListTile newWidget) { + super.update(newWidget); + assert(widget == newWidget); + _updateChild(widget.leading, _ListTileSlot.leading); + _updateChild(widget.title, _ListTileSlot.title); + _updateChild(widget.subtitle, _ListTileSlot.subtitle); + _updateChild(widget.trailing, _ListTileSlot.trailing); + } + + void _updateRenderObject(RenderBox? child, _ListTileSlot slot) { + switch (slot) { + case _ListTileSlot.leading: + renderObject.leading = child; + break; + case _ListTileSlot.title: + renderObject.title = child; + break; + case _ListTileSlot.subtitle: + renderObject.subtitle = child; + break; + case _ListTileSlot.trailing: + renderObject.trailing = child; + break; + } + } + + @override + void insertRenderObjectChild(RenderObject child, _ListTileSlot slot) { + assert(child is RenderBox); + _updateRenderObject(child as RenderBox, slot); + assert(renderObject.children.keys.contains(slot)); + } + + @override + void removeRenderObjectChild(RenderObject child, _ListTileSlot slot) { + assert(child is RenderBox); + assert(renderObject.children[slot] == child); + _updateRenderObject(null, slot); + assert(!renderObject.children.keys.contains(slot)); + } + + @override + void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) { + assert(false, 'not reachable'); + } +} + +class _RenderListTile extends RenderBox { _RenderListTile({ required bool isDense, required VisualDensity visualDensity, @@ -1382,14 +1472,46 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_ _minVerticalPadding = minVerticalPadding, _minLeadingWidth = minLeadingWidth; - RenderBox? get leading => childForSlot(_ListTileSlot.leading); - RenderBox? get title => childForSlot(_ListTileSlot.title); - RenderBox? get subtitle => childForSlot(_ListTileSlot.subtitle); - RenderBox? get trailing => childForSlot(_ListTileSlot.trailing); + final Map<_ListTileSlot, RenderBox> children = <_ListTileSlot, RenderBox>{}; + + RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _ListTileSlot slot) { + if (oldChild != null) { + dropChild(oldChild); + children.remove(slot); + } + if (newChild != null) { + children[slot] = newChild; + adoptChild(newChild); + } + return newChild; + } + + RenderBox? _leading; + RenderBox? get leading => _leading; + set leading(RenderBox? value) { + _leading = _updateChild(_leading, value, _ListTileSlot.leading); + } + + RenderBox? _title; + RenderBox? get title => _title; + set title(RenderBox? value) { + _title = _updateChild(_title, value, _ListTileSlot.title); + } + + RenderBox? _subtitle; + RenderBox? get subtitle => _subtitle; + set subtitle(RenderBox? value) { + _subtitle = _updateChild(_subtitle, value, _ListTileSlot.subtitle); + } + + RenderBox? _trailing; + RenderBox? get trailing => _trailing; + set trailing(RenderBox? value) { + _trailing = _updateChild(_trailing, value, _ListTileSlot.trailing); + } // The returned list is ordered for hit testing. - @override - Iterable get children sync* { + Iterable get _children sync* { if (leading != null) yield leading!; if (title != null) @@ -1493,6 +1615,44 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_ markNeedsLayout(); } + @override + void attach(PipelineOwner owner) { + super.attach(owner); + for (final RenderBox child in _children) + child.attach(owner); + } + + @override + void detach() { + super.detach(); + for (final RenderBox child in _children) + child.detach(); + } + + @override + void redepthChildren() { + _children.forEach(redepthChild); + } + + @override + void visitChildren(RenderObjectVisitor visitor) { + _children.forEach(visitor); + } + + @override + List debugDescribeChildren() { + final List value = []; + void add(RenderBox? child, String name) { + if (child != null) + value.add(child.toDiagnosticsNode(name: name)); + } + add(leading, 'leading'); + add(title, 'title'); + add(subtitle, 'subtitle'); + add(trailing, 'trailing'); + return value; + } + @override bool get sizedByParent => false; @@ -1745,7 +1905,7 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_ @override bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { assert(position != null); - for (final RenderBox child in children) { + for (final RenderBox child in _children) { final BoxParentData parentData = child.parentData! as BoxParentData; final bool isHit = result.addWithPaintOffset( offset: parentData.offset, diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index cf95c132da..dd4248f1cf 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -3167,11 +3167,6 @@ mixin ContainerParentDataMixin on ParentData { /// parent data. /// /// Moreover, this is a required mixin for render objects returned to [MultiChildRenderObjectWidget]. -/// -/// See also: -/// -/// * [SlottedContainerRenderObjectMixin], which organizes its children -/// in different named slots. mixin ContainerRenderObjectMixin> on RenderObject { bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType? equals }) { ParentDataType childParentData = child.parentData! as ParentDataType; diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 31cf0d0fb6..ed0424f23c 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1677,13 +1677,6 @@ abstract class InheritedWidget extends ProxyWidget { /// RenderObjectWidgets provide the configuration for [RenderObjectElement]s, /// which wrap [RenderObject]s, which provide the actual rendering of the /// application. -/// -/// See also: -/// -/// * [MultiChildRenderObjectWidget], which configures a [RenderObject] with -/// a single list of children. -/// * [SlottedMultiChildRenderObjectWidgetMixin], which configures a -/// [RenderObject] that organizes its children in different named slots. abstract class RenderObjectWidget extends Widget { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. @@ -1774,11 +1767,7 @@ abstract class SingleChildRenderObjectWidget extends RenderObjectWidget { /// See also: /// /// * [Stack], which uses [MultiChildRenderObjectWidget]. -/// * [RenderStack], for an example implementation of the associated render -/// object. -/// * [SlottedMultiChildRenderObjectWidgetMixin], which configures a -/// [RenderObject] that instead of having a single list of children organizes -/// its children in named slots. +/// * [RenderStack], for an example implementation of the associated render object. abstract class MultiChildRenderObjectWidget extends RenderObjectWidget { /// Initializes fields for subclasses. /// diff --git a/packages/flutter/lib/src/widgets/slotted_render_object_widget.dart b/packages/flutter/lib/src/widgets/slotted_render_object_widget.dart deleted file mode 100644 index 25ae7b4d3b..0000000000 --- a/packages/flutter/lib/src/widgets/slotted_render_object_widget.dart +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2014 The Flutter 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/foundation.dart'; -import 'package:flutter/rendering.dart'; - -import 'framework.dart'; - -/// A mixin for a [RenderObjectWidget] that configures a [RenderObject] -/// subclass, which organizes its children in different slots. -/// -/// Implementers of this mixin have to provide the list of available slots by -/// overriding [slots]. The list of slots must never change for a given class -/// implementing this mixin. In the common case, [Enum] values are used as slots -/// and [slots] is typically implemented to return the value of the enum's -/// `values` getter. -/// -/// Furthermore, [childForSlot] must be implemented to return the current -/// widget configuration for a given slot. -/// -/// The [RenderObject] returned by [createRenderObject] and updated by -/// [updateRenderObject] must implement the [SlottedContainerRenderObjectMixin]. -/// -/// The type parameter `S` is the type for the slots to be used by this -/// [RenderObjectWidget] and the [RenderObject] it configures. In the typical -/// case, `S` is an [Enum] type. -/// -/// {@tool dartpad} -/// This example uses the [SlottedMultiChildRenderObjectWidgetMixin] in -/// combination with the [SlottedContainerRenderObjectMixin] to implement a -/// widget that provides two slots: topLeft and bottomRight. The widget arranges -/// the children in those slots diagonally. -/// -/// ** See code in examples/api/lib/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0.dart ** -/// {@end-tool} -/// -/// See also: -/// -/// * [MultiChildRenderObjectWidget], which configures a [RenderObject] -/// with a single list of children. -/// * [ListTile], which uses [SlottedMultiChildRenderObjectWidgetMixin] in its -/// internal (private) implementation. -mixin SlottedMultiChildRenderObjectWidgetMixin on RenderObjectWidget { - /// Returns a list of all available slots. - /// - /// The list of slots must be static and must never change for a given class - /// implementing this mixin. - /// - /// Typically, an [Enum] is used to identify the different slots. In that case - /// this getter can be implemented by returning what the `values` getter - /// of the enum used returns. - @protected - Iterable get slots; - - /// Returns the widget that is currently occupying the provided `slot`. - /// - /// The [RenderObject] configured by this class will be configured to have - /// the [RenderObject] produced by the returned [Widget] in the provided - /// `slot`. - @protected - Widget? childForSlot(S slot); - - @override - SlottedContainerRenderObjectMixin createRenderObject(BuildContext context); - - @override - void updateRenderObject(BuildContext context, SlottedContainerRenderObjectMixin renderObject); - - @override - SlottedRenderObjectElement createElement() => SlottedRenderObjectElement(this); -} - -/// Mixin for a [RenderBox] configured by a [SlottedMultiChildRenderObjectWidgetMixin]. -/// -/// The [RenderBox] child currently occupying a given slot can be obtained by -/// calling [childForSlot]. -/// -/// Implementers may consider overriding [children] to return the children -/// of this render object in a consistent order (e.g. hit test order). -/// -/// The type parameter `S` is the type for the slots to be used by this -/// [RenderObject] and the [SlottedMultiChildRenderObjectWidgetMixin] it was -/// configured by. In the typical case, `S` is an [Enum] type. -/// -/// See [SlottedMultiChildRenderObjectWidgetMixin] for example code showcasing -/// how this mixin is used in combination with the -/// [SlottedMultiChildRenderObjectWidgetMixin]. -/// -/// See also: -/// -/// * [ContainerRenderObjectMixin], which organizes its children in a single -/// list. -mixin SlottedContainerRenderObjectMixin on RenderBox { - /// Returns the [RenderBox] child that is currently occupying the provided - /// `slot`. - /// - /// Returns null if no [RenderBox] is configured for the given slot. - @protected - RenderBox? childForSlot(S slot) => _slotToChild[slot]; - - /// Returns an [Iterable] of all non-null children. - /// - /// This getter is used by the default implementation of [attach], [detach], - /// [redepthChildren], [visitChildren], and [debugDescribeChildren] to iterate - /// over the children of this [RenderBox]. The base implementation makes no - /// guarantee about the order in which the children are returned. Subclasses, - /// for which the child order is important should override this getter and - /// return the children in the desired order. - @protected - Iterable get children => _slotToChild.values; - - /// Returns the debug name for a given `slot`. - /// - /// This method is called by [debugDescribeChildren] for each slot that is - /// currently occupied by a child to obtain a name for that slot for debug - /// outputs. - /// - /// The default implementation calls [EnumName.name] on `slot` it it is an - /// [Enum] value and `toString` if it is not. - @protected - String debugNameForSlot(S slot) { - if (slot is Enum) { - return slot.name; - } - return slot.toString(); - } - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - for (final RenderBox child in children) { - child.attach(owner); - } - } - - @override - void detach() { - super.detach(); - for (final RenderBox child in children) { - child.detach(); - } - } - - @override - void redepthChildren() { - children.forEach(redepthChild); - } - - @override - void visitChildren(RenderObjectVisitor visitor) { - children.forEach(visitor); - } - - @override - List debugDescribeChildren() { - final List value = []; - final Map childToSlot = Map.fromIterables( - _slotToChild.values, - _slotToChild.keys, - ); - for (final RenderBox child in children) { - _addDiagnostics(child, value, debugNameForSlot(childToSlot[child] as S)); - } - return value; - } - - void _addDiagnostics(RenderBox child, List value, String name) { - value.add(child.toDiagnosticsNode(name: name)); - } - - final Map _slotToChild = {}; - - void _setChild(RenderBox? child, S slot) { - final RenderBox? oldChild = _slotToChild[slot]; - if (oldChild != null) { - dropChild(oldChild); - _slotToChild.remove(slot); - } - if (child != null) { - _slotToChild[slot] = child; - adoptChild(child); - } - } -} - -/// Element used by the [SlottedMultiChildRenderObjectWidgetMixin]. -class SlottedRenderObjectElement extends RenderObjectElement { - /// Creates an element that uses the given widget as its configuration. - SlottedRenderObjectElement(SlottedMultiChildRenderObjectWidgetMixin widget) : super(widget); - - final Map _slotToChild = {}; - - @override - SlottedMultiChildRenderObjectWidgetMixin get widget => super.widget as SlottedMultiChildRenderObjectWidgetMixin; - - @override - SlottedContainerRenderObjectMixin get renderObject => super.renderObject as SlottedContainerRenderObjectMixin; - - @override - void visitChildren(ElementVisitor visitor) { - _slotToChild.values.forEach(visitor); - } - - @override - void forgetChild(Element child) { - assert(_slotToChild.containsValue(child)); - assert(child.slot is S); - assert(_slotToChild.containsKey(child.slot)); - _slotToChild.remove(child.slot); - super.forgetChild(child); - } - - @override - void mount(Element? parent, Object? newSlot) { - super.mount(parent, newSlot); - _updateChildren(); - } - - @override - void update(SlottedMultiChildRenderObjectWidgetMixin newWidget) { - super.update(newWidget); - assert(widget == newWidget); - _updateChildren(); - } - - List? _debugPreviousSlots; - - void _updateChildren() { - assert(() { - _debugPreviousSlots ??= widget.slots.toList(); - return listEquals(_debugPreviousSlots, widget.slots.toList()); - }(), '${widget.runtimeType}.slots must not change.'); - assert(widget.slots.toSet().length == widget.slots.length, 'slots must be unique'); - - for (final S slot in widget.slots) { - _updateChild(widget.childForSlot(slot), slot); - } - } - - void _updateChild(Widget? widget, S slot) { - final Element? oldChild = _slotToChild[slot]; - assert(oldChild == null || oldChild.slot == slot); // Reason why [moveRenderObjectChild] is not reachable. - final Element? newChild = updateChild(oldChild, widget, slot); - if (oldChild != null) { - _slotToChild.remove(slot); - } - if (newChild != null) { - _slotToChild[slot] = newChild; - } - } - - @override - void insertRenderObjectChild(RenderBox child, S slot) { - renderObject._setChild(child, slot); - assert(renderObject._slotToChild[slot] == child); - } - - @override - void removeRenderObjectChild(RenderBox child, S slot) { - assert(renderObject._slotToChild[slot] == child); - renderObject._setChild(null, slot); - assert(renderObject._slotToChild[slot] == null); - } - - @override - void moveRenderObjectChild(RenderBox child, Object? oldSlot, Object? newSlot) { - // Existing elements are never moved to a new slot, see assert in [_updateChild]. - assert(false, 'not reachable'); - } -} diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 62e702e79d..c424d6e9e3 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -116,7 +116,6 @@ export 'src/widgets/sliver_fill.dart'; export 'src/widgets/sliver_layout_builder.dart'; export 'src/widgets/sliver_persistent_header.dart'; export 'src/widgets/sliver_prototype_extent_list.dart'; -export 'src/widgets/slotted_render_object_widget.dart'; export 'src/widgets/spacer.dart'; export 'src/widgets/status_transitions.dart'; export 'src/widgets/table.dart'; diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index e229d00bf2..f4e0a7ced3 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -15,7 +15,7 @@ import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; Finder findRenderChipElement() { - return find.byElementPredicate((Element e) => '${e.renderObject.runtimeType}' == '_RenderChip'); + return find.byElementPredicate((Element e) => '${e.runtimeType}' == '_RenderChipElement'); } RenderBox getMaterialBox(WidgetTester tester) { diff --git a/packages/flutter/test/widgets/slotted_render_object_widget_test.dart b/packages/flutter/test/widgets/slotted_render_object_widget_test.dart deleted file mode 100644 index 7da4ae45a0..0000000000 --- a/packages/flutter/test/widgets/slotted_render_object_widget_test.dart +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2014 The Flutter 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/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../rendering/mock_canvas.dart'; - -const Color green = Color(0xFF00FF00); -const Color yellow = Color(0xFFFFFF00); - -void main() { - testWidgets('SlottedRenderObjectWidget test', (WidgetTester tester) async { - await tester.pumpWidget(buildWidget( - topLeft: Container( - height: 100, - width: 80, - color: yellow, - child: const Text('topLeft'), - ), - bottomRight: Container( - height: 120, - width: 110, - color: green, - child: const Text('bottomRight'), - ), - )); - - expect(find.text('topLeft'), findsOneWidget); - expect(find.text('bottomRight'), findsOneWidget); - expect(tester.getSize(find.byType(_Diagonal)), const Size(80 + 110, 100 + 120)); - expect(find.byType(_Diagonal), paints - ..rect( - rect: const Rect.fromLTWH(0, 0, 80, 100), - color: yellow, - ) - ..rect( - rect: const Rect.fromLTWH(80, 100, 110, 120), - color: green, - ) - ); - - await tester.pumpWidget(buildWidget( - topLeft: Container( - height: 200, - width: 100, - color: yellow, - child: const Text('topLeft'), - ), - bottomRight: Container( - height: 220, - width: 210, - color: green, - child: const Text('bottomRight'), - ), - )); - - expect(find.text('topLeft'), findsOneWidget); - expect(find.text('bottomRight'), findsOneWidget); - expect(tester.getSize(find.byType(_Diagonal)), const Size(100 + 210, 200 + 220)); - expect(find.byType(_Diagonal), paints - ..rect( - rect: const Rect.fromLTWH(0, 0, 100, 200), - color: yellow, - ) - ..rect( - rect: const Rect.fromLTWH(100, 200, 210, 220), - color: green, - ) - ); - - await tester.pumpWidget(buildWidget( - topLeft: Container( - height: 200, - width: 100, - color: yellow, - child: const Text('topLeft'), - ), - bottomRight: Container( - key: UniqueKey(), - height: 230, - width: 220, - color: green, - child: const Text('bottomRight'), - ), - )); - - expect(find.text('topLeft'), findsOneWidget); - expect(find.text('bottomRight'), findsOneWidget); - expect(tester.getSize(find.byType(_Diagonal)), const Size(100 + 220, 200 + 230)); - expect(find.byType(_Diagonal), paints - ..rect( - rect: const Rect.fromLTWH(0, 0, 100, 200), - color: yellow, - ) - ..rect( - rect: const Rect.fromLTWH(100, 200, 220, 230), - color: green, - ) - ); - - await tester.pumpWidget(buildWidget( - topLeft: Container( - height: 200, - width: 100, - color: yellow, - child: const Text('topLeft'), - ), - )); - - expect(find.text('topLeft'), findsOneWidget); - expect(find.text('bottomRight'), findsNothing); - expect(tester.getSize(find.byType(_Diagonal)), const Size(100, 200)); - expect(find.byType(_Diagonal), paints - ..rect( - rect: const Rect.fromLTWH(0, 0, 100, 200), - color: yellow, - ) - ); - - await tester.pumpWidget(buildWidget()); - expect(find.text('topLeft'), findsNothing); - expect(find.text('bottomRight'), findsNothing); - expect(tester.getSize(find.byType(_Diagonal)), Size.zero); - expect(find.byType(_Diagonal), paintsNothing); - - await tester.pumpWidget(Container()); - expect(find.byType(_Diagonal), findsNothing); - }); - - test('nameForSlot', () { - expect(_RenderDiagonal().publicNameForSlot(_DiagonalSlot.bottomRight), 'bottomRight'); - expect(_RenderDiagonal().publicNameForSlot(_DiagonalSlot.topLeft), 'topLeft'); - final _Slot slot = _Slot(); - expect(_RenderTest().publicNameForSlot(slot), slot.toString()); - }); - - testWidgets('debugDescribeChildren', (WidgetTester tester) async { - await tester.pumpWidget(buildWidget( - topLeft: const SizedBox( - height: 100, - width: 80, - ), - bottomRight: const SizedBox( - height: 120, - width: 110, - ), - )); - - expect( - tester.renderObject(find.byType(_Diagonal)).toStringDeep(), - equalsIgnoringHashCodes(r''' -_RenderDiagonal#00000 relayoutBoundary=up1 - │ creator: _Diagonal ← Align ← Directionality ← [root] - │ parentData: offset=Offset(0.0, 0.0) (can use size) - │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0) - │ size: Size(190.0, 220.0) - │ - ├─topLeft: RenderConstrainedBox#00000 relayoutBoundary=up2 - │ creator: SizedBox ← _Diagonal ← Align ← Directionality ← [root] - │ parentData: offset=Offset(0.0, 0.0) (can use size) - │ constraints: BoxConstraints(unconstrained) - │ size: Size(80.0, 100.0) - │ additionalConstraints: BoxConstraints(w=80.0, h=100.0) - │ - └─bottomRight: RenderConstrainedBox#00000 relayoutBoundary=up2 - creator: SizedBox ← _Diagonal ← Align ← Directionality ← [root] - parentData: offset=Offset(80.0, 100.0) (can use size) - constraints: BoxConstraints(unconstrained) - size: Size(110.0, 120.0) - additionalConstraints: BoxConstraints(w=110.0, h=120.0) -''') - ); - }); -} - -Widget buildWidget({Widget? topLeft, Widget? bottomRight}) { - return Directionality( - textDirection: TextDirection.ltr, - child: Align( - alignment: Alignment.topLeft, - child: _Diagonal( - topLeft: topLeft, - bottomRight: bottomRight, - ), - ), - ); -} - -enum _DiagonalSlot { - topLeft, - bottomRight, -} - -class _Diagonal extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<_DiagonalSlot> { - const _Diagonal({ - Key? key, - this.topLeft, - this.bottomRight, - this.backgroundColor, - }) : super(key: key); - - final Widget? topLeft; - final Widget? bottomRight; - final Color? backgroundColor; - - @override - Iterable<_DiagonalSlot> get slots => _DiagonalSlot.values; - - @override - Widget? childForSlot(Object slot) { - switch (slot) { - case _DiagonalSlot.topLeft: - return topLeft; - case _DiagonalSlot.bottomRight: - return bottomRight; - } - } - - @override - SlottedContainerRenderObjectMixin<_DiagonalSlot> createRenderObject( - BuildContext context, - ) { - return _RenderDiagonal(); - } -} - -class _RenderDiagonal extends RenderBox with SlottedContainerRenderObjectMixin<_DiagonalSlot> { - RenderBox? get _topLeft => childForSlot(_DiagonalSlot.topLeft); - RenderBox? get _bottomRight => childForSlot(_DiagonalSlot.bottomRight); - - @override - void performLayout() { - const BoxConstraints childConstraints = BoxConstraints(); - - Size topLeftSize = Size.zero; - if (_topLeft != null) { - _topLeft!.layout(childConstraints, parentUsesSize: true); - _positionChild(_topLeft!, Offset.zero); - topLeftSize = _topLeft!.size; - } - - Size bottomRightSize = Size.zero; - if (_bottomRight != null) { - _bottomRight!.layout(childConstraints, parentUsesSize: true); - _positionChild( - _bottomRight!, - Offset(topLeftSize.width, topLeftSize.height), - ); - bottomRightSize = _bottomRight!.size; - } - - size = constraints.constrain(Size( - topLeftSize.width + bottomRightSize.width, - topLeftSize.height + bottomRightSize.height, - )); - } - - void _positionChild(RenderBox child, Offset offset) { - (child.parentData! as BoxParentData).offset = offset; - } - - @override - void paint(PaintingContext context, Offset offset) { - if (_topLeft != null) { - _paintChild(_topLeft!, context, offset); - } - if (_bottomRight != null) { - _paintChild(_bottomRight!, context, offset); - } - } - - void _paintChild(RenderBox child, PaintingContext context, Offset offset) { - final BoxParentData childParentData = child.parentData! as BoxParentData; - context.paintChild(child, childParentData.offset + offset); - } - - String publicNameForSlot(_DiagonalSlot slot) => debugNameForSlot(slot); -} - -class _Slot { - @override - String toString() => describeIdentity(this); -} - -class _RenderTest extends RenderBox with SlottedContainerRenderObjectMixin<_Slot> { - String publicNameForSlot(_Slot slot) => debugNameForSlot(slot); -}