Revert "Mixin for slotted RenderObjectWidgets and RenderBox (#94077)" (#94620)

This reverts commit 988959dad9.
This commit is contained in:
Michael Goderbauer 2021-12-03 10:39:11 -08:00 committed by GitHub
parent 13f25dddc2
commit 885a1482f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 629 additions and 1002 deletions

View file

@ -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<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(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<DiagonalSlot> createRenderObject(
BuildContext context,
) {
return RenderDiagonal(
backgroundColor: backgroundColor,
);
}
@override
void updateRenderObject(
BuildContext context,
SlottedContainerRenderObjectMixin<DiagonalSlot> 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<DiagonalSlot>, 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<RenderBox> 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());
}

View file

@ -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));
}

View file

@ -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<double> 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<RenderBox> get children sync* {
Iterable<RenderBox> 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<DiagnosticsNode> debugDescribeChildren() {
final List<DiagnosticsNode> value = <DiagnosticsNode>[];
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;

View file

@ -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<RenderBox> get children sync* {
Iterable<RenderBox> 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<DiagnosticsNode> debugDescribeChildren() {
final List<DiagnosticsNode> value = <DiagnosticsNode>[];
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) {

View file

@ -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<RenderBox> get children sync* {
Iterable<RenderBox> 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<DiagnosticsNode> debugDescribeChildren() {
final List<DiagnosticsNode> value = <DiagnosticsNode>[];
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,

View file

@ -3167,11 +3167,6 @@ mixin ContainerParentDataMixin<ChildType extends RenderObject> 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<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {
bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType? equals }) {
ParentDataType childParentData = child.parentData! as ParentDataType;

View file

@ -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.
///

View file

@ -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<S> 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<S> 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<S> createRenderObject(BuildContext context);
@override
void updateRenderObject(BuildContext context, SlottedContainerRenderObjectMixin<S> renderObject);
@override
SlottedRenderObjectElement<S> createElement() => SlottedRenderObjectElement<S>(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<S> 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<RenderBox> 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<DiagnosticsNode> debugDescribeChildren() {
final List<DiagnosticsNode> value = <DiagnosticsNode>[];
final Map<RenderBox, S> childToSlot = Map<RenderBox, S>.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<DiagnosticsNode> value, String name) {
value.add(child.toDiagnosticsNode(name: name));
}
final Map<S, RenderBox> _slotToChild = <S, RenderBox>{};
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<S> extends RenderObjectElement {
/// Creates an element that uses the given widget as its configuration.
SlottedRenderObjectElement(SlottedMultiChildRenderObjectWidgetMixin<S> widget) : super(widget);
final Map<S, Element> _slotToChild = <S, Element>{};
@override
SlottedMultiChildRenderObjectWidgetMixin<S> get widget => super.widget as SlottedMultiChildRenderObjectWidgetMixin<S>;
@override
SlottedContainerRenderObjectMixin<S> get renderObject => super.renderObject as SlottedContainerRenderObjectMixin<S>;
@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<S> newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_updateChildren();
}
List<S>? _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');
}
}

View file

@ -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';

View file

@ -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) {

View file

@ -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);
}