mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This commit is contained in:
parent
612c5f58fd
commit
f4a44a9d68
@ -0,0 +1,289 @@
|
||||
// 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());
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// 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));
|
||||
}
|
@ -2059,7 +2059,7 @@ class _RenderChipRedirectingHitDetection extends RenderConstrainedBox {
|
||||
}
|
||||
}
|
||||
|
||||
class _ChipRenderWidget extends RenderObjectWidget {
|
||||
class _ChipRenderWidget extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<_ChipSlot> {
|
||||
const _ChipRenderWidget({
|
||||
Key? key,
|
||||
required this.theme,
|
||||
@ -2083,7 +2083,19 @@ class _ChipRenderWidget extends RenderObjectWidget {
|
||||
final ShapeBorder? avatarBorder;
|
||||
|
||||
@override
|
||||
_RenderChipElement createElement() => _RenderChipElement(this);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, _RenderChip renderObject) {
|
||||
@ -2100,7 +2112,7 @@ class _ChipRenderWidget extends RenderObjectWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
SlottedContainerRenderObjectMixin<_ChipSlot> createRenderObject(BuildContext context) {
|
||||
return _RenderChip(
|
||||
theme: theme,
|
||||
textDirection: Directionality.of(context),
|
||||
@ -2121,105 +2133,6 @@ 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({
|
||||
@ -2286,7 +2199,7 @@ class _ChipRenderTheme {
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderChip extends RenderBox {
|
||||
class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_ChipSlot> {
|
||||
_RenderChip({
|
||||
required _ChipRenderTheme theme,
|
||||
required TextDirection textDirection,
|
||||
@ -2307,8 +2220,6 @@ class _RenderChip extends RenderBox {
|
||||
enableAnimation.addListener(markNeedsPaint);
|
||||
}
|
||||
|
||||
final Map<_ChipSlot, RenderBox> children = <_ChipSlot, RenderBox>{};
|
||||
|
||||
bool? value;
|
||||
bool? isEnabled;
|
||||
late Rect _deleteButtonRect;
|
||||
@ -2319,35 +2230,9 @@ class _RenderChip extends RenderBox {
|
||||
Animation<double> enableAnimation;
|
||||
ShapeBorder? avatarBorder;
|
||||
|
||||
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);
|
||||
}
|
||||
RenderBox? get avatar => childForSlot(_ChipSlot.avatar);
|
||||
RenderBox? get deleteIcon => childForSlot(_ChipSlot.deleteIcon);
|
||||
RenderBox? get label => childForSlot(_ChipSlot.label);
|
||||
|
||||
_ChipRenderTheme get theme => _theme;
|
||||
_ChipRenderTheme _theme;
|
||||
@ -2370,7 +2255,8 @@ class _RenderChip extends RenderBox {
|
||||
}
|
||||
|
||||
// The returned list is ordered for hit testing.
|
||||
Iterable<RenderBox> get _children sync* {
|
||||
@override
|
||||
Iterable<RenderBox> get children sync* {
|
||||
if (avatar != null) {
|
||||
yield avatar!;
|
||||
}
|
||||
@ -2385,47 +2271,6 @@ class _RenderChip extends RenderBox {
|
||||
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;
|
||||
|
||||
|
@ -682,7 +682,7 @@ class _RenderDecorationLayout {
|
||||
}
|
||||
|
||||
// The workhorse: layout and paint a _Decorator widget's _Decoration.
|
||||
class _RenderDecoration extends RenderBox {
|
||||
class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin<_DecorationSlot> {
|
||||
_RenderDecoration({
|
||||
required _Decoration decoration,
|
||||
required TextDirection textDirection,
|
||||
@ -702,88 +702,22 @@ class _RenderDecoration extends RenderBox {
|
||||
_expands = expands;
|
||||
|
||||
static const double subtextGap = 8.0;
|
||||
final Map<_DecorationSlot, RenderBox> children = <_DecorationSlot, RenderBox>{};
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
||||
// The returned list is ordered for hit testing.
|
||||
Iterable<RenderBox> get _children sync* {
|
||||
@override
|
||||
Iterable<RenderBox> get children sync* {
|
||||
if (icon != null)
|
||||
yield icon!;
|
||||
if (input != null)
|
||||
@ -882,30 +816,6 @@ class _RenderDecoration extends RenderBox {
|
||||
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)
|
||||
@ -940,27 +850,6 @@ class _RenderDecoration extends RenderBox {
|
||||
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;
|
||||
|
||||
@ -1638,7 +1527,7 @@ class _RenderDecoration extends RenderBox {
|
||||
@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(
|
||||
@ -1667,146 +1556,7 @@ class _RenderDecoration extends RenderBox {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
class _Decorator extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<_DecorationSlot> {
|
||||
const _Decorator({
|
||||
Key? key,
|
||||
required this.textAlignVertical,
|
||||
@ -1829,7 +1579,35 @@ class _Decorator extends RenderObjectWidget {
|
||||
final bool expands;
|
||||
|
||||
@override
|
||||
_DecorationElement createElement() => _DecorationElement(this);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
_RenderDecoration createRenderObject(BuildContext context) {
|
||||
|
@ -1266,7 +1266,7 @@ enum _ListTileSlot {
|
||||
trailing,
|
||||
}
|
||||
|
||||
class _ListTile extends RenderObjectWidget {
|
||||
class _ListTile extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<_ListTileSlot> {
|
||||
const _ListTile({
|
||||
Key? key,
|
||||
this.leading,
|
||||
@ -1307,7 +1307,21 @@ class _ListTile extends RenderObjectWidget {
|
||||
final double minLeadingWidth;
|
||||
|
||||
@override
|
||||
_ListTileElement createElement() => _ListTileElement(this);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
_RenderListTile createRenderObject(BuildContext context) {
|
||||
@ -1339,111 +1353,7 @@ class _ListTile extends RenderObjectWidget {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_ListTileSlot> {
|
||||
_RenderListTile({
|
||||
required bool isDense,
|
||||
required VisualDensity visualDensity,
|
||||
@ -1472,46 +1382,14 @@ class _RenderListTile extends RenderBox {
|
||||
_minVerticalPadding = minVerticalPadding,
|
||||
_minLeadingWidth = minLeadingWidth;
|
||||
|
||||
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);
|
||||
}
|
||||
RenderBox? get leading => childForSlot(_ListTileSlot.leading);
|
||||
RenderBox? get title => childForSlot(_ListTileSlot.title);
|
||||
RenderBox? get subtitle => childForSlot(_ListTileSlot.subtitle);
|
||||
RenderBox? get trailing => childForSlot(_ListTileSlot.trailing);
|
||||
|
||||
// The returned list is ordered for hit testing.
|
||||
Iterable<RenderBox> get _children sync* {
|
||||
@override
|
||||
Iterable<RenderBox> get children sync* {
|
||||
if (leading != null)
|
||||
yield leading!;
|
||||
if (title != null)
|
||||
@ -1615,44 +1493,6 @@ class _RenderListTile extends RenderBox {
|
||||
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;
|
||||
|
||||
@ -1905,7 +1745,7 @@ class _RenderListTile extends RenderBox {
|
||||
@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,
|
||||
|
@ -3167,6 +3167,11 @@ 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;
|
||||
|
@ -1677,6 +1677,13 @@ 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.
|
||||
@ -1767,7 +1774,11 @@ abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
|
||||
/// See also:
|
||||
///
|
||||
/// * [Stack], which uses [MultiChildRenderObjectWidget].
|
||||
/// * [RenderStack], for an example implementation of the associated render object.
|
||||
/// * [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.
|
||||
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
|
||||
/// Initializes fields for subclasses.
|
||||
///
|
||||
|
@ -0,0 +1,271 @@
|
||||
// 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');
|
||||
}
|
||||
}
|
@ -116,6 +116,7 @@ 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';
|
||||
|
@ -15,7 +15,7 @@ import '../widgets/semantics_tester.dart';
|
||||
import 'feedback_tester.dart';
|
||||
|
||||
Finder findRenderChipElement() {
|
||||
return find.byElementPredicate((Element e) => '${e.runtimeType}' == '_RenderChipElement');
|
||||
return find.byElementPredicate((Element e) => '${e.renderObject.runtimeType}' == '_RenderChip');
|
||||
}
|
||||
|
||||
RenderBox getMaterialBox(WidgetTester tester) {
|
||||
|
@ -0,0 +1,292 @@
|
||||
// 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user