Skip to content

Commit

Permalink
Add Overlay.wrap for convenience (#139823)
Browse files Browse the repository at this point in the history
Towards flutter/flutter#137875.

The convenient `Overlay.wrap` function makes it easy to wrap a child with an Overlay so other visual elements can float on top of the child. This is useful if you want to get things like text selection working (i.e. with a SelectionArea) without using a Navigator.
  • Loading branch information
goderbauer authored Dec 8, 2023
1 parent fb37f9f commit 1d5e23a
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
47 changes: 47 additions & 0 deletions packages/flutter/lib/src/widgets/overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,20 @@ class Overlay extends StatefulWidget {
this.clipBehavior = Clip.hardEdge,
});

/// Wrap the provided `child` in an [Overlay] to allow other visual elements
/// (packed in [OverlayEntry]s) to float on top of the child.
///
/// This is a convenience method over the regular [Overlay] constructor: It
/// creates an [Overlay] and puts the provided `child` in an [OverlayEntry]
/// at the bottom of that newly created Overlay.
static Widget wrap({
Key? key,
Clip clipBehavior = Clip.hardEdge,
required Widget child,
}) {
return _WrappingOverlay(key: key, clipBehavior: clipBehavior, child: child);
}

/// The entries to include in the overlay initially.
///
/// These entries are only used when the [OverlayState] is initialized. If you
Expand Down Expand Up @@ -804,6 +818,39 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
}
}

class _WrappingOverlay extends StatefulWidget {
const _WrappingOverlay({super.key, this.clipBehavior = Clip.hardEdge, required this.child});

final Clip clipBehavior;
final Widget child;

@override
State<_WrappingOverlay> createState() => _WrappingOverlayState();
}

class _WrappingOverlayState extends State<_WrappingOverlay> {
late final OverlayEntry _entry = OverlayEntry(
opaque: true,
builder: (BuildContext context) {
return widget.child;
}
);

@override
void didUpdateWidget(_WrappingOverlay oldWidget) {
super.didUpdateWidget(oldWidget);
_entry.markNeedsBuild();
}

@override
Widget build(BuildContext context) {
return Overlay(
clipBehavior: widget.clipBehavior,
initialEntries: <OverlayEntry>[_entry],
);
}
}

/// Special version of a [Stack], that doesn't layout and render the first
/// [skipCount] children.
///
Expand Down
32 changes: 32 additions & 0 deletions packages/flutter/test/widgets/overlay_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,38 @@ void main() {
);
});
});

testWidgets('Overlay.wrap', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay.wrap(
child: const Center(
child: Text('Hello World'),
),
),
),
);

final State overlayState = tester.state(find.byType(Overlay));
expect(find.text('Hello World'), findsOneWidget);
expect(find.text('Bye, bye'), findsNothing);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay.wrap(
child: const Center(
child: Text('Bye, bye'),
),
),
),
);

expect(find.text('Hello World'), findsNothing);
expect(find.text('Bye, bye'), findsOneWidget);
expect(tester.state(find.byType(Overlay)), same(overlayState));
});
}

class StatefulTestWidget extends StatefulWidget {
Expand Down

0 comments on commit 1d5e23a

Please sign in to comment.