blob: 79e23777060428825199d532a316e01a8ab9ed53 [file] [log] [blame]
// 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.
// @dart = 2.6
part of engine;
/// Renders an RRect using path primitives.
abstract class _RRectRenderer {
// TODO(mdebbar): Backport the overlapping corners fix to houdini_painter.js
// To draw the rounded rectangle, perform the following steps:
// 0. Ensure border radius don't overlap
// 1. Flip left,right top,bottom since web doesn't support flipped
// coordinates with negative radii.
// 2. draw the line for the top
// 3. draw the arc for the top-right corner
// 4. draw the line for the right side
// 5. draw the arc for the bottom-right corner
// 6. draw the line for the bottom of the rectangle
// 7. draw the arc for the bottom-left corner
// 8. draw the line for the left side
// 9. draw the arc for the top-left corner
//
// After drawing, the current point will be the left side of the top of the
// rounded rectangle (after the corner).
// TODO(het): Confirm that this is the end point in Flutter for RRect
void render(ui.RRect inputRRect,
{bool startNewPath = true, bool reverse = false}) {
// Ensure border radius curves never overlap
final ui.RRect rrect = inputRRect.scaleRadii();
double left = rrect.left;
double right = rrect.right;
double top = rrect.top;
double bottom = rrect.bottom;
if (left > right) {
left = right;
right = rrect.left;
}
if (top > bottom) {
top = bottom;
bottom = rrect.top;
}
final double trRadiusX = rrect.trRadiusX.abs();
final double tlRadiusX = rrect.tlRadiusX.abs();
final double trRadiusY = rrect.trRadiusY.abs();
final double tlRadiusY = rrect.tlRadiusY.abs();
final double blRadiusX = rrect.blRadiusX.abs();
final double brRadiusX = rrect.brRadiusX.abs();
final double blRadiusY = rrect.blRadiusY.abs();
final double brRadiusY = rrect.brRadiusY.abs();
if (!reverse) {
if (startNewPath) {
beginPath();
}
moveTo(left + trRadiusX, top);
// Top side and top-right corner
lineTo(right - trRadiusX, top);
ellipse(
right - trRadiusX,
top + trRadiusY,
trRadiusX,
trRadiusY,
0,
1.5 * math.pi,
2.0 * math.pi,
false,
);
// Right side and bottom-right corner
lineTo(right, bottom - brRadiusY);
ellipse(
right - brRadiusX,
bottom - brRadiusY,
brRadiusX,
brRadiusY,
0,
0,
0.5 * math.pi,
false,
);
// Bottom side and bottom-left corner
lineTo(left + blRadiusX, bottom);
ellipse(
left + blRadiusX,
bottom - blRadiusY,
blRadiusX,
blRadiusY,
0,
0.5 * math.pi,
math.pi,
false,
);
// Left side and top-left corner
lineTo(left, top + tlRadiusY);
ellipse(
left + tlRadiusX,
top + tlRadiusY,
tlRadiusX,
tlRadiusY,
0,
math.pi,
1.5 * math.pi,
false,
);
} else {
// Draw the rounded rectangle, counterclockwise.
moveTo(right - trRadiusX, top);
if (startNewPath) {
beginPath();
}
// Top side and top-left corner
lineTo(left + tlRadiusX, top);
ellipse(
left + tlRadiusX,
top + tlRadiusY,
tlRadiusX,
tlRadiusY,
0,
1.5 * math.pi,
1 * math.pi,
true,
);
// Left side and bottom-left corner
lineTo(left, bottom - blRadiusY);
ellipse(
left + blRadiusX,
bottom - blRadiusY,
blRadiusX,
blRadiusY,
0,
1 * math.pi,
0.5 * math.pi,
true,
);
// Bottom side and bottom-right corner
lineTo(right - brRadiusX, bottom);
ellipse(
right - brRadiusX,
bottom - brRadiusY,
brRadiusX,
brRadiusY,
0,
0.5 * math.pi,
0 * math.pi,
true,
);
// Right side and top-right corner
lineTo(right, top + trRadiusY);
ellipse(
right - trRadiusX,
top + trRadiusY,
trRadiusX,
trRadiusY,
0,
0 * math.pi,
1.5 * math.pi,
true,
);
}
}
void beginPath();
void moveTo(double x, double y);
void lineTo(double x, double y);
void ellipse(double centerX, double centerY, double radiusX, double radiusY,
double rotation, double startAngle, double endAngle, bool antiClockwise);
}
/// Renders RRect to a 2d canvas.
class _RRectToCanvasRenderer extends _RRectRenderer {
final html.CanvasRenderingContext2D context;
_RRectToCanvasRenderer(this.context);
void beginPath() {
context.beginPath();
}
void moveTo(double x, double y) {
context.moveTo(x, y);
}
void lineTo(double x, double y) {
context.lineTo(x, y);
}
void ellipse(double centerX, double centerY, double radiusX, double radiusY,
double rotation, double startAngle, double endAngle, bool antiClockwise) {
DomRenderer.ellipse(context, centerX, centerY, radiusX, radiusY, rotation, startAngle,
endAngle, antiClockwise);
}
}
/// Renders RRect to a path.
class _RRectToPathRenderer extends _RRectRenderer {
final ui.Path path;
_RRectToPathRenderer(this.path);
void beginPath() {}
void moveTo(double x, double y) {
path.moveTo(x, y);
}
void lineTo(double x, double y) {
path.lineTo(x, y);
}
void ellipse(double centerX, double centerY, double radiusX, double radiusY,
double rotation, double startAngle, double endAngle, bool antiClockwise) {
path.addArc(
ui.Rect.fromLTRB(centerX - radiusX, centerY - radiusY,
centerX + radiusX, centerY + radiusY),
startAngle,
antiClockwise ? startAngle - endAngle : endAngle - startAngle);
}
}
typedef RRectRendererEllipseCallback = void Function(double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, bool antiClockwise);
typedef RRectRendererCallback = void Function(double x, double y);
/// Converts RRect to path primitives with callbacks.
class RRectMetricsRenderer extends _RRectRenderer {
RRectMetricsRenderer({this.moveToCallback, this.lineToCallback, this.ellipseCallback});
final RRectRendererEllipseCallback ellipseCallback;
final RRectRendererCallback lineToCallback;
final RRectRendererCallback moveToCallback;
@override
void beginPath() {}
@override
void ellipse(double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, bool antiClockwise) => ellipseCallback(
centerX, centerY, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise);
@override
void lineTo(double x, double y) => lineToCallback(x, y);
@override
void moveTo(double x, double y) => moveToCallback(x, y);
}