diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index 021ed6262e2bf..2322466e48a07 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -7,6 +7,9 @@ part of engine; /// Enable this to print every command applied by a canvas. const bool _debugDumpPaintCommands = false; +// Similar to [Offset.distance] +double _getDistance(double x, double y) => math.sqrt(x * x + y * y); + /// Records canvas commands to be applied to a [EngineCanvas]. /// /// See [Canvas] for docs for these methods. @@ -228,12 +231,31 @@ class RecordingCanvas { } void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { - // If inner rect is not contained inside outer, flutter engine skips - // painting rectangle. - if (!(outer.contains(ui.Offset(inner.left, inner.top)) && - outer.contains(ui.Offset(inner.right, inner.bottom)))) { - return; + // Ensure inner is fully contained within outer, by comparing its + // defining points (including its border radius) + ui.Rect innerRect = inner.outerRect; + if (outer.outerRect.intersect(innerRect) != innerRect) { + return; // inner is not fully contained within outer + } + + // Compare radius "length" of the rectangles that are going to be actually drawn + final ui.RRect scaledOuter = outer.scaleRadii(); + final ui.RRect scaledInner = inner.scaleRadii(); + + final double outerTl = _getDistance(scaledOuter.tlRadiusX, scaledOuter.tlRadiusY); + final double outerTr = _getDistance(scaledOuter.trRadiusX, scaledOuter.trRadiusY); + final double outerBl = _getDistance(scaledOuter.blRadiusX, scaledOuter.blRadiusY); + final double outerBr = _getDistance(scaledOuter.brRadiusX, scaledOuter.brRadiusY); + + final double innerTl = _getDistance(scaledInner.tlRadiusX, scaledInner.tlRadiusY); + final double innerTr = _getDistance(scaledInner.trRadiusX, scaledInner.trRadiusY); + final double innerBl = _getDistance(scaledInner.blRadiusX, scaledInner.blRadiusY); + final double innerBr = _getDistance(scaledInner.brRadiusX, scaledInner.brRadiusY); + + if (innerTl >= outerTl || innerTr >= outerTr || innerBl >= outerBl || innerBr >= outerBr) { + return; // Some inner radius is overlapping some outer radius } + _hasArbitraryPaint = true; _didDraw = true; final double strokeWidth = diff --git a/lib/web_ui/test/engine/recording_canvas_test.dart b/lib/web_ui/test/engine/recording_canvas_test.dart new file mode 100644 index 0000000000000..d8f1963e59aa6 --- /dev/null +++ b/lib/web_ui/test/engine/recording_canvas_test.dart @@ -0,0 +1,60 @@ +// Copyright 2013 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:ui/ui.dart'; +import 'package:ui/src/engine.dart'; +import 'package:test/test.dart'; + +import '../mock_engine_canvas.dart'; + +void main() { + + RecordingCanvas underTest; + MockEngineCanvas mockCanvas; + + setUp(() { + underTest = RecordingCanvas(Rect.largest); + mockCanvas = MockEngineCanvas(); + }); + + group('drawDRRect', () { + final RRect rrect = RRect.fromLTRBR(10, 10, 50, 50, Radius.circular(3)); + final Paint somePaint = Paint()..color = const Color(0xFFFF0000); + + test('Happy case', () { + underTest.drawDRRect(rrect, rrect.deflate(1), somePaint); + underTest.apply(mockCanvas); + // Expect drawDRRect to be called + expect(mockCanvas.methodCallLog.length, equals(1)); + MockCanvasCall mockCall = mockCanvas.methodCallLog[0]; + expect(mockCall.methodName, equals('drawDRRect')); + expect(mockCall.arguments, equals({ + 'outer': rrect, + 'inner': rrect.deflate(1), + 'paint': somePaint.webOnlyPaintData, + })); + }); + + test('Inner RRect > Outer RRect', () { + underTest.drawDRRect(rrect, rrect.inflate(1), somePaint); + underTest.apply(mockCanvas); + // Expect nothing to be called + expect(mockCanvas.methodCallLog.length, equals(0)); + }); + + test('Inner RRect not completely inside Outer RRect', () { + underTest.drawDRRect(rrect, rrect.deflate(1).shift(const Offset(0.0, 10)), somePaint); + underTest.apply(mockCanvas); + // Expect nothing to be called + expect(mockCanvas.methodCallLog.length, equals(0)); + }); + + test('Inner RRect same as Outer RRect', () { + underTest.drawDRRect(rrect, rrect, somePaint); + underTest.apply(mockCanvas); + // Expect nothing to be called + expect(mockCanvas.methodCallLog.length, equals(0)); + }); + }); +}