blob: ac66e3b696fd742b532456b5de269f1c43579514 [file] [log] [blame]
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
/// A widget that simplify the writing of deeply nested widget trees.
///
/// It relies on the new kind of widget [SingleChildWidget], which has two
/// concrete implementations:
/// - [SingleChildStatelessWidget]
/// - [SingleChildStatefulWidget]
///
/// They are both respectively a [SingleChildWidget] variant of [StatelessWidget]
/// and [StatefulWidget].
///
/// The difference between a widget and its single-child variant is that they have
/// a custom `build` method that takes an extra parameter.
///
/// As such, a `StatelessWidget` would be:
///
/// ```dart
/// class MyWidget extends StatelessWidget {
/// MyWidget({Key key, this.child}): super(key: key);
///
/// final Widget child;
///
/// @override
/// Widget build(BuildContext context) {
/// return SomethingWidget(child: child);
/// }
/// }
/// ```
///
/// Whereas a [SingleChildStatelessWidget] would be:
///
/// ```dart
/// class MyWidget extends SingleChildStatelessWidget {
/// MyWidget({Key key, Widget child}): super(key: key, child: child);
///
/// @override
/// Widget buildWithChild(BuildContext context, Widget child) {
/// return SomethingWidget(child: child);
/// }
/// }
/// ```
///
/// This allows our new `MyWidget` to be used both with:
///
/// ```dart
/// MyWidget(
/// child: AnotherWidget(),
/// )
/// ```
///
/// and to be placed inside `children` of [Nested] like so:
///
/// ```dart
/// Nested(
/// children: [
/// MyWidget(),
/// ...
/// ],
/// child: AnotherWidget(),
/// )
/// ```
class Nested extends StatelessWidget implements SingleChildWidget {
/// Allows configuring key, children and child
Nested({
Key? key,
required List<SingleChildWidget> children,
Widget? child,
}) : assert(children.isNotEmpty),
_children = children,
_child = child,
super(key: key);
final List<SingleChildWidget> _children;
final Widget? _child;
@override
Widget build(BuildContext context) {
throw StateError('implemented internally');
}
@override
_NestedElement createElement() => _NestedElement(this);
}
class _NestedElement extends StatelessElement
with SingleChildWidgetElementMixin {
_NestedElement(Nested widget) : super(widget);
@override
Nested get widget => super.widget as Nested;
final nodes = <_NestedHookElement>{};
@override
Widget build() {
_NestedHook? nestedHook;
var nextNode = _parent?.injectedChild ?? widget._child;
for (final child in widget._children.reversed) {
nextNode = nestedHook = _NestedHook(
owner: this,
wrappedWidget: child,
injectedChild: nextNode,
);
}
if (nestedHook != null) {
// We manually update _NestedHookElement instead of letter widgets do their thing
// because an item N may be constant but N+1 not. So, if we used widgets
// then N+1 wouldn't rebuild because N didn't change
for (final node in nodes) {
node
..wrappedChild = nestedHook!.wrappedWidget
..injectedChild = nestedHook.injectedChild;
final next = nestedHook.injectedChild;
if (next is _NestedHook) {
nestedHook = next;
} else {
break;
}
}
}
return nextNode!;
}
}
class _NestedHook extends StatelessWidget {
_NestedHook({
this.injectedChild,
required this.wrappedWidget,
required this.owner,
});
final SingleChildWidget wrappedWidget;
final Widget? injectedChild;
final _NestedElement owner;
@override
_NestedHookElement createElement() => _NestedHookElement(this);
@override
Widget build(BuildContext context) => throw StateError('handled internally');
}
class _NestedHookElement extends StatelessElement {
_NestedHookElement(_NestedHook widget) : super(widget);
@override
_NestedHook get widget => super.widget as _NestedHook;
Widget? _injectedChild;
Widget? get injectedChild => _injectedChild;
set injectedChild(Widget? value) {
final previous = _injectedChild;
if (value is _NestedHook &&
previous is _NestedHook &&
Widget.canUpdate(value.wrappedWidget, previous.wrappedWidget)) {
// no need to rebuild the wrapped widget just for a _NestedHook.
// The widget doesn't matter here, only its Element.
return;
}
if (previous != value) {
_injectedChild = value;
visitChildren((e) => e.markNeedsBuild());
}
}
SingleChildWidget? _wrappedChild;
SingleChildWidget? get wrappedChild => _wrappedChild;
set wrappedChild(SingleChildWidget? value) {
if (_wrappedChild != value) {
_wrappedChild = value;
markNeedsBuild();
}
}
@override
void mount(Element? parent, dynamic newSlot) {
widget.owner.nodes.add(this);
_wrappedChild = widget.wrappedWidget;
_injectedChild = widget.injectedChild;
super.mount(parent, newSlot);
}
@override
void unmount() {
widget.owner.nodes.remove(this);
super.unmount();
}
@override
Widget build() {
return wrappedChild!;
}
}
/// A [Widget] that takes a single descendant.
///
/// As opposed to [ProxyWidget], it may have a "build" method.
///
/// See also:
/// - [SingleChildStatelessWidget]
/// - [SingleChildStatefulWidget]
abstract class SingleChildWidget implements Widget {
@override
SingleChildWidgetElementMixin createElement();
}
mixin SingleChildWidgetElementMixin on Element {
_NestedHookElement? _parent;
@override
void mount(Element? parent, dynamic newSlot) {
if (parent is _NestedHookElement?) {
_parent = parent;
}
super.mount(parent, newSlot);
}
@override
void activate() {
super.activate();
visitAncestorElements((parent) {
if (parent is _NestedHookElement) {
_parent = parent;
}
return false;
});
}
}
/// A [StatelessWidget] that implements [SingleChildWidget] and is therefore
/// compatible with [Nested].
///
/// Its [build] method must **not** be overriden. Instead use [buildWithChild].
abstract class SingleChildStatelessWidget extends StatelessWidget
implements SingleChildWidget {
/// Creates a widget that has exactly one child widget.
const SingleChildStatelessWidget({Key? key, Widget? child})
: _child = child,
super(key: key);
final Widget? _child;
/// A [build] method that receives an extra `child` parameter.
///
/// This method may be called with a `child` different from the parameter
/// passed to the constructor of [SingleChildStatelessWidget].
/// It may also be called again with a different `child`, without this widget
/// being recreated.
Widget buildWithChild(BuildContext context, Widget? child);
@override
Widget build(BuildContext context) => buildWithChild(context, _child);
@override
SingleChildStatelessElement createElement() {
return SingleChildStatelessElement(this);
}
}
/// An [Element] that uses a [SingleChildStatelessWidget] as its configuration.
class SingleChildStatelessElement extends StatelessElement
with SingleChildWidgetElementMixin {
/// Creates an element that uses the given widget as its configuration.
SingleChildStatelessElement(SingleChildStatelessWidget widget)
: super(widget);
@override
Widget build() {
if (_parent != null) {
return widget.buildWithChild(this, _parent!.injectedChild);
}
return super.build();
}
@override
SingleChildStatelessWidget get widget =>
super.widget as SingleChildStatelessWidget;
}
/// A [StatefulWidget] that is compatible with [Nested].
abstract class SingleChildStatefulWidget extends StatefulWidget
implements SingleChildWidget {
/// Creates a widget that has exactly one child widget.
const SingleChildStatefulWidget({Key? key, Widget? child})
: _child = child,
super(key: key);
final Widget? _child;
@override
SingleChildStatefulElement createElement() {
return SingleChildStatefulElement(this);
}
}
/// A [State] for [SingleChildStatefulWidget].
///
/// Do not override [build] and instead override [buildWithChild].
abstract class SingleChildState<T extends SingleChildStatefulWidget>
extends State<T> {
/// A [build] method that receives an extra `child` parameter.
///
/// This method may be called with a `child` different from the parameter
/// passed to the constructor of [SingleChildStatelessWidget].
/// It may also be called again with a different `child`, without this widget
/// being recreated.
Widget buildWithChild(BuildContext context, Widget? child);
@override
Widget build(BuildContext context) => buildWithChild(context, widget._child);
}
/// An [Element] that uses a [SingleChildStatefulWidget] as its configuration.
class SingleChildStatefulElement extends StatefulElement
with SingleChildWidgetElementMixin {
/// Creates an element that uses the given widget as its configuration.
SingleChildStatefulElement(SingleChildStatefulWidget widget) : super(widget);
@override
SingleChildStatefulWidget get widget =>
super.widget as SingleChildStatefulWidget;
@override
SingleChildState<SingleChildStatefulWidget> get state =>
super.state as SingleChildState<SingleChildStatefulWidget>;
@override
Widget build() {
if (_parent != null) {
return state.buildWithChild(this, _parent!.injectedChild!);
}
return super.build();
}
}
/// A [SingleChildWidget] that delegates its implementation to a callback.
///
/// It works like [Builder], but is compatible with [Nested].
class SingleChildBuilder extends SingleChildStatelessWidget {
/// Creates a widget that delegates its build to a callback.
///
/// The [builder] argument must not be null.
const SingleChildBuilder({Key? key, required this.builder, Widget? child})
: super(key: key, child: child);
/// Called to obtain the child widget.
///
/// The `child` parameter may be different from the one parameter passed to
/// the constructor of [SingleChildBuilder].
final Widget Function(BuildContext context, Widget? child) builder;
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return builder(context, child);
}
}
mixin SingleChildStatelessWidgetMixin
implements StatelessWidget, SingleChildStatelessWidget {
Widget? get child;
@override
Widget? get _child => child;
@override
SingleChildStatelessElement createElement() {
return SingleChildStatelessElement(this);
}
@override
Widget build(BuildContext context) {
return buildWithChild(context, child);
}
}
mixin SingleChildStatefulWidgetMixin on StatefulWidget
implements SingleChildWidget {
Widget? get child;
@override
_SingleChildStatefulMixinElement createElement() =>
_SingleChildStatefulMixinElement(this);
}
mixin SingleChildStateMixin<T extends StatefulWidget> on State<T> {
Widget buildWithChild(BuildContext context, Widget child);
@override
Widget build(BuildContext context) {
return buildWithChild(
context,
(widget as SingleChildStatefulWidgetMixin).child!,
);
}
}
class _SingleChildStatefulMixinElement extends StatefulElement
with SingleChildWidgetElementMixin {
_SingleChildStatefulMixinElement(SingleChildStatefulWidgetMixin widget)
: super(widget);
@override
SingleChildStatefulWidgetMixin get widget =>
super.widget as SingleChildStatefulWidgetMixin;
@override
SingleChildStateMixin<StatefulWidget> get state =>
super.state as SingleChildStateMixin<StatefulWidget>;
@override
Widget build() {
if (_parent != null) {
return state.buildWithChild(this, _parent!.injectedChild!);
}
return super.build();
}
}
mixin SingleChildInheritedElementMixin
on InheritedElement, SingleChildWidgetElementMixin {
@override
Widget build() {
if (_parent != null) {
return _parent!.injectedChild!;
}
return super.build();
}
}