Unverified Commit e0ac5da1 authored by TheBiirb's avatar TheBiirb Committed by GitHub

Added clipBehavior to Overlay, Flow, AnimatedSize and AndroidView (#65910)

parent 3e838da9
......@@ -81,10 +81,13 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
AlignmentGeometry alignment = Alignment.center,
TextDirection? textDirection,
RenderBox? child,
Clip clipBehavior = Clip.hardEdge,
}) : assert(vsync != null),
assert(duration != null),
assert(curve != null),
assert(clipBehavior != null),
_vsync = vsync,
_clipBehavior = clipBehavior,
super(child: child, alignment: alignment, textDirection: textDirection) {
_controller = AnimationController(
vsync: vsync,
......@@ -139,6 +142,20 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
_animation.curve = value;
}
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge], and must not be null.
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior = Clip.hardEdge;
set clipBehavior(Clip value) {
assert(value != null);
if (value != _clipBehavior) {
_clipBehavior = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
}
/// Whether the size is being currently animated towards the child's size.
///
/// See [RenderAnimatedSizeState] for situations when we may not be animating
......@@ -275,9 +292,9 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
@override
void paint(PaintingContext context, Offset offset) {
if (child != null && _hasVisualOverflow) {
if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) {
final Rect rect = Offset.zero & size;
context.pushClipRect(needsCompositing, offset, rect, super.paint);
context.pushClipRect(needsCompositing, offset, rect, super.paint, clipBehavior: clipBehavior);
} else {
super.paint(context, offset);
}
......
......@@ -181,8 +181,11 @@ class RenderFlow extends RenderBox
RenderFlow({
List<RenderBox>? children,
required FlowDelegate delegate,
Clip clipBehavior = Clip.hardEdge,
}) : assert(delegate != null),
_delegate = delegate {
assert(clipBehavior != null),
_delegate = delegate,
_clipBehavior = clipBehavior {
addAll(children);
}
......@@ -221,6 +224,20 @@ class RenderFlow extends RenderBox
}
}
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge], and must not be null.
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior = Clip.hardEdge;
set clipBehavior(Clip value) {
assert(value != null);
if (value != _clipBehavior) {
_clipBehavior = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
......@@ -365,7 +382,11 @@ class RenderFlow extends RenderBox
@override
void paint(PaintingContext context, Offset offset) {
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintWithDelegate);
if (clipBehavior == Clip.none) {
_paintWithDelegate(context, offset);
} else {
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintWithDelegate, clipBehavior: clipBehavior);
}
}
@override
......
......@@ -83,10 +83,13 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
required AndroidViewController viewController,
required PlatformViewHitTestBehavior hitTestBehavior,
required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
Clip clipBehavior = Clip.hardEdge,
}) : assert(viewController != null),
assert(hitTestBehavior != null),
assert(gestureRecognizers != null),
_viewController = viewController {
assert(clipBehavior != null),
_viewController = viewController,
_clipBehavior = clipBehavior {
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
updateGestureRecognizers(gestureRecognizers);
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
......@@ -115,6 +118,20 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
}
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge], and must not be null.
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior = Clip.hardEdge;
set clipBehavior(Clip value) {
assert(value != null);
if (value != _clipBehavior) {
_clipBehavior = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
}
void _onPlatformViewCreated(int id) {
markNeedsSemanticsUpdate();
}
......@@ -188,8 +205,8 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
// Clip the texture if it's going to paint out of the bounds of the renter box
// (see comment in _paintTexture for an explanation of when this happens).
if (size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height) {
context.pushClipRect(true, offset, offset & size, _paintTexture);
if (size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height && clipBehavior != Clip.none) {
context.pushClipRect(true, offset, offset & size, _paintTexture, clipBehavior: clipBehavior);
return;
}
......
......@@ -61,7 +61,9 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
required this.duration,
this.reverseDuration,
required this.vsync,
}) : super(key: key, child: child);
this.clipBehavior = Clip.hardEdge,
}) : assert(clipBehavior != null),
super(key: key, child: child);
/// The alignment of the child within the parent when the parent is not yet
/// the same size as the child.
......@@ -101,6 +103,11 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
/// The [TickerProvider] for this widget.
final TickerProvider vsync;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge], and must not be null.
final Clip clipBehavior;
@override
RenderAnimatedSize createRenderObject(BuildContext context) {
return RenderAnimatedSize(
......@@ -110,6 +117,7 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
curve: curve,
vsync: vsync,
textDirection: Directionality.of(context),
clipBehavior: clipBehavior,
);
}
......@@ -121,7 +129,8 @@ class AnimatedSize extends SingleChildRenderObjectWidget {
..reverseDuration = reverseDuration
..curve = curve
..vsync = vsync
..textDirection = Directionality.of(context);
..textDirection = Directionality.of(context)
..clipBehavior = clipBehavior;
}
@override
......
......@@ -5091,7 +5091,9 @@ class Flow extends MultiChildRenderObjectWidget {
Key? key,
required this.delegate,
List<Widget> children = const <Widget>[],
this.clipBehavior = Clip.hardEdge,
}) : assert(delegate != null),
assert(clipBehavior != null),
super(key: key, children: RepaintBoundary.wrapAll(children));
// https://github.com/dart-lang/sdk/issues/29277
......@@ -5106,18 +5108,26 @@ class Flow extends MultiChildRenderObjectWidget {
Key? key,
required this.delegate,
List<Widget> children = const <Widget>[],
this.clipBehavior = Clip.hardEdge,
}) : assert(delegate != null),
assert(clipBehavior != null),
super(key: key, children: children);
/// The delegate that controls the transformation matrices of the children.
final FlowDelegate delegate;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
@override
RenderFlow createRenderObject(BuildContext context) => RenderFlow(delegate: delegate);
RenderFlow createRenderObject(BuildContext context) => RenderFlow(delegate: delegate, clipBehavior: clipBehavior);
@override
void updateRenderObject(BuildContext context, RenderFlow renderObject) {
renderObject.delegate = delegate;
renderObject.clipBehavior = clipBehavior;
}
}
......
......@@ -213,7 +213,9 @@ class Overlay extends StatefulWidget {
const Overlay({
Key? key,
this.initialEntries = const <OverlayEntry>[],
this.clipBehavior = Clip.hardEdge,
}) : assert(initialEntries != null),
assert(clipBehavior != null),
super(key: key);
/// The entries to include in the overlay initially.
......@@ -231,6 +233,11 @@ class Overlay extends StatefulWidget {
/// To remove an entry from an [Overlay], use [OverlayEntry.remove].
final List<OverlayEntry> initialEntries;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge], and must not be null.
final Clip clipBehavior;
/// The state from the closest instance of this class that encloses the given context.
///
/// In debug mode, if the `debugRequiredFor` argument is provided then this
......@@ -470,6 +477,7 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
return _Theatre(
skipCount: children.length - onstageCount,
children: children.reversed.toList(growable: false),
clipBehavior: widget.clipBehavior,
);
}
......@@ -490,15 +498,19 @@ class _Theatre extends MultiChildRenderObjectWidget {
_Theatre({
Key? key,
this.skipCount = 0,
this.clipBehavior = Clip.hardEdge,
List<Widget> children = const <Widget>[],
}) : assert(skipCount != null),
assert(skipCount >= 0),
assert(children != null),
assert(children.length >= skipCount),
assert(clipBehavior != null),
super(key: key, children: children);
final int skipCount;
final Clip clipBehavior;
@override
_TheatreElement createElement() => _TheatreElement(this);
......@@ -507,6 +519,7 @@ class _Theatre extends MultiChildRenderObjectWidget {
return _RenderTheatre(
skipCount: skipCount,
textDirection: Directionality.of(context)!,
clipBehavior: clipBehavior,
);
}
......@@ -514,7 +527,8 @@ class _Theatre extends MultiChildRenderObjectWidget {
void updateRenderObject(BuildContext context, _RenderTheatre renderObject) {
renderObject
..skipCount = skipCount
..textDirection = Directionality.of(context)!;
..textDirection = Directionality.of(context)!
..clipBehavior = clipBehavior;
}
@override
......@@ -545,11 +559,14 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox
List<RenderBox>? children,
required TextDirection textDirection,
int skipCount = 0,
Clip clipBehavior = Clip.hardEdge,
}) : assert(skipCount != null),
assert(skipCount >= 0),
assert(textDirection != null),
assert(clipBehavior != null),
_textDirection = textDirection,
_skipCount = skipCount {
_skipCount = skipCount,
_clipBehavior = clipBehavior {
addAll(children);
}
......@@ -593,6 +610,20 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox
}
}
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge], and must not be null.
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior = Clip.hardEdge;
set clipBehavior(Clip value) {
assert(value != null);
if (value != _clipBehavior) {
_clipBehavior = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
}
RenderBox? get _firstOnstageChild {
if (skipCount == super.childCount) {
return null;
......@@ -724,8 +755,8 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox
@override
void paint(PaintingContext context, Offset offset) {
if (_hasVisualOverflow) {
context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack);
if (_hasVisualOverflow && clipBehavior != Clip.none) {
context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack, clipBehavior: clipBehavior);
} else {
paintStack(context, offset);
}
......
......@@ -71,9 +71,11 @@ class AndroidView extends StatefulWidget {
this.gestureRecognizers,
this.creationParams,
this.creationParamsCodec,
this.clipBehavior = Clip.hardEdge,
}) : assert(viewType != null),
assert(hitTestBehavior != null),
assert(creationParams == null || creationParamsCodec != null),
assert(clipBehavior != null),
super(key: key);
/// The unique identifier for Android view type to be embedded by this widget.
......@@ -181,6 +183,11 @@ class AndroidView extends StatefulWidget {
/// This must not be null if [creationParams] is not null.
final MessageCodec<dynamic>? creationParamsCodec;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.hardEdge], and must not be null.
final Clip clipBehavior;
@override
State<AndroidView> createState() => _AndroidViewState();
}
......@@ -434,6 +441,7 @@ class _AndroidViewState extends State<AndroidView> {
controller: _controller,
hitTestBehavior: widget.hitTestBehavior,
gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
clipBehavior: widget.clipBehavior,
),
);
}
......@@ -643,14 +651,17 @@ class _AndroidPlatformView extends LeafRenderObjectWidget {
required this.controller,
required this.hitTestBehavior,
required this.gestureRecognizers,
this.clipBehavior = Clip.hardEdge,
}) : assert(controller != null),
assert(hitTestBehavior != null),
assert(gestureRecognizers != null),
assert(clipBehavior != null),
super(key: key);
final AndroidViewController controller;
final PlatformViewHitTestBehavior hitTestBehavior;
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
final Clip clipBehavior;
@override
RenderObject createRenderObject(BuildContext context) =>
......@@ -658,6 +669,7 @@ class _AndroidPlatformView extends LeafRenderObjectWidget {
viewController: controller,
hitTestBehavior: hitTestBehavior,
gestureRecognizers: gestureRecognizers,
clipBehavior: clipBehavior,
);
@override
......@@ -665,6 +677,7 @@ class _AndroidPlatformView extends LeafRenderObjectWidget {
renderObject.viewController = controller;
renderObject.hitTestBehavior = hitTestBehavior;
renderObject.updateGestureRecognizers(gestureRecognizers);
renderObject.clipBehavior = clipBehavior;
}
}
......
......@@ -281,5 +281,41 @@ void main() {
await tester.pump(const Duration(milliseconds: 10));
}
});
testWidgets('can set and update clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Center(
child: AnimatedSize(
duration: const Duration(milliseconds: 200),
vsync: tester,
child: const SizedBox(
width: 100.0,
height: 100.0,
),
),
),
);
// By default, clipBehavior should be Clip.hardEdge
final RenderAnimatedSize renderObject = tester.renderObject(find.byType(AnimatedSize));
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
for(final Clip clip in Clip.values) {
await tester.pumpWidget(
Center(
child: AnimatedSize(
duration: const Duration(milliseconds: 200),
vsync: tester,
clipBehavior: clip,
child: const SizedBox(
width: 100.0,
height: 100.0,
),
),
),
);
expect(renderObject.clipBehavior, clip);
}
});
});
}
......@@ -157,4 +157,62 @@ void main() {
expect(opacityLayer.alpha, equals(opacity * 255));
expect(layer.firstChild, isA<TransformLayer>());
});
testWidgets('Flow can set and update clipBehavior', (WidgetTester tester) async {
const double opacity = 0.2;
await tester.pumpWidget(
Flow(
delegate: OpacityFlowDelegate(opacity),
children: const <Widget>[
SizedBox(width: 100.0, height: 100.0),
],
),
);
// By default, clipBehavior should be Clip.hardEdge
final RenderFlow renderObject = tester.renderObject(find.byType(Flow));
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
for(final Clip clip in Clip.values) {
await tester.pumpWidget(
Flow(
delegate: OpacityFlowDelegate(opacity),
children: const <Widget>[
SizedBox(width: 100.0, height: 100.0),
],
clipBehavior: clip,
),
);
expect(renderObject.clipBehavior, clip);
}
});
testWidgets('Flow.unwrapped can set and update clipBehavior', (WidgetTester tester) async {
const double opacity = 0.2;
await tester.pumpWidget(
Flow.unwrapped(
delegate: OpacityFlowDelegate(opacity),
children: const <Widget>[
SizedBox(width: 100.0, height: 100.0),
],
),
);
// By default, clipBehavior should be Clip.hardEdge
final RenderFlow renderObject = tester.renderObject(find.byType(Flow));
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
for(final Clip clip in Clip.values) {
await tester.pumpWidget(
Flow.unwrapped(
delegate: OpacityFlowDelegate(opacity),
children: const <Widget>[
SizedBox(width: 100.0, height: 100.0),
],
clipBehavior: clip,
),
);
expect(renderObject.clipBehavior, clip);
}
});
}
......@@ -1002,7 +1002,7 @@ void main() {
semantics.dispose();
});
testWidgets('Can used Positioned within OverlayEntry', (WidgetTester tester) async {
testWidgets('Can use Positioned within OverlayEntry', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
......@@ -1024,6 +1024,42 @@ void main() {
expect(tester.getTopLeft(find.text('positioned child')), const Offset(145, 123));
});
testWidgets('Overlay can set and update clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) => Container(),
),
],
),
),
);
// By default, clipBehavior should be Clip.hardEdge
final dynamic renderObject = tester.renderObject(find.byType(Overlay));
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
for(final Clip clip in Clip.values) {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) => Container(),
),
],
clipBehavior: clip,
),
),
);
expect(renderObject.clipBehavior, clip);
}
});
}
class StatefulTestWidget extends StatefulWidget {
......
......@@ -1103,6 +1103,52 @@ void main() {
expect(viewsController.lastClearedFocusViewId, currentViewId + 1);
});
testWidgets('can set and update clipBehavior', (WidgetTester tester) async {
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
await tester.pumpWidget(
const Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
),
),
),
);
// By default, clipBehavior should be Clip.hardEdge
final RenderAndroidView renderObject = tester.renderObject(
find.descendant(
of: find.byType(AndroidView),
matching: find.byWidgetPredicate(
(Widget widget) => widget.runtimeType.toString() == '_AndroidPlatformView',
),
),
);
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
for(final Clip clip in Clip.values) {
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(
viewType: 'webview',
layoutDirection: TextDirection.ltr,
clipBehavior: clip,
),
),
),
);
expect(renderObject.clipBehavior, clip);
}
});
});
group('AndroidViewSurface', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment