[Engine] Add RoundSuperellipse to drawing OP (#160883)
This PR adds support for clipping round superellipse to the engine. For what a rounded superellipse is, see [this design doc](https://docs.google.com/document/d/1CJXULKJGQt22FOFsrlm2TKVjKBtif1yU4U50cMfL6Kc/edit?tab=t.0). Video demos can be found at https://github.com/flutter/engine/pull/56726 and https://github.com/flutter/flutter/pull/161409. Only impeller can actually render it. On Skia and Web, this shape falls back to `RRect`. Part of https://github.com/flutter/flutter/issues/139321 and https://github.com/flutter/flutter/issues/13914, also related to https://github.com/flutter/flutter/issues/91523. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
7d23780c7f
commit
5b7a3c8b20
@ -58,6 +58,7 @@
|
||||
../../../flutter/flow/layers/clip_path_layer_unittests.cc
|
||||
../../../flutter/flow/layers/clip_rect_layer_unittests.cc
|
||||
../../../flutter/flow/layers/clip_rrect_layer_unittests.cc
|
||||
../../../flutter/flow/layers/clip_rsuperellipse_layer_unittests.cc
|
||||
../../../flutter/flow/layers/color_filter_layer_unittests.cc
|
||||
../../../flutter/flow/layers/container_layer_unittests.cc
|
||||
../../../flutter/flow/layers/display_list_layer_unittests.cc
|
||||
|
@ -41379,6 +41379,8 @@ ORIGIN: ../../../flutter/flow/layers/clip_rect_layer.cc + ../../../flutter/LICEN
|
||||
ORIGIN: ../../../flutter/flow/layers/clip_rect_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/clip_rrect_layer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/clip_rrect_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/clip_rsuperellipse_layer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/clip_rsuperellipse_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/clip_shape_layer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/color_filter_layer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/flow/layers/color_filter_layer.h + ../../../flutter/LICENSE
|
||||
@ -42490,6 +42492,8 @@ ORIGIN: ../../../flutter/lib/ui/painting/picture_recorder.cc + ../../../flutter/
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/picture_recorder.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/rrect.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/rrect.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/rsuperellipse.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/rsuperellipse.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/shader.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/shader.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/ui/painting/single_frame_codec.cc + ../../../flutter/LICENSE
|
||||
@ -44342,6 +44346,8 @@ FILE: ../../../flutter/flow/layers/clip_rect_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/clip_rect_layer.h
|
||||
FILE: ../../../flutter/flow/layers/clip_rrect_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/clip_rrect_layer.h
|
||||
FILE: ../../../flutter/flow/layers/clip_rsuperellipse_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/clip_rsuperellipse_layer.h
|
||||
FILE: ../../../flutter/flow/layers/clip_shape_layer.h
|
||||
FILE: ../../../flutter/flow/layers/color_filter_layer.cc
|
||||
FILE: ../../../flutter/flow/layers/color_filter_layer.h
|
||||
@ -45457,6 +45463,8 @@ FILE: ../../../flutter/lib/ui/painting/picture_recorder.cc
|
||||
FILE: ../../../flutter/lib/ui/painting/picture_recorder.h
|
||||
FILE: ../../../flutter/lib/ui/painting/rrect.cc
|
||||
FILE: ../../../flutter/lib/ui/painting/rrect.h
|
||||
FILE: ../../../flutter/lib/ui/painting/rsuperellipse.cc
|
||||
FILE: ../../../flutter/lib/ui/painting/rsuperellipse.h
|
||||
FILE: ../../../flutter/lib/ui/painting/shader.cc
|
||||
FILE: ../../../flutter/lib/ui/painting/shader.h
|
||||
FILE: ../../../flutter/lib/ui/painting/single_frame_codec.cc
|
||||
|
@ -335,6 +335,12 @@ void DisplayListGLComplexityCalculator::GLHelper::drawDiffRoundRect(
|
||||
AccumulateComplexity(complexity);
|
||||
}
|
||||
|
||||
void DisplayListGLComplexityCalculator::GLHelper::drawRoundSuperellipse(
|
||||
const DlRoundSuperellipse& rse) {
|
||||
// Drawing RSEs on Skia falls back to RRect.
|
||||
drawRoundRect(rse.ToApproximateRoundRect());
|
||||
}
|
||||
|
||||
void DisplayListGLComplexityCalculator::GLHelper::drawPath(const DlPath& path) {
|
||||
if (IsComplex()) {
|
||||
return;
|
||||
|
@ -51,6 +51,7 @@ class DisplayListGLComplexityCalculator
|
||||
void drawRoundRect(const DlRoundRect& rrect) override;
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override;
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
|
||||
void drawPath(const DlPath& path) override;
|
||||
void drawArc(const DlRect& oval_bounds,
|
||||
DlScalar start_degrees,
|
||||
|
@ -329,6 +329,12 @@ void DisplayListMetalComplexityCalculator::MetalHelper::drawDiffRoundRect(
|
||||
AccumulateComplexity(complexity);
|
||||
}
|
||||
|
||||
void DisplayListMetalComplexityCalculator::MetalHelper::drawRoundSuperellipse(
|
||||
const DlRoundSuperellipse& rse) {
|
||||
// Drawing RSEs on Skia falls back to RRect.
|
||||
drawRoundRect(rse.ToApproximateRoundRect());
|
||||
}
|
||||
|
||||
void DisplayListMetalComplexityCalculator::MetalHelper::drawPath(
|
||||
const DlPath& path) {
|
||||
if (IsComplex()) {
|
||||
|
@ -51,6 +51,7 @@ class DisplayListMetalComplexityCalculator
|
||||
void drawRoundRect(const DlRoundRect& rrect) override;
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override;
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
|
||||
void drawPath(const DlPath& path) override;
|
||||
void drawArc(const DlRect& oval_bounds,
|
||||
DlScalar start_degrees,
|
||||
|
@ -300,10 +300,12 @@ DisplayListOpCategory DisplayList::GetOpCategory(DisplayListOpType type) {
|
||||
case DisplayListOpType::kClipIntersectRect:
|
||||
case DisplayListOpType::kClipIntersectOval:
|
||||
case DisplayListOpType::kClipIntersectRoundRect:
|
||||
case DisplayListOpType::kClipIntersectRoundSuperellipse:
|
||||
case DisplayListOpType::kClipIntersectPath:
|
||||
case DisplayListOpType::kClipDifferenceRect:
|
||||
case DisplayListOpType::kClipDifferenceOval:
|
||||
case DisplayListOpType::kClipDifferenceRoundRect:
|
||||
case DisplayListOpType::kClipDifferenceRoundSuperellipse:
|
||||
case DisplayListOpType::kClipDifferencePath:
|
||||
return DisplayListOpCategory::kClip;
|
||||
|
||||
@ -316,6 +318,7 @@ DisplayListOpCategory DisplayList::GetOpCategory(DisplayListOpType type) {
|
||||
case DisplayListOpType::kDrawCircle:
|
||||
case DisplayListOpType::kDrawRoundRect:
|
||||
case DisplayListOpType::kDrawDiffRoundRect:
|
||||
case DisplayListOpType::kDrawRoundSuperellipse:
|
||||
case DisplayListOpType::kDrawArc:
|
||||
case DisplayListOpType::kDrawPath:
|
||||
case DisplayListOpType::kDrawPoints:
|
||||
|
@ -97,10 +97,12 @@ namespace flutter {
|
||||
V(ClipIntersectRect) \
|
||||
V(ClipIntersectOval) \
|
||||
V(ClipIntersectRoundRect) \
|
||||
V(ClipIntersectRoundSuperellipse) \
|
||||
V(ClipIntersectPath) \
|
||||
V(ClipDifferenceRect) \
|
||||
V(ClipDifferenceOval) \
|
||||
V(ClipDifferenceRoundRect) \
|
||||
V(ClipDifferenceRoundSuperellipse) \
|
||||
V(ClipDifferencePath) \
|
||||
\
|
||||
V(DrawPaint) \
|
||||
@ -113,6 +115,7 @@ namespace flutter {
|
||||
V(DrawCircle) \
|
||||
V(DrawRoundRect) \
|
||||
V(DrawDiffRoundRect) \
|
||||
V(DrawRoundSuperellipse) \
|
||||
V(DrawArc) \
|
||||
V(DrawPath) \
|
||||
\
|
||||
|
@ -4512,7 +4512,7 @@ TEST_F(DisplayListTest, DrawDisplayListForwardsBackdropFlag) {
|
||||
#define CLIP_EXPECTOR(name) ClipExpector name(__FILE__, __LINE__)
|
||||
|
||||
struct ClipExpectation {
|
||||
std::variant<DlRect, DlRoundRect, DlPath> shape;
|
||||
std::variant<DlRect, DlRoundRect, DlRoundSuperellipse, DlPath> shape;
|
||||
bool is_oval;
|
||||
DlClipOp clip_op;
|
||||
bool is_aa;
|
||||
@ -4524,6 +4524,8 @@ struct ClipExpectation {
|
||||
case 1:
|
||||
return "DlRoundRect";
|
||||
case 2:
|
||||
return "DlRoundSuperellipse";
|
||||
case 3:
|
||||
return "DlPath";
|
||||
default:
|
||||
return "Unknown";
|
||||
@ -4632,6 +4634,11 @@ class ClipExpector : public virtual DlOpReceiver,
|
||||
bool is_aa) override {
|
||||
check(rrect, clip_op, is_aa);
|
||||
}
|
||||
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override {
|
||||
check(rse, clip_op, is_aa);
|
||||
}
|
||||
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {
|
||||
check(path, clip_op, is_aa);
|
||||
}
|
||||
|
@ -1025,6 +1025,42 @@ void DisplayListBuilder::ClipRoundRect(const DlRoundRect& rrect,
|
||||
break;
|
||||
}
|
||||
}
|
||||
void DisplayListBuilder::ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
if (rse.IsRect()) {
|
||||
ClipRect(rse.GetBounds(), clip_op, is_aa);
|
||||
return;
|
||||
}
|
||||
if (rse.IsOval()) {
|
||||
ClipOval(rse.GetBounds(), clip_op, is_aa);
|
||||
return;
|
||||
}
|
||||
if (current_info().is_nop) {
|
||||
return;
|
||||
}
|
||||
if (current_info().has_valid_clip && clip_op == DlClipOp::kIntersect &&
|
||||
layer_local_state().rsuperellipse_covers_cull(rse)) {
|
||||
return;
|
||||
}
|
||||
global_state().clipRSuperellipse(rse, clip_op, is_aa);
|
||||
layer_local_state().clipRSuperellipse(rse, clip_op, is_aa);
|
||||
if (global_state().is_cull_rect_empty() ||
|
||||
layer_local_state().is_cull_rect_empty()) {
|
||||
current_info().is_nop = true;
|
||||
return;
|
||||
}
|
||||
current_info().has_valid_clip = true;
|
||||
checkForDeferredSave();
|
||||
switch (clip_op) {
|
||||
case DlClipOp::kIntersect:
|
||||
Push<ClipIntersectRoundSuperellipseOp>(0, rse, is_aa);
|
||||
break;
|
||||
case DlClipOp::kDifference:
|
||||
Push<ClipDifferenceRoundSuperellipseOp>(0, rse, is_aa);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void DisplayListBuilder::ClipPath(const DlPath& path,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
@ -1214,6 +1250,27 @@ void DisplayListBuilder::DrawDiffRoundRect(const DlRoundRect& outer,
|
||||
SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawDRRectFlags);
|
||||
drawDiffRoundRect(outer, inner);
|
||||
}
|
||||
void DisplayListBuilder::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
|
||||
if (rse.IsRect()) {
|
||||
drawRect(rse.GetBounds());
|
||||
} else if (rse.IsOval()) {
|
||||
drawOval(rse.GetBounds());
|
||||
} else {
|
||||
DisplayListAttributeFlags flags = kDrawRSuperellipseFlags;
|
||||
OpResult result = PaintResult(current_, flags);
|
||||
if (result != OpResult::kNoEffect &&
|
||||
AccumulateOpBounds(rse.GetBounds(), flags)) {
|
||||
Push<DrawRoundSuperellipseOp>(0, rse);
|
||||
CheckLayerOpacityCompatibility();
|
||||
UpdateLayerResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DisplayListBuilder::DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
const DlPaint& paint) {
|
||||
SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRSuperellipseFlags);
|
||||
drawRoundSuperellipse(rse);
|
||||
}
|
||||
void DisplayListBuilder::drawPath(const DlPath& path) {
|
||||
DisplayListAttributeFlags flags = kDrawPathFlags;
|
||||
OpResult result = PaintResult(current_, flags);
|
||||
|
@ -118,6 +118,10 @@ class DisplayListBuilder final : public virtual DlCanvas,
|
||||
DlClipOp clip_op = DlClipOp::kIntersect,
|
||||
bool is_aa = false) override;
|
||||
// |DlCanvas|
|
||||
void ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op = DlClipOp::kIntersect,
|
||||
bool is_aa = false) override;
|
||||
// |DlCanvas|
|
||||
void ClipPath(const DlPath& path,
|
||||
DlClipOp clip_op = DlClipOp::kIntersect,
|
||||
bool is_aa = false) override;
|
||||
@ -172,6 +176,9 @@ class DisplayListBuilder final : public virtual DlCanvas,
|
||||
const DlRoundRect& inner,
|
||||
const DlPaint& paint) override;
|
||||
// |DlCanvas|
|
||||
void DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
const DlPaint& paint) override;
|
||||
// |DlCanvas|
|
||||
void DrawPath(const DlPath& path, const DlPaint& paint) override;
|
||||
// |DlCanvas|
|
||||
void DrawArc(const DlRect& bounds,
|
||||
@ -409,6 +416,12 @@ class DisplayListBuilder final : public virtual DlCanvas,
|
||||
ClipRoundRect(rrect, clip_op, is_aa);
|
||||
}
|
||||
// |DlOpReceiver|
|
||||
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override {
|
||||
ClipRoundSuperellipse(rse, clip_op, is_aa);
|
||||
}
|
||||
// |DlOpReceiver|
|
||||
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {
|
||||
ClipPath(path, clip_op, is_aa);
|
||||
}
|
||||
@ -438,6 +451,8 @@ class DisplayListBuilder final : public virtual DlCanvas,
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override;
|
||||
// |DlOpReceiver|
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
|
||||
// |DlOpReceiver|
|
||||
void drawPath(const DlPath& path) override;
|
||||
// |DlOpReceiver|
|
||||
void drawArc(const DlRect& bounds,
|
||||
|
@ -85,6 +85,9 @@ class DlCanvas {
|
||||
virtual void ClipRoundRect(const DlRoundRect& rrect,
|
||||
DlClipOp clip_op = DlClipOp::kIntersect,
|
||||
bool is_aa = false) = 0;
|
||||
virtual void ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op = DlClipOp::kIntersect,
|
||||
bool is_aa = false) = 0;
|
||||
virtual void ClipPath(const DlPath& path,
|
||||
DlClipOp clip_op = DlClipOp::kIntersect,
|
||||
bool is_aa = false) = 0;
|
||||
@ -125,6 +128,8 @@ class DlCanvas {
|
||||
virtual void DrawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner,
|
||||
const DlPaint& paint) = 0;
|
||||
virtual void DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
const DlPaint& paint) = 0;
|
||||
virtual void DrawPath(const DlPath& path, const DlPaint& paint) = 0;
|
||||
virtual void DrawArc(const DlRect& bounds,
|
||||
DlScalar start,
|
||||
|
@ -323,6 +323,10 @@ class DisplayListOpFlags : DisplayListFlags {
|
||||
kBasePaintFlags | //
|
||||
kBaseStrokeOrFillFlags //
|
||||
};
|
||||
static constexpr DisplayListAttributeFlags kDrawRSuperellipseFlags{
|
||||
kBasePaintFlags | //
|
||||
kBaseStrokeOrFillFlags //
|
||||
};
|
||||
static constexpr DisplayListAttributeFlags kDrawPathFlags{
|
||||
kBasePaintFlags | //
|
||||
kBaseStrokeOrFillFlags | //
|
||||
|
@ -291,6 +291,9 @@ class DlOpReceiver {
|
||||
virtual void clipRoundRect(const DlRoundRect& rrect,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) = 0;
|
||||
virtual void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) = 0;
|
||||
virtual void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) = 0;
|
||||
|
||||
// The following rendering methods all take their rendering attributes
|
||||
@ -313,6 +316,7 @@ class DlOpReceiver {
|
||||
virtual void drawRoundRect(const DlRoundRect& rrect) = 0;
|
||||
virtual void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) = 0;
|
||||
virtual void drawRoundSuperellipse(const DlRoundSuperellipse& rse) = 0;
|
||||
virtual void drawPath(const DlPath& path) = 0;
|
||||
virtual void drawArc(const DlRect& oval_bounds,
|
||||
DlScalar start_degrees,
|
||||
|
@ -486,6 +486,7 @@ struct TransformResetOp final : TransformClipOpBase {
|
||||
// DlRect is 16 more bytes, which packs efficiently into 24 bytes total
|
||||
// DlRoundRect is 48 more bytes, which rounds up to 48 bytes
|
||||
// which packs into 56 bytes total
|
||||
// DlRoundSuperellipse is the same as DlRoundRect
|
||||
// CacheablePath is 128 more bytes, which packs efficiently into 136 bytes total
|
||||
//
|
||||
// We could pack the clip_op and the bool both into the free 4 bytes after
|
||||
@ -509,9 +510,11 @@ struct TransformResetOp final : TransformClipOpBase {
|
||||
DEFINE_CLIP_SHAPE_OP(Rect, DlRect, Intersect)
|
||||
DEFINE_CLIP_SHAPE_OP(Oval, DlRect, Intersect)
|
||||
DEFINE_CLIP_SHAPE_OP(RoundRect, DlRoundRect, Intersect)
|
||||
DEFINE_CLIP_SHAPE_OP(RoundSuperellipse, DlRoundSuperellipse, Intersect)
|
||||
DEFINE_CLIP_SHAPE_OP(Rect, DlRect, Difference)
|
||||
DEFINE_CLIP_SHAPE_OP(Oval, DlRect, Difference)
|
||||
DEFINE_CLIP_SHAPE_OP(RoundRect, DlRoundRect, Difference)
|
||||
DEFINE_CLIP_SHAPE_OP(RoundSuperellipse, DlRoundSuperellipse, Difference)
|
||||
#undef DEFINE_CLIP_SHAPE_OP
|
||||
|
||||
// 4 byte header + 20 byte payload packs evenly into 24 bytes
|
||||
@ -578,6 +581,7 @@ struct DrawColorOp final : DrawOpBase {
|
||||
// SkOval is same as DlRect
|
||||
// DlRoundRect is 48 more bytes, using 52 bytes which rounds up to 56 bytes
|
||||
// total (4 bytes unused)
|
||||
// DlRoundSuperellipse is the same as DlRoundRect
|
||||
#define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \
|
||||
struct Draw##op_name##Op final : DrawOpBase { \
|
||||
static constexpr auto kType = DisplayListOpType::kDraw##op_name; \
|
||||
@ -594,6 +598,7 @@ struct DrawColorOp final : DrawOpBase {
|
||||
DEFINE_DRAW_1ARG_OP(Rect, DlRect, rect)
|
||||
DEFINE_DRAW_1ARG_OP(Oval, DlRect, oval)
|
||||
DEFINE_DRAW_1ARG_OP(RoundRect, DlRoundRect, rrect)
|
||||
DEFINE_DRAW_1ARG_OP(RoundSuperellipse, DlRoundSuperellipse, rse)
|
||||
#undef DEFINE_DRAW_1ARG_OP
|
||||
|
||||
// 4 byte header + 16 byte payload uses 20 bytes but is rounded
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "flutter/impeller/geometry/path.h"
|
||||
#include "flutter/impeller/geometry/rect.h"
|
||||
#include "flutter/impeller/geometry/round_rect.h"
|
||||
#include "flutter/impeller/geometry/round_superellipse.h"
|
||||
#include "flutter/impeller/geometry/rstransform.h"
|
||||
#include "flutter/impeller/geometry/scalar.h"
|
||||
|
||||
@ -32,6 +33,7 @@ using DlISize = impeller::ISize32;
|
||||
using DlRect = impeller::Rect;
|
||||
using DlIRect = impeller::IRect32;
|
||||
using DlRoundRect = impeller::RoundRect;
|
||||
using DlRoundSuperellipse = impeller::RoundSuperellipse;
|
||||
using DlRoundingRadii = impeller::RoundingRadii;
|
||||
using DlMatrix = impeller::Matrix;
|
||||
using DlQuad = impeller::Quad;
|
||||
@ -203,6 +205,16 @@ inline const SkRRect ToSkRRect(const DlRoundRect& round_rect) {
|
||||
return rrect;
|
||||
};
|
||||
|
||||
// Approximates a rounded superellipse with a round rectangle to the
|
||||
// best practical accuracy.
|
||||
//
|
||||
// Skia does not support rounded superellipses directly, so rendering
|
||||
// `DlRoundSuperellipses` on Skia requires falling back to RRect.
|
||||
inline constexpr const SkRRect ToApproximateSkRRect(
|
||||
const DlRoundSuperellipse& rse) {
|
||||
return ToSkRRect(rse.ToApproximateRoundRect());
|
||||
};
|
||||
|
||||
inline constexpr SkMatrix ToSkMatrix(const DlMatrix& matrix) {
|
||||
return SkMatrix::MakeAll(matrix.m[0], matrix.m[4], matrix.m[12], //
|
||||
matrix.m[1], matrix.m[5], matrix.m[13], //
|
||||
|
@ -68,5 +68,14 @@ TEST(DisplayListGeometryTypes, VectorToSizeConversion) {
|
||||
EXPECT_NE(ToDlSize(sk_v), dl_s);
|
||||
}
|
||||
|
||||
TEST(DisplayListGeometryTypes, RSEToRRectConversion) {
|
||||
DlRoundSuperellipse dl_rse = DlRoundSuperellipse::MakeRectRadius(
|
||||
DlRect::MakeLTRB(10, 20, 30, 40), 1.0f);
|
||||
SkRRect sk_rrect =
|
||||
SkRRect::MakeRectXY(SkRect::MakeLTRB(10, 20, 30, 40), 1.0f, 1.0f);
|
||||
|
||||
EXPECT_EQ(sk_rrect, ToApproximateSkRRect(dl_rse));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
@ -157,6 +157,13 @@ void DlSkCanvasAdapter::ClipRoundRect(const DlRoundRect& rrect,
|
||||
delegate_->clipRRect(ToSkRRect(rrect), ToSk(clip_op), is_aa);
|
||||
}
|
||||
|
||||
void DlSkCanvasAdapter::ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
// Skia doesn't support round superellipse, thus fall back to round rectangle.
|
||||
delegate_->clipRRect(ToApproximateSkRRect(rse), ToSk(clip_op), is_aa);
|
||||
}
|
||||
|
||||
void DlSkCanvasAdapter::ClipPath(const DlPath& path,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
@ -235,6 +242,12 @@ void DlSkCanvasAdapter::DrawDiffRoundRect(const DlRoundRect& outer,
|
||||
delegate_->drawDRRect(ToSkRRect(outer), ToSkRRect(inner), ToSk(paint));
|
||||
}
|
||||
|
||||
void DlSkCanvasAdapter::DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
const DlPaint& paint) {
|
||||
// Skia doesn't support round superellipse, thus fall back to round rectangle.
|
||||
delegate_->drawRRect(ToApproximateSkRRect(rse), ToSk(paint));
|
||||
}
|
||||
|
||||
void DlSkCanvasAdapter::DrawPath(const DlPath& path, const DlPaint& paint) {
|
||||
path.WillRenderSkPath();
|
||||
delegate_->drawPath(path.GetSkPath(), ToSk(paint));
|
||||
|
@ -69,6 +69,9 @@ class DlSkCanvasAdapter final : public virtual DlCanvas {
|
||||
void ClipRoundRect(const DlRoundRect& rrect,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
void ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
void ClipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override;
|
||||
|
||||
/// Conservative estimate of the bounds of all outstanding clip operations
|
||||
@ -104,6 +107,8 @@ class DlSkCanvasAdapter final : public virtual DlCanvas {
|
||||
void DrawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner,
|
||||
const DlPaint& paint) override;
|
||||
void DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
const DlPaint& paint) override;
|
||||
void DrawPath(const DlPath& path, const DlPaint& paint) override;
|
||||
void DrawArc(const DlRect& bounds,
|
||||
DlScalar start,
|
||||
|
@ -140,6 +140,12 @@ void DlSkCanvasDispatcher::clipRoundRect(const DlRoundRect& rrect,
|
||||
bool is_aa) {
|
||||
canvas_->clipRRect(ToSkRRect(rrect), ToSk(clip_op), is_aa);
|
||||
}
|
||||
void DlSkCanvasDispatcher::clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
// Skia doesn't support round superellipse, thus fall back to round rectangle.
|
||||
canvas_->clipRRect(ToApproximateSkRRect(rse), ToSk(clip_op), is_aa);
|
||||
}
|
||||
void DlSkCanvasDispatcher::clipPath(const DlPath& path,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
@ -192,6 +198,11 @@ void DlSkCanvasDispatcher::drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) {
|
||||
canvas_->drawDRRect(ToSkRRect(outer), ToSkRRect(inner), paint());
|
||||
}
|
||||
void DlSkCanvasDispatcher::drawRoundSuperellipse(
|
||||
const DlRoundSuperellipse& rse) {
|
||||
// Skia doesn't support round superellipse, thus fall back to round rectangle.
|
||||
canvas_->drawRRect(ToApproximateSkRRect(rse), paint());
|
||||
}
|
||||
void DlSkCanvasDispatcher::drawPath(const DlPath& path) {
|
||||
path.WillRenderSkPath();
|
||||
canvas_->drawPath(path.GetSkPath(), paint());
|
||||
|
@ -56,6 +56,9 @@ class DlSkCanvasDispatcher : public virtual DlOpReceiver,
|
||||
void clipRoundRect(const DlRoundRect& rrect,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override;
|
||||
|
||||
void drawPaint() override;
|
||||
@ -71,6 +74,7 @@ class DlSkCanvasDispatcher : public virtual DlOpReceiver,
|
||||
void drawRoundRect(const DlRoundRect& rrect) override;
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override;
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
|
||||
void drawPath(const DlPath& path) override;
|
||||
void drawArc(const DlRect& bounds,
|
||||
DlScalar start,
|
||||
|
@ -546,6 +546,34 @@ std::vector<DisplayListInvocationGroup> CreateAllClipOps() {
|
||||
r.clipRoundRect(kTestRRect, DlClipOp::kDifference, false);
|
||||
}},
|
||||
}},
|
||||
{"ClipRSuperellipse",
|
||||
{
|
||||
{1, 56, 0,
|
||||
[](DlOpReceiver& r) {
|
||||
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kIntersect,
|
||||
true);
|
||||
}},
|
||||
{1, 56, 0,
|
||||
[](DlOpReceiver& r) {
|
||||
r.clipRoundSuperellipse(kTestRSuperellipse.Shift(1, 1),
|
||||
DlClipOp::kIntersect, true);
|
||||
}},
|
||||
{1, 56, 0,
|
||||
[](DlOpReceiver& r) {
|
||||
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kIntersect,
|
||||
false);
|
||||
}},
|
||||
{1, 56, 0,
|
||||
[](DlOpReceiver& r) {
|
||||
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kDifference,
|
||||
true);
|
||||
}},
|
||||
{1, 56, 0,
|
||||
[](DlOpReceiver& r) {
|
||||
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kDifference,
|
||||
false);
|
||||
}},
|
||||
}},
|
||||
{"ClipPath",
|
||||
{
|
||||
{1, 24, 0,
|
||||
@ -725,6 +753,17 @@ std::vector<DisplayListInvocationGroup> CreateAllRenderingOps() {
|
||||
{1, 56, 1,
|
||||
[](DlOpReceiver& r) { r.drawRoundRect(kTestRRect.Shift(5, 5)); }},
|
||||
}},
|
||||
{"DrawRSuperellipse",
|
||||
{
|
||||
{1, 56, 1,
|
||||
[](DlOpReceiver& r) {
|
||||
r.drawRoundSuperellipse(kTestRSuperellipse);
|
||||
}},
|
||||
{1, 56, 1,
|
||||
[](DlOpReceiver& r) {
|
||||
r.drawRoundSuperellipse(kTestRSuperellipse.Shift(5, 5));
|
||||
}},
|
||||
}},
|
||||
{"DrawDRRect",
|
||||
{
|
||||
{1, 104, 1,
|
||||
|
@ -170,6 +170,8 @@ constexpr DlRect kTestBounds = DlRect::MakeLTRB(10, 10, 50, 60);
|
||||
constexpr SkRect kTestSkBounds = SkRect::MakeLTRB(10, 10, 50, 60);
|
||||
static const DlRoundRect kTestRRect =
|
||||
DlRoundRect::MakeRectXY(kTestBounds, 5, 5);
|
||||
static const DlRoundSuperellipse kTestRSuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(kTestBounds, 3, 3);
|
||||
static const SkRRect kTestSkRRect = SkRRect::MakeRectXY(kTestSkBounds, 5, 5);
|
||||
static const SkRRect kTestRRectRect = SkRRect::MakeRect(kTestSkBounds);
|
||||
static const DlRoundRect kTestInnerRRect =
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "flutter/display_list/dl_builder.h"
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/impeller/geometry/round_superellipse_param.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
@ -67,6 +68,20 @@ void DisplayListMatrixClipState::clipOval(const DlRect& bounds,
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
inline std::array<DlRect, 2> RoundingRadiiSafeRects(
|
||||
const DlRect& bounds,
|
||||
const impeller::RoundingRadii& radii) {
|
||||
return {
|
||||
bounds.Expand( //
|
||||
-std::max(radii.top_left.width, radii.bottom_left.width), 0,
|
||||
-std::max(radii.top_right.width, radii.bottom_right.width), 0),
|
||||
bounds.Expand(
|
||||
0, -std::max(radii.top_left.height, radii.top_right.height), //
|
||||
0, -std::max(radii.bottom_left.height, radii.bottom_right.height))};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DisplayListMatrixClipState::clipRRect(const DlRoundRect& rrect,
|
||||
DlClipOp op,
|
||||
bool is_aa) {
|
||||
@ -83,15 +98,34 @@ void DisplayListMatrixClipState::clipRRect(const DlRoundRect& rrect,
|
||||
cull_rect_ = DlRect();
|
||||
return;
|
||||
}
|
||||
auto radii = rrect.GetRadii();
|
||||
DlRect safe = bounds.Expand(
|
||||
-std::max(radii.top_left.width, radii.bottom_left.width), 0,
|
||||
-std::max(radii.top_right.width, radii.bottom_right.width), 0);
|
||||
adjustCullRect(safe, op, is_aa);
|
||||
safe = bounds.Expand(
|
||||
0, -std::max(radii.top_left.height, radii.top_right.height), //
|
||||
0, -std::max(radii.bottom_left.height, radii.bottom_right.height));
|
||||
adjustCullRect(safe, op, is_aa);
|
||||
auto safe_rects = RoundingRadiiSafeRects(bounds, rrect.GetRadii());
|
||||
adjustCullRect(safe_rects[0], op, is_aa);
|
||||
adjustCullRect(safe_rects[1], op, is_aa);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayListMatrixClipState::clipRSuperellipse(
|
||||
const DlRoundSuperellipse& rse,
|
||||
DlClipOp op,
|
||||
bool is_aa) {
|
||||
DlRect bounds = rse.GetBounds();
|
||||
if (rse.IsRect()) {
|
||||
return clipRect(bounds, op, is_aa);
|
||||
}
|
||||
switch (op) {
|
||||
case DlClipOp::kIntersect:
|
||||
adjustCullRect(bounds, op, is_aa);
|
||||
break;
|
||||
case DlClipOp::kDifference: {
|
||||
if (rsuperellipse_covers_cull(rse)) {
|
||||
cull_rect_ = DlRect();
|
||||
return;
|
||||
}
|
||||
auto safe_rects = RoundingRadiiSafeRects(bounds, rse.GetRadii());
|
||||
adjustCullRect(safe_rects[0], op, is_aa);
|
||||
adjustCullRect(safe_rects[1], op, is_aa);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -299,6 +333,38 @@ bool DisplayListMatrixClipState::rrect_covers_cull(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayListMatrixClipState::rsuperellipse_covers_cull(
|
||||
const DlRoundSuperellipse& content) const {
|
||||
if (content.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (cull_rect_.IsEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (content.IsRect()) {
|
||||
return rect_covers_cull(content.GetBounds());
|
||||
}
|
||||
if (content.IsOval()) {
|
||||
return oval_covers_cull(content.GetBounds());
|
||||
}
|
||||
DlPoint corners[4];
|
||||
if (!getLocalCullCorners(corners)) {
|
||||
return false;
|
||||
}
|
||||
auto outer = content.GetBounds();
|
||||
auto param = impeller::RoundSuperellipseParam::MakeBoundsRadii(
|
||||
outer, content.GetRadii());
|
||||
for (auto corner : corners) {
|
||||
if (!outer.Contains(corner)) {
|
||||
return false;
|
||||
}
|
||||
if (!param.Contains(corner)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayListMatrixClipState::getLocalCullCorners(DlPoint corners[4]) const {
|
||||
if (!is_matrix_invertable()) {
|
||||
return false;
|
||||
|
@ -43,6 +43,7 @@ class DisplayListMatrixClipState {
|
||||
bool rect_covers_cull(const DlRect& content) const;
|
||||
bool oval_covers_cull(const DlRect& content_bounds) const;
|
||||
bool rrect_covers_cull(const DlRoundRect& content) const;
|
||||
bool rsuperellipse_covers_cull(const DlRoundSuperellipse& content) const;
|
||||
|
||||
bool content_culled(const DlRect& content_bounds) const;
|
||||
bool is_cull_rect_empty() const { return cull_rect_.IsEmpty(); }
|
||||
@ -107,6 +108,9 @@ class DisplayListMatrixClipState {
|
||||
void clipRect(const DlRect& rect, DlClipOp op, bool is_aa);
|
||||
void clipOval(const DlRect& bounds, DlClipOp op, bool is_aa);
|
||||
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa);
|
||||
void clipRSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp op,
|
||||
bool is_aa);
|
||||
void clipPath(const DlPath& path, DlClipOp op, bool is_aa);
|
||||
|
||||
private:
|
||||
|
@ -47,6 +47,9 @@ class IgnoreClipDispatchHelper : public virtual DlOpReceiver {
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override {}
|
||||
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {}
|
||||
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override {}
|
||||
};
|
||||
|
||||
// A utility class that will ignore all DlOpReceiver methods relating
|
||||
@ -92,6 +95,7 @@ class IgnoreDrawDispatchHelper : public virtual DlOpReceiver {
|
||||
void drawRoundRect(const DlRoundRect& rrect) override {}
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override {}
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override {}
|
||||
void drawPath(const DlPath& path) override {}
|
||||
void drawArc(const DlRect& oval_bounds,
|
||||
DlScalar start_degrees,
|
||||
|
@ -31,6 +31,8 @@ source_set("flow") {
|
||||
"layers/clip_rect_layer.h",
|
||||
"layers/clip_rrect_layer.cc",
|
||||
"layers/clip_rrect_layer.h",
|
||||
"layers/clip_rsuperellipse_layer.cc",
|
||||
"layers/clip_rsuperellipse_layer.h",
|
||||
"layers/clip_shape_layer.h",
|
||||
"layers/color_filter_layer.cc",
|
||||
"layers/color_filter_layer.h",
|
||||
@ -161,6 +163,7 @@ if (enable_unittests) {
|
||||
"layers/clip_path_layer_unittests.cc",
|
||||
"layers/clip_rect_layer_unittests.cc",
|
||||
"layers/clip_rrect_layer_unittests.cc",
|
||||
"layers/clip_rsuperellipse_layer_unittests.cc",
|
||||
"layers/color_filter_layer_unittests.cc",
|
||||
"layers/container_layer_unittests.cc",
|
||||
"layers/display_list_layer_unittests.cc",
|
||||
|
23
engine/src/flutter/flow/layers/clip_rsuperellipse_layer.cc
Normal file
23
engine/src/flutter/flow/layers/clip_rsuperellipse_layer.cc
Normal file
@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
#include "flutter/flow/layers/clip_rsuperellipse_layer.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
ClipRSuperellipseLayer::ClipRSuperellipseLayer(
|
||||
const DlRoundSuperellipse& clip_rsuperellipse,
|
||||
Clip clip_behavior)
|
||||
: ClipShapeLayer(clip_rsuperellipse, clip_behavior) {}
|
||||
|
||||
const DlRect ClipRSuperellipseLayer::clip_shape_bounds() const {
|
||||
return clip_shape().GetBounds();
|
||||
}
|
||||
|
||||
void ClipRSuperellipseLayer::ApplyClip(
|
||||
LayerStateStack::MutatorContext& mutator) const {
|
||||
mutator.clipRSuperellipse(clip_shape(), clip_behavior() != Clip::kHardEdge);
|
||||
}
|
||||
|
||||
} // namespace flutter
|
28
engine/src/flutter/flow/layers/clip_rsuperellipse_layer.h
Normal file
28
engine/src/flutter/flow/layers/clip_rsuperellipse_layer.h
Normal file
@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_FLOW_LAYERS_CLIP_RSUPERELLIPSE_LAYER_H_
|
||||
#define FLUTTER_FLOW_LAYERS_CLIP_RSUPERELLIPSE_LAYER_H_
|
||||
|
||||
#include "flutter/flow/layers/clip_shape_layer.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class ClipRSuperellipseLayer : public ClipShapeLayer<DlRoundSuperellipse> {
|
||||
public:
|
||||
ClipRSuperellipseLayer(const DlRoundSuperellipse& clip_rsuperellipse,
|
||||
Clip clip_behavior);
|
||||
|
||||
protected:
|
||||
const DlRect clip_shape_bounds() const override;
|
||||
|
||||
void ApplyClip(LayerStateStack::MutatorContext& mutator) const override;
|
||||
|
||||
private:
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(ClipRSuperellipseLayer);
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_FLOW_LAYERS_CLIP_RSUPERELLIPSE_LAYER_H_
|
@ -0,0 +1,628 @@
|
||||
// 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.
|
||||
|
||||
#include "flutter/flow/layers/clip_rsuperellipse_layer.h"
|
||||
|
||||
#include "flutter/flow/layers/layer_tree.h"
|
||||
#include "flutter/flow/layers/opacity_layer.h"
|
||||
#include "flutter/flow/layers/platform_view_layer.h"
|
||||
#include "flutter/flow/testing/layer_test.h"
|
||||
#include "flutter/flow/testing/mock_embedder.h"
|
||||
#include "flutter/flow/testing/mock_layer.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
|
||||
// TODO(zanderso): https://github.com/flutter/flutter/issues/127701
|
||||
// NOLINTBEGIN(bugprone-unchecked-optional-access)
|
||||
|
||||
namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
using ClipRSuperellipseLayerTest = LayerTest;
|
||||
|
||||
#ifndef NDEBUG
|
||||
TEST_F(ClipRSuperellipseLayerTest, ClipNoneBehaviorDies) {
|
||||
const DlRoundSuperellipse layer_rsuperellipse = DlRoundSuperellipse();
|
||||
EXPECT_DEATH_IF_SUPPORTED(
|
||||
auto clip = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
|
||||
Clip::kNone),
|
||||
"clip_behavior != Clip::kNone");
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, PaintingEmptyLayerDies) {
|
||||
const DlRoundSuperellipse layer_rsuperellipse = DlRoundSuperellipse();
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
|
||||
Clip::kHardEdge);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
|
||||
// Untouched
|
||||
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);
|
||||
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
|
||||
|
||||
EXPECT_EQ(layer->paint_bounds(), DlRect());
|
||||
EXPECT_EQ(layer->child_paint_bounds(), DlRect());
|
||||
EXPECT_FALSE(layer->needs_painting(paint_context()));
|
||||
|
||||
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
|
||||
"needs_painting\\(context\\)");
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, PaintBeforePrerollDies) {
|
||||
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
|
||||
const DlRoundSuperellipse layer_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRect(layer_bounds);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
|
||||
Clip::kHardEdge);
|
||||
EXPECT_EQ(layer->paint_bounds(), DlRect());
|
||||
EXPECT_EQ(layer->child_paint_bounds(), DlRect());
|
||||
EXPECT_FALSE(layer->needs_painting(paint_context()));
|
||||
|
||||
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
|
||||
"needs_painting\\(context\\)");
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, PaintingCulledLayerDies) {
|
||||
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
|
||||
const DlRect child_bounds = DlRect::MakeXYWH(1.0, 2.0, 2.0, 2.0);
|
||||
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
|
||||
const DlRect distant_bounds = DlRect::MakeXYWH(100.0, 100.0, 10.0, 10.0);
|
||||
const DlPath child_path = DlPath::MakeRect(child_bounds);
|
||||
const DlRoundSuperellipse layer_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRect(layer_bounds);
|
||||
const DlPaint child_paint = DlPaint(DlColor::kYellow());
|
||||
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
|
||||
Clip::kHardEdge);
|
||||
layer->Add(mock_layer);
|
||||
|
||||
// Cull these children
|
||||
preroll_context()->state_stack.set_preroll_delegate(distant_bounds,
|
||||
initial_matrix);
|
||||
layer->Preroll(preroll_context());
|
||||
|
||||
// Untouched
|
||||
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds);
|
||||
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
|
||||
|
||||
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
|
||||
EXPECT_EQ(layer->paint_bounds(), child_bounds);
|
||||
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
|
||||
EXPECT_TRUE(mock_layer->needs_painting(paint_context()));
|
||||
EXPECT_TRUE(layer->needs_painting(paint_context()));
|
||||
EXPECT_EQ(mock_layer->parent_cull_rect(), DlRect());
|
||||
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
|
||||
EXPECT_EQ(mock_layer->parent_mutators(),
|
||||
std::vector({Mutator(ToApproximateSkRRect(layer_rsuperellipse))}));
|
||||
|
||||
auto mutator = paint_context().state_stack.save();
|
||||
mutator.clipRect(distant_bounds, false);
|
||||
EXPECT_FALSE(mock_layer->needs_painting(paint_context()));
|
||||
EXPECT_FALSE(layer->needs_painting(paint_context()));
|
||||
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
|
||||
"needs_painting\\(context\\)");
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, ChildOutsideBounds) {
|
||||
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
|
||||
const DlRect local_cull_bounds = DlRect::MakeXYWH(0.0, 0.0, 2.0, 4.0);
|
||||
const DlRect device_cull_bounds =
|
||||
local_cull_bounds.TransformAndClipBounds(initial_matrix);
|
||||
const DlRect child_bounds = DlRect::MakeXYWH(2.5, 5.0, 4.5, 4.0);
|
||||
const DlRect clip_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
|
||||
const DlPath child_path = DlPath::MakeRect(child_bounds);
|
||||
const DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRect(clip_bounds);
|
||||
const DlPaint child_paint = DlPaint(DlColor::kYellow());
|
||||
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
|
||||
Clip::kHardEdge);
|
||||
layer->Add(mock_layer);
|
||||
|
||||
auto clip_cull_rect = clip_bounds.Intersection(local_cull_bounds);
|
||||
ASSERT_TRUE(clip_cull_rect.has_value());
|
||||
auto clip_layer_bounds = child_bounds.Intersection(clip_bounds);
|
||||
ASSERT_TRUE(clip_layer_bounds.has_value());
|
||||
|
||||
// Set up both contexts to cull clipped child
|
||||
preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,
|
||||
initial_matrix);
|
||||
paint_context().canvas->ClipRect(device_cull_bounds);
|
||||
paint_context().canvas->Transform(initial_matrix);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
// Untouched
|
||||
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),
|
||||
device_cull_bounds);
|
||||
EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),
|
||||
local_cull_bounds);
|
||||
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
|
||||
|
||||
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
|
||||
EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds.value());
|
||||
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
|
||||
EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect.value());
|
||||
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
|
||||
EXPECT_EQ(mock_layer->parent_mutators(),
|
||||
std::vector({Mutator(ToApproximateSkRRect(clip_rsuperellipse))}));
|
||||
|
||||
EXPECT_FALSE(mock_layer->needs_painting(paint_context()));
|
||||
ASSERT_FALSE(layer->needs_painting(paint_context()));
|
||||
// Top level layer not visible so calling layer->Paint()
|
||||
// would trip an FML_DCHECK
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, FullyContainedChild) {
|
||||
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
|
||||
const DlRect child_bounds = DlRect::MakeXYWH(1.0, 2.0, 2.0, 2.0);
|
||||
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
|
||||
const DlPath child_path = DlPath::MakeRect(child_bounds) +
|
||||
DlPath::MakeOval(child_bounds.Expand(-0.1f));
|
||||
const DlRoundSuperellipse layer_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(layer_bounds, 0.1, 0.1);
|
||||
const DlPaint child_paint = DlPaint(DlColor::kYellow());
|
||||
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
|
||||
Clip::kHardEdge);
|
||||
layer->Add(mock_layer);
|
||||
|
||||
preroll_context()->state_stack.set_preroll_delegate(initial_matrix);
|
||||
layer->Preroll(preroll_context());
|
||||
|
||||
// Untouched
|
||||
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);
|
||||
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
|
||||
|
||||
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
|
||||
EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());
|
||||
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
|
||||
EXPECT_TRUE(mock_layer->needs_painting(paint_context()));
|
||||
EXPECT_TRUE(layer->needs_painting(paint_context()));
|
||||
EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);
|
||||
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
|
||||
EXPECT_EQ(mock_layer->parent_mutators(),
|
||||
std::vector({Mutator(ToApproximateSkRRect(layer_rsuperellipse))}));
|
||||
|
||||
layer->Paint(display_list_paint_context());
|
||||
DisplayListBuilder expected_builder;
|
||||
/* (ClipRSuperellipse)layer::Paint */ {
|
||||
expected_builder.Save();
|
||||
{
|
||||
expected_builder.ClipRoundSuperellipse(layer_rsuperellipse);
|
||||
/* mock_layer::Paint */ {
|
||||
expected_builder.DrawPath(child_path, child_paint);
|
||||
}
|
||||
}
|
||||
expected_builder.Restore();
|
||||
}
|
||||
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, PartiallyContainedChild) {
|
||||
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
|
||||
const DlRect local_cull_bounds = DlRect::MakeXYWH(0.0, 0.0, 4.0, 5.5);
|
||||
const DlRect device_cull_bounds =
|
||||
local_cull_bounds.TransformAndClipBounds(initial_matrix);
|
||||
const DlRect child_bounds = DlRect::MakeXYWH(2.5, 5.0, 4.5, 4.0);
|
||||
const DlRect clip_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
|
||||
const DlPath child_path = DlPath::MakeRect(child_bounds) +
|
||||
DlPath::MakeOval(child_bounds.Expand(-0.1f));
|
||||
const DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(clip_bounds, 0.1, 0.1);
|
||||
const DlPaint child_paint = DlPaint(DlColor::kYellow());
|
||||
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
|
||||
Clip::kHardEdge);
|
||||
layer->Add(mock_layer);
|
||||
|
||||
auto clip_cull_rect = clip_bounds.Intersection(local_cull_bounds);
|
||||
ASSERT_TRUE(clip_cull_rect.has_value());
|
||||
auto clip_layer_bounds = child_bounds.Intersection(clip_bounds);
|
||||
ASSERT_TRUE(clip_layer_bounds.has_value());
|
||||
|
||||
preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,
|
||||
initial_matrix);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
// Untouched
|
||||
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),
|
||||
device_cull_bounds);
|
||||
EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),
|
||||
local_cull_bounds);
|
||||
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
|
||||
|
||||
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
|
||||
EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds.value());
|
||||
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
|
||||
EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect.value());
|
||||
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
|
||||
EXPECT_EQ(mock_layer->parent_mutators(),
|
||||
std::vector({Mutator(ToApproximateSkRRect(clip_rsuperellipse))}));
|
||||
|
||||
layer->Paint(display_list_paint_context());
|
||||
DisplayListBuilder expected_builder;
|
||||
/* (ClipRSuperellipse)layer::Paint */ {
|
||||
expected_builder.Save();
|
||||
{
|
||||
expected_builder.ClipRoundSuperellipse(clip_rsuperellipse);
|
||||
/* mock_layer::Paint */ {
|
||||
expected_builder.DrawPath(child_path, child_paint);
|
||||
}
|
||||
}
|
||||
expected_builder.Restore();
|
||||
}
|
||||
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
|
||||
}
|
||||
|
||||
static bool ReadbackResult(PrerollContext* context,
|
||||
Clip clip_behavior,
|
||||
const std::shared_ptr<Layer>& child,
|
||||
bool before) {
|
||||
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
|
||||
const DlRoundSuperellipse layer_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRect(layer_bounds);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
|
||||
clip_behavior);
|
||||
if (child != nullptr) {
|
||||
layer->Add(child);
|
||||
}
|
||||
context->surface_needs_readback = before;
|
||||
layer->Preroll(context);
|
||||
return context->surface_needs_readback;
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, Readback) {
|
||||
PrerollContext* context = preroll_context();
|
||||
DlPath path;
|
||||
DlPaint paint;
|
||||
|
||||
const Clip hard = Clip::kHardEdge;
|
||||
const Clip soft = Clip::kAntiAlias;
|
||||
const Clip save_layer = Clip::kAntiAliasWithSaveLayer;
|
||||
|
||||
std::shared_ptr<MockLayer> nochild;
|
||||
auto reader = std::make_shared<MockLayer>(path, paint);
|
||||
reader->set_fake_reads_surface(true);
|
||||
auto nonreader = std::make_shared<MockLayer>(path, paint);
|
||||
|
||||
// No children, no prior readback -> no readback after
|
||||
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
|
||||
|
||||
// No children, prior readback -> readback after
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
|
||||
|
||||
// Non readback child, no prior readback -> no readback after
|
||||
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
|
||||
|
||||
// Non readback child, prior readback -> readback after
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
|
||||
|
||||
// Readback child, no prior readback -> readback after unless SaveLayer
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
|
||||
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
|
||||
|
||||
// Readback child, prior readback -> readback after
|
||||
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
|
||||
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, OpacityInheritance) {
|
||||
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
|
||||
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
|
||||
DlRect clip_rect = DlRect::MakeWH(500, 500);
|
||||
DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
|
||||
auto clip_rsuperellipse_layer = std::make_shared<ClipRSuperellipseLayer>(
|
||||
clip_rsuperellipse, Clip::kHardEdge);
|
||||
clip_rsuperellipse_layer->Add(mock1);
|
||||
|
||||
// ClipRectLayer will pass through compatibility from a compatible child
|
||||
PrerollContext* context = preroll_context();
|
||||
clip_rsuperellipse_layer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags,
|
||||
LayerStateStack::kCallerCanApplyOpacity);
|
||||
|
||||
auto path2 = DlPath::MakeRectLTRB(40, 40, 50, 50);
|
||||
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
|
||||
clip_rsuperellipse_layer->Add(mock2);
|
||||
|
||||
// ClipRectLayer will pass through compatibility from multiple
|
||||
// non-overlapping compatible children
|
||||
clip_rsuperellipse_layer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags,
|
||||
LayerStateStack::kCallerCanApplyOpacity);
|
||||
|
||||
auto path3 = DlPath::MakeRectLTRB(20, 20, 40, 40);
|
||||
auto mock3 = MockLayer::MakeOpacityCompatible(path3);
|
||||
clip_rsuperellipse_layer->Add(mock3);
|
||||
|
||||
// ClipRectLayer will not pass through compatibility from multiple
|
||||
// overlapping children even if they are individually compatible
|
||||
clip_rsuperellipse_layer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, 0);
|
||||
|
||||
{
|
||||
// ClipRectLayer(aa with saveLayer) will always be compatible
|
||||
auto clip_rsuperellipse_savelayer =
|
||||
std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
|
||||
Clip::kAntiAliasWithSaveLayer);
|
||||
clip_rsuperellipse_savelayer->Add(mock1);
|
||||
clip_rsuperellipse_savelayer->Add(mock2);
|
||||
|
||||
// Double check first two children are compatible and non-overlapping
|
||||
clip_rsuperellipse_savelayer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
|
||||
|
||||
// Now add the overlapping child and test again, should still be compatible
|
||||
clip_rsuperellipse_savelayer->Add(mock3);
|
||||
clip_rsuperellipse_savelayer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
|
||||
}
|
||||
|
||||
// An incompatible, but non-overlapping child for the following tests
|
||||
auto path4 = DlPath::MakeRectLTRB(60, 60, 70, 70);
|
||||
auto mock4 = MockLayer::Make(path4);
|
||||
|
||||
{
|
||||
// ClipRectLayer with incompatible child will not be compatible
|
||||
auto clip_rsuperellipse_bad_child =
|
||||
std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
|
||||
Clip::kHardEdge);
|
||||
clip_rsuperellipse_bad_child->Add(mock1);
|
||||
clip_rsuperellipse_bad_child->Add(mock2);
|
||||
|
||||
// Double check first two children are compatible and non-overlapping
|
||||
clip_rsuperellipse_bad_child->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags,
|
||||
LayerStateStack::kCallerCanApplyOpacity);
|
||||
|
||||
clip_rsuperellipse_bad_child->Add(mock4);
|
||||
|
||||
// The third child is non-overlapping, but not compatible so the
|
||||
// TransformLayer should end up incompatible
|
||||
clip_rsuperellipse_bad_child->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// ClipRectLayer(aa with saveLayer) will always be compatible
|
||||
auto clip_rsuperellipse_savelayer_bad_child =
|
||||
std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
|
||||
Clip::kAntiAliasWithSaveLayer);
|
||||
clip_rsuperellipse_savelayer_bad_child->Add(mock1);
|
||||
clip_rsuperellipse_savelayer_bad_child->Add(mock2);
|
||||
|
||||
// Double check first two children are compatible and non-overlapping
|
||||
clip_rsuperellipse_savelayer_bad_child->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
|
||||
|
||||
// Now add the incompatible child and test again, should still be compatible
|
||||
clip_rsuperellipse_savelayer_bad_child->Add(mock4);
|
||||
clip_rsuperellipse_savelayer_bad_child->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, OpacityInheritancePainting) {
|
||||
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
|
||||
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
|
||||
auto path2 = DlPath::MakeRectLTRB(40, 40, 50, 50);
|
||||
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
|
||||
DlRect clip_rect = DlRect::MakeWH(500, 500);
|
||||
DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
|
||||
auto clip_rect_layer = std::make_shared<ClipRSuperellipseLayer>(
|
||||
clip_rsuperellipse, Clip::kAntiAlias);
|
||||
clip_rect_layer->Add(mock1);
|
||||
clip_rect_layer->Add(mock2);
|
||||
|
||||
// ClipRectLayer will pass through compatibility from multiple
|
||||
// non-overlapping compatible children
|
||||
PrerollContext* context = preroll_context();
|
||||
clip_rect_layer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags,
|
||||
LayerStateStack::kCallerCanApplyOpacity);
|
||||
|
||||
int opacity_alpha = 0x7F;
|
||||
DlPoint offset = DlPoint(10, 10);
|
||||
auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);
|
||||
opacity_layer->Add(clip_rect_layer);
|
||||
opacity_layer->Preroll(context);
|
||||
EXPECT_TRUE(opacity_layer->children_can_accept_opacity());
|
||||
|
||||
DisplayListBuilder expected_builder;
|
||||
/* OpacityLayer::Paint() */ {
|
||||
expected_builder.Save();
|
||||
{
|
||||
expected_builder.Translate(offset.x, offset.y);
|
||||
/* ClipRectLayer::Paint() */ {
|
||||
expected_builder.Save();
|
||||
expected_builder.ClipRoundSuperellipse(clip_rsuperellipse,
|
||||
DlClipOp::kIntersect, true);
|
||||
/* child layer1 paint */ {
|
||||
expected_builder.DrawPath(path1, DlPaint().setAlpha(opacity_alpha));
|
||||
}
|
||||
/* child layer2 paint */ {
|
||||
expected_builder.DrawPath(path2, DlPaint().setAlpha(opacity_alpha));
|
||||
}
|
||||
expected_builder.Restore();
|
||||
}
|
||||
}
|
||||
expected_builder.Restore();
|
||||
}
|
||||
|
||||
opacity_layer->Paint(display_list_paint_context());
|
||||
EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list()));
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, OpacityInheritanceSaveLayerPainting) {
|
||||
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
|
||||
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
|
||||
auto path2 = DlPath::MakeRectLTRB(20, 20, 40, 40);
|
||||
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
|
||||
auto children_bounds = path1.GetBounds().Union(path2.GetBounds());
|
||||
DlRect clip_rect = DlRect::MakeWH(500, 500);
|
||||
DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
|
||||
auto clip_rsuperellipse_layer = std::make_shared<ClipRSuperellipseLayer>(
|
||||
clip_rsuperellipse, Clip::kAntiAliasWithSaveLayer);
|
||||
clip_rsuperellipse_layer->Add(mock1);
|
||||
clip_rsuperellipse_layer->Add(mock2);
|
||||
|
||||
// ClipRectLayer will pass through compatibility from multiple
|
||||
// non-overlapping compatible children
|
||||
PrerollContext* context = preroll_context();
|
||||
clip_rsuperellipse_layer->Preroll(context);
|
||||
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
|
||||
|
||||
int opacity_alpha = 0x7F;
|
||||
DlPoint offset = DlPoint(10, 10);
|
||||
auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);
|
||||
opacity_layer->Add(clip_rsuperellipse_layer);
|
||||
opacity_layer->Preroll(context);
|
||||
EXPECT_TRUE(opacity_layer->children_can_accept_opacity());
|
||||
|
||||
DisplayListBuilder expected_builder;
|
||||
/* OpacityLayer::Paint() */ {
|
||||
expected_builder.Save();
|
||||
{
|
||||
expected_builder.Translate(offset.x, offset.y);
|
||||
/* ClipRectLayer::Paint() */ {
|
||||
expected_builder.Save();
|
||||
expected_builder.ClipRoundSuperellipse(clip_rsuperellipse,
|
||||
DlClipOp::kIntersect, true);
|
||||
expected_builder.SaveLayer(children_bounds,
|
||||
&DlPaint().setAlpha(opacity_alpha));
|
||||
/* child layer1 paint */ {
|
||||
expected_builder.DrawPath(path1, DlPaint());
|
||||
}
|
||||
/* child layer2 paint */ { //
|
||||
expected_builder.DrawPath(path2, DlPaint());
|
||||
}
|
||||
expected_builder.Restore();
|
||||
}
|
||||
}
|
||||
expected_builder.Restore();
|
||||
}
|
||||
|
||||
opacity_layer->Paint(display_list_paint_context());
|
||||
EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list()));
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, LayerCached) {
|
||||
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
|
||||
DlPaint paint = DlPaint();
|
||||
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
|
||||
DlRect clip_rect = DlRect::MakeWH(500, 500);
|
||||
DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(
|
||||
clip_rsuperellipse, Clip::kAntiAliasWithSaveLayer);
|
||||
layer->Add(mock1);
|
||||
|
||||
auto initial_transform = DlMatrix::MakeTranslation({50.0, 25.5});
|
||||
DlMatrix cache_ctm = initial_transform;
|
||||
DisplayListBuilder cache_canvas;
|
||||
cache_canvas.Transform(cache_ctm);
|
||||
|
||||
use_mock_raster_cache();
|
||||
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
|
||||
|
||||
const auto* clip_cache_item = layer->raster_cache_item();
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
|
||||
|
||||
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
|
||||
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
|
||||
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
|
||||
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
|
||||
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);
|
||||
EXPECT_EQ(clip_cache_item->cache_state(),
|
||||
RasterCacheItem::CacheState::kCurrent);
|
||||
EXPECT_TRUE(raster_cache()->Draw(clip_cache_item->GetId().value(),
|
||||
cache_canvas, &paint));
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, NoSaveLayerShouldNotCache) {
|
||||
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
|
||||
|
||||
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
|
||||
DlRect clip_rect = DlRect::MakeWH(500, 500);
|
||||
DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
|
||||
auto layer = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
|
||||
Clip::kAntiAlias);
|
||||
layer->Add(mock1);
|
||||
|
||||
auto initial_transform = DlMatrix::MakeTranslation({50.0, 25.5});
|
||||
|
||||
use_mock_raster_cache();
|
||||
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
|
||||
|
||||
const auto* clip_cache_item = layer->raster_cache_item();
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
|
||||
|
||||
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
|
||||
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
|
||||
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
|
||||
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
|
||||
|
||||
layer->Preroll(preroll_context());
|
||||
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
|
||||
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
|
||||
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
|
||||
}
|
||||
|
||||
TEST_F(ClipRSuperellipseLayerTest, EmptyClipDoesNotCullPlatformView) {
|
||||
const DlPoint view_offset = DlPoint(0.0f, 0.0f);
|
||||
const DlSize view_size = DlSize(8.0f, 8.0f);
|
||||
const int64_t view_id = 42;
|
||||
auto platform_view =
|
||||
std::make_shared<PlatformViewLayer>(view_offset, view_size, view_id);
|
||||
|
||||
DlRoundSuperellipse clip_rsuperellipse =
|
||||
DlRoundSuperellipse::MakeRectXY(DlRect(), 20, 20);
|
||||
auto clip = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
|
||||
Clip::kAntiAlias);
|
||||
clip->Add(platform_view);
|
||||
|
||||
auto embedder = MockViewEmbedder();
|
||||
DisplayListBuilder fake_overlay_builder;
|
||||
embedder.AddCanvas(&fake_overlay_builder);
|
||||
preroll_context()->view_embedder = &embedder;
|
||||
paint_context().view_embedder = &embedder;
|
||||
|
||||
clip->Preroll(preroll_context());
|
||||
EXPECT_EQ(embedder.prerolled_views(), std::vector<int64_t>({view_id}));
|
||||
|
||||
clip->Paint(paint_context());
|
||||
EXPECT_EQ(embedder.painted_views(), std::vector<int64_t>({view_id}));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
// NOLINTEND(bugprone-unchecked-optional-access)
|
@ -58,6 +58,9 @@ class DummyDelegate : public LayerStateStack::Delegate {
|
||||
|
||||
void clipRect(const DlRect& rect, DlClipOp op, bool is_aa) override {}
|
||||
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override {}
|
||||
void clipRSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp op,
|
||||
bool is_aa) override {}
|
||||
void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override {}
|
||||
|
||||
private:
|
||||
@ -121,6 +124,11 @@ class DlCanvasDelegate : public LayerStateStack::Delegate {
|
||||
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override {
|
||||
canvas_->ClipRoundRect(rrect, op, is_aa);
|
||||
}
|
||||
void clipRSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp op,
|
||||
bool is_aa) override {
|
||||
canvas_->ClipRoundSuperellipse(rse, op, is_aa);
|
||||
}
|
||||
void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override {
|
||||
canvas_->ClipPath(path, op, is_aa);
|
||||
}
|
||||
@ -176,6 +184,11 @@ class PrerollDelegate : public LayerStateStack::Delegate {
|
||||
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override {
|
||||
state().clipRRect(rrect, op, is_aa);
|
||||
}
|
||||
void clipRSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp op,
|
||||
bool is_aa) override {
|
||||
state().clipRSuperellipse(rse, op, is_aa);
|
||||
}
|
||||
void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override {
|
||||
state().clipPath(path, op, is_aa);
|
||||
}
|
||||
@ -446,6 +459,33 @@ class ClipRRectEntry : public LayerStateStack::StateEntry {
|
||||
FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipRRectEntry);
|
||||
};
|
||||
|
||||
class ClipRSuperellipseEntry : public LayerStateStack::StateEntry {
|
||||
public:
|
||||
ClipRSuperellipseEntry(const DlRoundSuperellipse& clip_rsuperellipse,
|
||||
bool is_aa)
|
||||
: clip_rsuperellipse_(clip_rsuperellipse), is_aa_(is_aa) {}
|
||||
|
||||
void apply(LayerStateStack* stack) const override {
|
||||
stack->delegate_->clipRSuperellipse(clip_rsuperellipse_,
|
||||
DlClipOp::kIntersect, is_aa_);
|
||||
}
|
||||
void update_mutators(MutatorsStack* mutators_stack) const override {
|
||||
// MutatorsStack doesn't support non-Skia classes, and therefore this method
|
||||
// has to use approximate RRect, which might cause trouble for certain
|
||||
// embedded apps.
|
||||
// TODO(dkwingsmt): Make this method push a correct ClipRoundedSuperellipse
|
||||
// mutator.
|
||||
// https://github.com/flutter/flutter/issues/163716
|
||||
mutators_stack->PushClipRRect(ToApproximateSkRRect(clip_rsuperellipse_));
|
||||
}
|
||||
|
||||
private:
|
||||
const DlRoundSuperellipse clip_rsuperellipse_;
|
||||
const bool is_aa_;
|
||||
|
||||
FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipRSuperellipseEntry);
|
||||
};
|
||||
|
||||
class ClipPathEntry : public LayerStateStack::StateEntry {
|
||||
public:
|
||||
ClipPathEntry(const DlPath& clip_path, bool is_aa)
|
||||
@ -570,6 +610,13 @@ void MutatorContext::clipRRect(const DlRoundRect& rrect, bool is_aa) {
|
||||
layer_state_stack_->push_clip_rrect(rrect, is_aa);
|
||||
}
|
||||
|
||||
void MutatorContext::clipRSuperellipse(const DlRoundSuperellipse& rse,
|
||||
bool is_aa) {
|
||||
layer_state_stack_->maybe_save_layer_for_clip(save_needed_);
|
||||
save_needed_ = false;
|
||||
layer_state_stack_->push_clip_rsuperellipse(rse, is_aa);
|
||||
}
|
||||
|
||||
void MutatorContext::clipPath(const DlPath& path, bool is_aa) {
|
||||
layer_state_stack_->maybe_save_layer_for_clip(save_needed_);
|
||||
save_needed_ = false;
|
||||
@ -700,6 +747,13 @@ void LayerStateStack::push_clip_rrect(const DlRoundRect& rrect, bool is_aa) {
|
||||
apply_last_entry();
|
||||
}
|
||||
|
||||
void LayerStateStack::push_clip_rsuperellipse(const DlRoundSuperellipse& rse,
|
||||
bool is_aa) {
|
||||
state_stack_.emplace_back(
|
||||
std::make_unique<ClipRSuperellipseEntry>(rse, is_aa));
|
||||
apply_last_entry();
|
||||
}
|
||||
|
||||
void LayerStateStack::push_clip_path(const DlPath& path, bool is_aa) {
|
||||
state_stack_.emplace_back(std::make_unique<ClipPathEntry>(path, is_aa));
|
||||
apply_last_entry();
|
||||
|
@ -207,6 +207,7 @@ class LayerStateStack {
|
||||
|
||||
void clipRect(const DlRect& rect, bool is_aa);
|
||||
void clipRRect(const DlRoundRect& rrect, bool is_aa);
|
||||
void clipRSuperellipse(const DlRoundSuperellipse& rse, bool is_aa);
|
||||
void clipPath(const DlPath& path, bool is_aa);
|
||||
|
||||
private:
|
||||
@ -334,6 +335,7 @@ class LayerStateStack {
|
||||
|
||||
void push_clip_rect(const DlRect& rect, bool is_aa);
|
||||
void push_clip_rrect(const DlRoundRect& rrect, bool is_aa);
|
||||
void push_clip_rsuperellipse(const DlRoundSuperellipse& rse, bool is_aa);
|
||||
void push_clip_path(const DlPath& path, bool is_aa);
|
||||
// ---------------------
|
||||
|
||||
@ -408,6 +410,7 @@ class LayerStateStack {
|
||||
friend class ClipEntry;
|
||||
friend class ClipRectEntry;
|
||||
friend class ClipRRectEntry;
|
||||
friend class ClipRSuperellipseEntry;
|
||||
friend class ClipPathEntry;
|
||||
|
||||
class Delegate {
|
||||
@ -447,6 +450,9 @@ class LayerStateStack {
|
||||
virtual void clipRRect(const DlRoundRect& rrect,
|
||||
DlClipOp op,
|
||||
bool is_aa) = 0;
|
||||
virtual void clipRSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp op,
|
||||
bool is_aa) = 0;
|
||||
virtual void clipPath(const DlPath& path, DlClipOp op, bool is_aa) = 0;
|
||||
};
|
||||
friend class DummyDelegate;
|
||||
|
@ -540,6 +540,29 @@ void Canvas::DrawRoundRect(const RoundRect& round_rect, const Paint& paint) {
|
||||
DrawPath(path, paint);
|
||||
}
|
||||
|
||||
void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& rse,
|
||||
const Paint& paint) {
|
||||
if (paint.style == Paint::Style::kFill) {
|
||||
// TODO(dkwingsmt): Investigate if RSE can use the `AttemptDrawBlurredRRect`
|
||||
// optimization at some point, such as a large enough mask radius.
|
||||
// https://github.com/flutter/flutter/issues/163893
|
||||
Entity entity;
|
||||
entity.SetTransform(GetCurrentTransform());
|
||||
entity.SetBlendMode(paint.blend_mode);
|
||||
|
||||
RoundSuperellipseGeometry geom(rse.GetBounds(), rse.GetRadii());
|
||||
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = PathBuilder{}
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.AddRoundSuperellipse(rse)
|
||||
.SetBounds(rse.GetBounds())
|
||||
.TakePath();
|
||||
DrawPath(path, paint);
|
||||
}
|
||||
|
||||
void Canvas::DrawCircle(const Point& center,
|
||||
Scalar radius,
|
||||
const Paint& paint) {
|
||||
|
@ -201,6 +201,8 @@ class Canvas {
|
||||
|
||||
void DrawRoundRect(const RoundRect& rect, const Paint& paint);
|
||||
|
||||
void DrawRoundSuperellipse(const RoundSuperellipse& rse, const Paint& paint);
|
||||
|
||||
void DrawCircle(const Point& center, Scalar radius, const Paint& paint);
|
||||
|
||||
void DrawPoints(const Point points[],
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "impeller/entity/geometry/fill_path_geometry.h"
|
||||
#include "impeller/entity/geometry/rect_geometry.h"
|
||||
#include "impeller/entity/geometry/round_rect_geometry.h"
|
||||
#include "impeller/entity/geometry/round_superellipse_geometry.h"
|
||||
#include "impeller/geometry/color.h"
|
||||
#include "impeller/geometry/path.h"
|
||||
#include "impeller/geometry/path_builder.h"
|
||||
@ -468,6 +469,25 @@ void DlDispatcherBase::clipRoundRect(const DlRoundRect& rrect,
|
||||
}
|
||||
}
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void DlDispatcherBase::clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
flutter::DlClipOp sk_op,
|
||||
bool is_aa) {
|
||||
AUTO_DEPTH_WATCHER(0u);
|
||||
|
||||
auto clip_op = ToClipOperation(sk_op);
|
||||
if (rse.IsRect()) {
|
||||
RectGeometry geom(rse.GetBounds());
|
||||
GetCanvas().ClipGeometry(geom, clip_op, /*is_aa=*/is_aa);
|
||||
} else if (rse.IsOval()) {
|
||||
EllipseGeometry geom(rse.GetBounds());
|
||||
GetCanvas().ClipGeometry(geom, clip_op);
|
||||
} else {
|
||||
RoundSuperellipseGeometry geom(rse.GetBounds(), rse.GetRadii());
|
||||
GetCanvas().ClipGeometry(geom, clip_op);
|
||||
}
|
||||
}
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void DlDispatcherBase::clipPath(const DlPath& path,
|
||||
flutter::DlClipOp sk_op,
|
||||
@ -603,6 +623,13 @@ void DlDispatcherBase::drawDiffRoundRect(const DlRoundRect& outer,
|
||||
GetCanvas().DrawPath(builder.TakePath(FillType::kOdd), paint_);
|
||||
}
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void DlDispatcherBase::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
|
||||
AUTO_DEPTH_WATCHER(1u);
|
||||
|
||||
GetCanvas().DrawRoundSuperellipse(rse, paint_);
|
||||
}
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void DlDispatcherBase::drawPath(const DlPath& path) {
|
||||
AUTO_DEPTH_WATCHER(1u);
|
||||
|
@ -25,6 +25,7 @@ using DlPoint = flutter::DlPoint;
|
||||
using DlRect = flutter::DlRect;
|
||||
using DlIRect = flutter::DlIRect;
|
||||
using DlRoundRect = flutter::DlRoundRect;
|
||||
using DlRoundSuperellipse = flutter::DlRoundSuperellipse;
|
||||
using DlPath = flutter::DlPath;
|
||||
|
||||
class DlDispatcherBase : public flutter::DlOpReceiver {
|
||||
@ -138,6 +139,11 @@ class DlDispatcherBase : public flutter::DlOpReceiver {
|
||||
flutter::DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
flutter::DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void clipPath(const DlPath& path,
|
||||
flutter::DlClipOp clip_op,
|
||||
@ -174,6 +180,9 @@ class DlDispatcherBase : public flutter::DlOpReceiver {
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override;
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
|
||||
|
||||
// |flutter::DlOpReceiver|
|
||||
void drawPath(const DlPath& path) override;
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "impeller/entity/geometry/line_geometry.h"
|
||||
#include "impeller/entity/geometry/rect_geometry.h"
|
||||
#include "impeller/entity/geometry/round_rect_geometry.h"
|
||||
#include "impeller/entity/geometry/round_superellipse_geometry.h"
|
||||
#include "impeller/entity/geometry/stroke_path_geometry.h"
|
||||
#include "impeller/geometry/rect.h"
|
||||
|
||||
@ -110,6 +111,12 @@ std::unique_ptr<Geometry> Geometry::MakeRoundRect(const Rect& rect,
|
||||
return std::make_unique<RoundRectGeometry>(rect, radii);
|
||||
}
|
||||
|
||||
std::unique_ptr<Geometry> Geometry::MakeRoundSuperellipse(
|
||||
const Rect& rect,
|
||||
Scalar corner_radius) {
|
||||
return std::make_unique<RoundSuperellipseGeometry>(rect, corner_radius);
|
||||
}
|
||||
|
||||
bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const {
|
||||
return false;
|
||||
}
|
||||
|
@ -83,6 +83,9 @@ class Geometry {
|
||||
static std::unique_ptr<Geometry> MakeRoundRect(const Rect& rect,
|
||||
const Size& radii);
|
||||
|
||||
static std::unique_ptr<Geometry> MakeRoundSuperellipse(const Rect& rect,
|
||||
Scalar corner_radius);
|
||||
|
||||
virtual GeometryResult GetPositionBuffer(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const = 0;
|
||||
|
@ -12,14 +12,12 @@ namespace impeller {
|
||||
|
||||
/// Geometry class that can generate vertices for a rounded superellipse.
|
||||
///
|
||||
/// A superellipse is an ellipse-like shape that is defined by the parameters N,
|
||||
/// alpha, and beta:
|
||||
///
|
||||
/// 1 = |x / b| ^n + |y / a| ^n
|
||||
///
|
||||
/// A rounded superellipse is a square-like superellipse (a=b) with its four
|
||||
/// corners replaced by circular arcs. It replicates the `RoundedRectangle`
|
||||
/// shape in SwiftUI with corner style `.continuous`.
|
||||
/// A rounded superellipse is a shape similar to a typical rounded rectangle
|
||||
/// (`RoundRect`), but with smoother transitions between the straight sides and
|
||||
/// the rounded corners. It resembles the `RoundedRectangle` shape in SwiftUI
|
||||
/// with the `.continuous` corner style. Technically, it is created by replacing
|
||||
/// the four corners of a superellipse (also known as a Lamé curve) with
|
||||
/// circular arcs.
|
||||
///
|
||||
/// The `bounds` defines the position and size of the shape. The `corner_radius`
|
||||
/// corresponds to SwiftUI's `cornerRadius` parameter, which is close to, but
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "flutter/impeller/geometry/round_superellipse.h"
|
||||
|
||||
#include "flutter/impeller/geometry/round_rect.h"
|
||||
#include "flutter/impeller/geometry/round_superellipse_param.h"
|
||||
|
||||
namespace impeller {
|
||||
@ -30,4 +31,11 @@ RoundSuperellipse RoundSuperellipse::MakeRectRadii(
|
||||
return param.Contains(p);
|
||||
}
|
||||
|
||||
RoundRect RoundSuperellipse::ToApproximateRoundRect() const {
|
||||
// Experiments have shown that using the same corner radii for the RRect
|
||||
// provides an approximation that is close to optimal, as achieving a perfect
|
||||
// match is not feasible.
|
||||
return RoundRect::MakeRectRadii(GetBounds(), GetRadii());
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
struct RoundRect;
|
||||
|
||||
struct RoundSuperellipse {
|
||||
RoundSuperellipse() = default;
|
||||
|
||||
@ -124,6 +126,14 @@ struct RoundSuperellipse {
|
||||
return !(*this == r);
|
||||
}
|
||||
|
||||
// Approximates a rounded superellipse with a round rectangle to the
|
||||
// best practical accuracy.
|
||||
//
|
||||
// This is used for Skia backends, which does not support rounded
|
||||
// superellipses directly, so rendering rounded superellipses
|
||||
// falls back to RRect.
|
||||
[[nodiscard]] RoundRect ToApproximateRoundRect() const;
|
||||
|
||||
private:
|
||||
constexpr RoundSuperellipse(const Rect& bounds, const RoundingRadii& radii)
|
||||
: bounds_(bounds), radii_(radii) {}
|
||||
|
@ -103,6 +103,8 @@ source_set("ui") {
|
||||
"painting/picture_recorder.h",
|
||||
"painting/rrect.cc",
|
||||
"painting/rrect.h",
|
||||
"painting/rsuperellipse.cc",
|
||||
"painting/rsuperellipse.h",
|
||||
"painting/shader.cc",
|
||||
"painting/shader.h",
|
||||
"painting/single_frame_codec.cc",
|
||||
|
@ -174,6 +174,15 @@ class ClipRRectEngineLayer extends _EngineLayerWrapper {
|
||||
ClipRRectEngineLayer._(super.nativeLayer) : super._();
|
||||
}
|
||||
|
||||
/// An opaque handle to a clip rounded superellipse engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushClipRSuperellipse].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class ClipRSuperellipseEngineLayer extends _EngineLayerWrapper {
|
||||
ClipRSuperellipseEngineLayer._(super.nativeLayer) : super._();
|
||||
}
|
||||
|
||||
/// An opaque handle to a clip path engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushClipPath].
|
||||
@ -325,6 +334,22 @@ abstract class SceneBuilder {
|
||||
ClipRRectEngineLayer? oldLayer,
|
||||
});
|
||||
|
||||
/// Pushes a rounded-superellipse clip operation onto the operation stack.
|
||||
///
|
||||
/// Rasterization outside the given rounded superellipse is discarded.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
|
||||
/// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
|
||||
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
|
||||
RSuperellipse rse, {
|
||||
Clip clipBehavior = Clip.antiAlias,
|
||||
ClipRSuperellipseEngineLayer? oldLayer,
|
||||
});
|
||||
|
||||
/// Pushes a path clip operation onto the operation stack.
|
||||
///
|
||||
/// Rasterization outside the given path is discarded.
|
||||
@ -720,6 +745,36 @@ base class _NativeSceneBuilder extends NativeFieldWrapperClass1 implements Scene
|
||||
EngineLayer? oldLayer,
|
||||
);
|
||||
|
||||
@override
|
||||
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
|
||||
RSuperellipse rse, {
|
||||
Clip clipBehavior = Clip.antiAlias,
|
||||
ClipRSuperellipseEngineLayer? oldLayer,
|
||||
}) {
|
||||
assert(clipBehavior != Clip.none);
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRSuperellipse'));
|
||||
final EngineLayer engineLayer = _NativeEngineLayer._();
|
||||
_pushClipRSuperellipse(
|
||||
engineLayer,
|
||||
rse._getValue32(),
|
||||
clipBehavior.index,
|
||||
oldLayer?._nativeLayer,
|
||||
);
|
||||
final ClipRSuperellipseEngineLayer layer = ClipRSuperellipseEngineLayer._(engineLayer);
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
|
||||
@Native<Void Function(Pointer<Void>, Handle, Handle, Int32, Handle)>(
|
||||
symbol: 'SceneBuilder::pushClipRSuperellipse',
|
||||
)
|
||||
external void _pushClipRSuperellipse(
|
||||
EngineLayer layer,
|
||||
Float32List rrect,
|
||||
int clipBehavior,
|
||||
EngineLayer? oldLayer,
|
||||
);
|
||||
|
||||
@override
|
||||
ClipPathEngineLayer pushClipPath(
|
||||
Path path, {
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "flutter/flow/layers/clip_path_layer.h"
|
||||
#include "flutter/flow/layers/clip_rect_layer.h"
|
||||
#include "flutter/flow/layers/clip_rrect_layer.h"
|
||||
#include "flutter/flow/layers/clip_rsuperellipse_layer.h"
|
||||
#include "flutter/flow/layers/color_filter_layer.h"
|
||||
#include "flutter/flow/layers/container_layer.h"
|
||||
#include "flutter/flow/layers/display_list_layer.h"
|
||||
@ -101,6 +102,21 @@ void SceneBuilder::pushClipRRect(Dart_Handle layer_handle,
|
||||
}
|
||||
}
|
||||
|
||||
void SceneBuilder::pushClipRSuperellipse(
|
||||
Dart_Handle layer_handle,
|
||||
const RSuperellipse& rse,
|
||||
int clip_behavior,
|
||||
const fml::RefPtr<EngineLayer>& old_layer) {
|
||||
auto layer = std::make_shared<flutter::ClipRSuperellipseLayer>(
|
||||
rse.rsuperellipse, static_cast<flutter::Clip>(clip_behavior));
|
||||
PushLayer(layer);
|
||||
EngineLayer::MakeRetained(layer_handle, layer);
|
||||
|
||||
if (old_layer && old_layer->Layer()) {
|
||||
layer->AssignOldLayer(old_layer->Layer().get());
|
||||
}
|
||||
}
|
||||
|
||||
void SceneBuilder::pushClipPath(Dart_Handle layer_handle,
|
||||
const CanvasPath* path,
|
||||
int clip_behavior,
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "flutter/lib/ui/painting/path.h"
|
||||
#include "flutter/lib/ui/painting/picture.h"
|
||||
#include "flutter/lib/ui/painting/rrect.h"
|
||||
#include "flutter/lib/ui/painting/rsuperellipse.h"
|
||||
#include "flutter/lib/ui/painting/shader.h"
|
||||
#include "third_party/tonic/typed_data/typed_list.h"
|
||||
|
||||
@ -59,6 +60,10 @@ class SceneBuilder : public RefCountedDartWrappable<SceneBuilder> {
|
||||
const RRect& rrect,
|
||||
int clip_behavior,
|
||||
const fml::RefPtr<EngineLayer>& old_layer);
|
||||
void pushClipRSuperellipse(Dart_Handle layer_handle,
|
||||
const RSuperellipse& rse,
|
||||
int clip_behavior,
|
||||
const fml::RefPtr<EngineLayer>& old_layer);
|
||||
void pushClipPath(Dart_Handle layer_handle,
|
||||
const CanvasPath* path,
|
||||
int clip_behavior,
|
||||
|
@ -138,11 +138,13 @@ typedef CanvasPath Path;
|
||||
V(Canvas, clipPath) \
|
||||
V(Canvas, clipRect) \
|
||||
V(Canvas, clipRRect) \
|
||||
V(Canvas, clipRSuperellipse) \
|
||||
V(Canvas, drawArc) \
|
||||
V(Canvas, drawAtlas) \
|
||||
V(Canvas, drawCircle) \
|
||||
V(Canvas, drawColor) \
|
||||
V(Canvas, drawDRRect) \
|
||||
V(Canvas, drawRSuperellipse) \
|
||||
V(Canvas, drawImage) \
|
||||
V(Canvas, drawImageNine) \
|
||||
V(Canvas, drawImageRect) \
|
||||
@ -288,8 +290,9 @@ typedef CanvasPath Path;
|
||||
V(SceneBuilder, pop) \
|
||||
V(SceneBuilder, pushBackdropFilter) \
|
||||
V(SceneBuilder, pushClipPath) \
|
||||
V(SceneBuilder, pushClipRRect) \
|
||||
V(SceneBuilder, pushClipRect) \
|
||||
V(SceneBuilder, pushClipRRect) \
|
||||
V(SceneBuilder, pushClipRSuperellipse) \
|
||||
V(SceneBuilder, pushColorFilter) \
|
||||
V(SceneBuilder, pushImageFilter) \
|
||||
V(SceneBuilder, pushOffset) \
|
||||
|
@ -1093,162 +1093,21 @@ class Radius {
|
||||
}
|
||||
}
|
||||
|
||||
/// An immutable rounded rectangle with the custom radii for all four corners.
|
||||
class RRect {
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and the same radii along its horizontal axis and its vertical axis.
|
||||
///
|
||||
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
|
||||
const RRect.fromLTRBXY(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom,
|
||||
double radiusX,
|
||||
double radiusY,
|
||||
) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and the same radius in each corner.
|
||||
///
|
||||
/// Will assert in debug mode if the `radius` is negative in either x or y.
|
||||
RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
|
||||
: this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and the same radii
|
||||
/// along its horizontal axis and its vertical axis.
|
||||
///
|
||||
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
|
||||
RRect.fromRectXY(Rect rect, double radiusX, double radiusY)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and a radius that is
|
||||
/// the same in each corner.
|
||||
///
|
||||
/// Will assert in debug mode if the `radius` is negative in either x or y.
|
||||
RRect.fromRectAndRadius(Rect rect, Radius radius)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and topLeft, topRight, bottomRight, and bottomLeft radii.
|
||||
///
|
||||
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
|
||||
/// assert in debug mode if any of the radii are negative in either x or y.
|
||||
RRect.fromLTRBAndCorners(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and topLeft,
|
||||
/// topRight, bottomRight, and bottomLeft radii.
|
||||
///
|
||||
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
|
||||
/// assert in debug mode if any of the radii are negative in either x or y.
|
||||
RRect.fromRectAndCorners(
|
||||
Rect rect, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
);
|
||||
|
||||
const RRect._raw({
|
||||
this.left = 0.0,
|
||||
this.top = 0.0,
|
||||
this.right = 0.0,
|
||||
this.bottom = 0.0,
|
||||
this.tlRadiusX = 0.0,
|
||||
this.tlRadiusY = 0.0,
|
||||
this.trRadiusX = 0.0,
|
||||
this.trRadiusY = 0.0,
|
||||
this.brRadiusX = 0.0,
|
||||
this.brRadiusY = 0.0,
|
||||
this.blRadiusX = 0.0,
|
||||
this.blRadiusY = 0.0,
|
||||
// The common base class for `RRect` and `RSuperellipse`.
|
||||
abstract class _RRectLike<T extends _RRectLike<T>> {
|
||||
const _RRectLike({
|
||||
required this.left,
|
||||
required this.top,
|
||||
required this.right,
|
||||
required this.bottom,
|
||||
required this.tlRadiusX,
|
||||
required this.tlRadiusY,
|
||||
required this.trRadiusX,
|
||||
required this.trRadiusY,
|
||||
required this.brRadiusX,
|
||||
required this.brRadiusY,
|
||||
required this.blRadiusX,
|
||||
required this.blRadiusY,
|
||||
}) : assert(tlRadiusX >= 0),
|
||||
assert(tlRadiusY >= 0),
|
||||
assert(trRadiusX >= 0),
|
||||
@ -1258,6 +1117,25 @@ class RRect {
|
||||
assert(blRadiusX >= 0),
|
||||
assert(blRadiusY >= 0);
|
||||
|
||||
// Implemented by a subclass to return an object constructed with the given
|
||||
// parameters.
|
||||
//
|
||||
// Used by various methods that construct an object of the same shape.
|
||||
T _create({
|
||||
required double left,
|
||||
required double top,
|
||||
required double right,
|
||||
required double bottom,
|
||||
required double tlRadiusX,
|
||||
required double tlRadiusY,
|
||||
required double trRadiusX,
|
||||
required double trRadiusY,
|
||||
required double brRadiusX,
|
||||
required double brRadiusY,
|
||||
required double blRadiusX,
|
||||
required double blRadiusY,
|
||||
});
|
||||
|
||||
Float32List _getValue32() {
|
||||
final Float32List result = Float32List(12);
|
||||
result[0] = left;
|
||||
@ -1323,12 +1201,9 @@ class RRect {
|
||||
/// The bottom-left [Radius].
|
||||
Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
|
||||
|
||||
/// A rounded rectangle with all the values set to zero.
|
||||
static const RRect zero = RRect._raw();
|
||||
|
||||
/// Returns a new [RRect] translated by the given offset.
|
||||
RRect shift(Offset offset) {
|
||||
return RRect._raw(
|
||||
/// Returns a clone translated by the given offset.
|
||||
T shift(Offset offset) {
|
||||
return _create(
|
||||
left: left + offset.dx,
|
||||
top: top + offset.dy,
|
||||
right: right + offset.dx,
|
||||
@ -1344,10 +1219,10 @@ class RRect {
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a new [RRect] with edges and radii moved outwards by the given
|
||||
/// Returns a clone with edges and radii moved outwards by the given
|
||||
/// delta.
|
||||
RRect inflate(double delta) {
|
||||
return RRect._raw(
|
||||
T inflate(double delta) {
|
||||
return _create(
|
||||
left: left - delta,
|
||||
top: top - delta,
|
||||
right: right + delta,
|
||||
@ -1363,8 +1238,8 @@ class RRect {
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a new [RRect] with edges and radii moved inwards by the given delta.
|
||||
RRect deflate(double delta) => inflate(-delta);
|
||||
/// Returns a clone with edges and radii moved inwards by the given delta.
|
||||
T deflate(double delta) => inflate(-delta);
|
||||
|
||||
/// The distance between the left and right edges of this rectangle.
|
||||
double get width => right - left;
|
||||
@ -1516,7 +1391,7 @@ class RRect {
|
||||
///
|
||||
/// See the [Skia scaling implementation](https://github.com/google/skia/blob/main/src/core/SkRRect.cpp)
|
||||
/// for more details.
|
||||
RRect scaleRadii() {
|
||||
T scaleRadii() {
|
||||
double scale = 1.0;
|
||||
scale = _getMin(scale, blRadiusY, tlRadiusY, height);
|
||||
scale = _getMin(scale, tlRadiusX, trRadiusX, width);
|
||||
@ -1525,7 +1400,7 @@ class RRect {
|
||||
assert(scale >= 0);
|
||||
|
||||
if (scale < 1.0) {
|
||||
return RRect._raw(
|
||||
return _create(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
@ -1541,7 +1416,7 @@ class RRect {
|
||||
);
|
||||
}
|
||||
|
||||
return RRect._raw(
|
||||
return _create(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
@ -1557,6 +1432,294 @@ class RRect {
|
||||
);
|
||||
}
|
||||
|
||||
// Linearly interpolate between this object and another of the same shape.
|
||||
T _lerpTo(T? b, double t) {
|
||||
assert(runtimeType == T);
|
||||
if (b == null) {
|
||||
final double k = 1.0 - t;
|
||||
return _create(
|
||||
left: left * k,
|
||||
top: top * k,
|
||||
right: right * k,
|
||||
bottom: bottom * k,
|
||||
tlRadiusX: math.max(0, tlRadiusX * k),
|
||||
tlRadiusY: math.max(0, tlRadiusY * k),
|
||||
trRadiusX: math.max(0, trRadiusX * k),
|
||||
trRadiusY: math.max(0, trRadiusY * k),
|
||||
brRadiusX: math.max(0, brRadiusX * k),
|
||||
brRadiusY: math.max(0, brRadiusY * k),
|
||||
blRadiusX: math.max(0, blRadiusX * k),
|
||||
blRadiusY: math.max(0, blRadiusY * k),
|
||||
);
|
||||
} else {
|
||||
return _create(
|
||||
left: _lerpDouble(left, b.left, t),
|
||||
top: _lerpDouble(top, b.top, t),
|
||||
right: _lerpDouble(right, b.right, t),
|
||||
bottom: _lerpDouble(bottom, b.bottom, t),
|
||||
tlRadiusX: math.max(0, _lerpDouble(tlRadiusX, b.tlRadiusX, t)),
|
||||
tlRadiusY: math.max(0, _lerpDouble(tlRadiusY, b.tlRadiusY, t)),
|
||||
trRadiusX: math.max(0, _lerpDouble(trRadiusX, b.trRadiusX, t)),
|
||||
trRadiusY: math.max(0, _lerpDouble(trRadiusY, b.trRadiusY, t)),
|
||||
brRadiusX: math.max(0, _lerpDouble(brRadiusX, b.brRadiusX, t)),
|
||||
brRadiusY: math.max(0, _lerpDouble(brRadiusY, b.brRadiusY, t)),
|
||||
blRadiusX: math.max(0, _lerpDouble(blRadiusX, b.blRadiusX, t)),
|
||||
blRadiusY: math.max(0, _lerpDouble(blRadiusY, b.blRadiusY, t)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is _RRectLike &&
|
||||
other.left == left &&
|
||||
other.top == top &&
|
||||
other.right == right &&
|
||||
other.bottom == bottom &&
|
||||
other.tlRadiusX == tlRadiusX &&
|
||||
other.tlRadiusY == tlRadiusY &&
|
||||
other.trRadiusX == trRadiusX &&
|
||||
other.trRadiusY == trRadiusY &&
|
||||
other.blRadiusX == blRadiusX &&
|
||||
other.blRadiusY == blRadiusY &&
|
||||
other.brRadiusX == brRadiusX &&
|
||||
other.brRadiusY == brRadiusY;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
tlRadiusX,
|
||||
tlRadiusY,
|
||||
trRadiusX,
|
||||
trRadiusY,
|
||||
blRadiusX,
|
||||
blRadiusY,
|
||||
brRadiusX,
|
||||
brRadiusY,
|
||||
);
|
||||
|
||||
String _toString({required String className}) {
|
||||
final String rect =
|
||||
'${left.toStringAsFixed(1)}, '
|
||||
'${top.toStringAsFixed(1)}, '
|
||||
'${right.toStringAsFixed(1)}, '
|
||||
'${bottom.toStringAsFixed(1)}';
|
||||
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) {
|
||||
if (tlRadius.x == tlRadius.y) {
|
||||
return '$className.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
|
||||
}
|
||||
return '$className.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
|
||||
}
|
||||
return '$className.fromLTRBAndCorners('
|
||||
'$rect, '
|
||||
'topLeft: $tlRadius, '
|
||||
'topRight: $trRadius, '
|
||||
'bottomRight: $brRadius, '
|
||||
'bottomLeft: $blRadius'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
/// An immutable rounded rectangle with the custom radii for all four corners.
|
||||
class RRect extends _RRectLike<RRect> {
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and the same radii along its horizontal axis and its vertical axis.
|
||||
///
|
||||
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
|
||||
const RRect.fromLTRBXY(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom,
|
||||
double radiusX,
|
||||
double radiusY,
|
||||
) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and the same radius in each corner.
|
||||
///
|
||||
/// Will assert in debug mode if the `radius` is negative in either x or y.
|
||||
RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
|
||||
: this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and the same radii
|
||||
/// along its horizontal axis and its vertical axis.
|
||||
///
|
||||
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
|
||||
RRect.fromRectXY(Rect rect, double radiusX, double radiusY)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and a radius that is
|
||||
/// the same in each corner.
|
||||
///
|
||||
/// Will assert in debug mode if the `radius` is negative in either x or y.
|
||||
RRect.fromRectAndRadius(Rect rect, Radius radius)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and topLeft, topRight, bottomRight, and bottomLeft radii.
|
||||
///
|
||||
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
|
||||
/// assert in debug mode if any of the radii are negative in either x or y.
|
||||
RRect.fromLTRBAndCorners(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and topLeft,
|
||||
/// topRight, bottomRight, and bottomLeft radii.
|
||||
///
|
||||
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
|
||||
/// assert in debug mode if any of the radii are negative in either x or y.
|
||||
RRect.fromRectAndCorners(
|
||||
Rect rect, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
);
|
||||
|
||||
const RRect._raw({
|
||||
super.left = 0.0,
|
||||
super.top = 0.0,
|
||||
super.right = 0.0,
|
||||
super.bottom = 0.0,
|
||||
super.tlRadiusX = 0.0,
|
||||
super.tlRadiusY = 0.0,
|
||||
super.trRadiusX = 0.0,
|
||||
super.trRadiusY = 0.0,
|
||||
super.brRadiusX = 0.0,
|
||||
super.brRadiusY = 0.0,
|
||||
super.blRadiusX = 0.0,
|
||||
super.blRadiusY = 0.0,
|
||||
});
|
||||
|
||||
@override
|
||||
RRect _create({
|
||||
required double left,
|
||||
required double top,
|
||||
required double right,
|
||||
required double bottom,
|
||||
required double tlRadiusX,
|
||||
required double tlRadiusY,
|
||||
required double trRadiusX,
|
||||
required double trRadiusY,
|
||||
required double brRadiusX,
|
||||
required double brRadiusY,
|
||||
required double blRadiusX,
|
||||
required double blRadiusY,
|
||||
}) => RRect._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
|
||||
/// A rounded rectangle with all the values set to zero.
|
||||
static const RRect zero = RRect._raw();
|
||||
|
||||
/// Whether the point specified by the given offset (which is assumed to be
|
||||
/// relative to the origin) lies inside the rounded rectangle.
|
||||
///
|
||||
@ -1626,120 +1789,254 @@ class RRect {
|
||||
/// Values for `t` are usually obtained from an [Animation<double>], such as
|
||||
/// an [AnimationController].
|
||||
static RRect? lerp(RRect? a, RRect? b, double t) {
|
||||
if (a == null) {
|
||||
if (b == null) {
|
||||
if (a == null) {
|
||||
return null;
|
||||
} else {
|
||||
final double k = 1.0 - t;
|
||||
return RRect._raw(
|
||||
left: a.left * k,
|
||||
top: a.top * k,
|
||||
right: a.right * k,
|
||||
bottom: a.bottom * k,
|
||||
tlRadiusX: math.max(0, a.tlRadiusX * k),
|
||||
tlRadiusY: math.max(0, a.tlRadiusY * k),
|
||||
trRadiusX: math.max(0, a.trRadiusX * k),
|
||||
trRadiusY: math.max(0, a.trRadiusY * k),
|
||||
brRadiusX: math.max(0, a.brRadiusX * k),
|
||||
brRadiusY: math.max(0, a.brRadiusY * k),
|
||||
blRadiusX: math.max(0, a.blRadiusX * k),
|
||||
blRadiusY: math.max(0, a.blRadiusY * k),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (a == null) {
|
||||
return RRect._raw(
|
||||
left: b.left * t,
|
||||
top: b.top * t,
|
||||
right: b.right * t,
|
||||
bottom: b.bottom * t,
|
||||
tlRadiusX: math.max(0, b.tlRadiusX * t),
|
||||
tlRadiusY: math.max(0, b.tlRadiusY * t),
|
||||
trRadiusX: math.max(0, b.trRadiusX * t),
|
||||
trRadiusY: math.max(0, b.trRadiusY * t),
|
||||
brRadiusX: math.max(0, b.brRadiusX * t),
|
||||
brRadiusY: math.max(0, b.brRadiusY * t),
|
||||
blRadiusX: math.max(0, b.blRadiusX * t),
|
||||
blRadiusY: math.max(0, b.blRadiusY * t),
|
||||
);
|
||||
} else {
|
||||
return RRect._raw(
|
||||
left: _lerpDouble(a.left, b.left, t),
|
||||
top: _lerpDouble(a.top, b.top, t),
|
||||
right: _lerpDouble(a.right, b.right, t),
|
||||
bottom: _lerpDouble(a.bottom, b.bottom, t),
|
||||
tlRadiusX: math.max(0, _lerpDouble(a.tlRadiusX, b.tlRadiusX, t)),
|
||||
tlRadiusY: math.max(0, _lerpDouble(a.tlRadiusY, b.tlRadiusY, t)),
|
||||
trRadiusX: math.max(0, _lerpDouble(a.trRadiusX, b.trRadiusX, t)),
|
||||
trRadiusY: math.max(0, _lerpDouble(a.trRadiusY, b.trRadiusY, t)),
|
||||
brRadiusX: math.max(0, _lerpDouble(a.brRadiusX, b.brRadiusX, t)),
|
||||
brRadiusY: math.max(0, _lerpDouble(a.brRadiusY, b.brRadiusY, t)),
|
||||
blRadiusX: math.max(0, _lerpDouble(a.blRadiusX, b.blRadiusX, t)),
|
||||
blRadiusY: math.max(0, _lerpDouble(a.blRadiusY, b.blRadiusY, t)),
|
||||
);
|
||||
return b._lerpTo(null, 1 - t);
|
||||
}
|
||||
return a._lerpTo(b, t);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is RRect &&
|
||||
other.left == left &&
|
||||
other.top == top &&
|
||||
other.right == right &&
|
||||
other.bottom == bottom &&
|
||||
other.tlRadiusX == tlRadiusX &&
|
||||
other.tlRadiusY == tlRadiusY &&
|
||||
other.trRadiusX == trRadiusX &&
|
||||
other.trRadiusY == trRadiusY &&
|
||||
other.blRadiusX == blRadiusX &&
|
||||
other.blRadiusY == blRadiusY &&
|
||||
other.brRadiusX == brRadiusX &&
|
||||
other.brRadiusY == brRadiusY;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
tlRadiusX,
|
||||
tlRadiusY,
|
||||
trRadiusX,
|
||||
trRadiusY,
|
||||
blRadiusX,
|
||||
blRadiusY,
|
||||
brRadiusX,
|
||||
brRadiusY,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final String rect =
|
||||
'${left.toStringAsFixed(1)}, '
|
||||
'${top.toStringAsFixed(1)}, '
|
||||
'${right.toStringAsFixed(1)}, '
|
||||
'${bottom.toStringAsFixed(1)}';
|
||||
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) {
|
||||
if (tlRadius.x == tlRadius.y) {
|
||||
return 'RRect.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
|
||||
return _toString(className: 'RRect');
|
||||
}
|
||||
return 'RRect.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
|
||||
}
|
||||
return 'RRect.fromLTRBAndCorners('
|
||||
'$rect, '
|
||||
'topLeft: $tlRadius, '
|
||||
'topRight: $trRadius, '
|
||||
'bottomRight: $brRadius, '
|
||||
'bottomLeft: $blRadius'
|
||||
')';
|
||||
|
||||
/// An immutable rounded superellipse.
|
||||
///
|
||||
/// A rounded superellipse is a shape similar to a typical rounded rectangle
|
||||
/// ([RRect]), but with smoother transitions between the straight sides and the
|
||||
/// rounded corners. It resembles the `RoundedRectangle` shape in SwiftUI with
|
||||
/// the `.continuous` corner style.
|
||||
///
|
||||
/// Technically, a canonical rounded superellipse, i.e. one with a uniform
|
||||
/// corner radius ([RSuperellipse.fromRectAndRadius]), is created by replacing
|
||||
/// the four corners of a superellipse (also known as a Lamé curve) with
|
||||
/// circular arcs. A rounded superellipse with non-uniform radii is extended on it
|
||||
/// by concatenating arc segments and transformation.
|
||||
///
|
||||
/// The corner radius parameters used in this class corresponds to SwiftUI's
|
||||
/// `cornerRadius` parameter, which is close to, but not exactly equals to, the
|
||||
/// radius of the corner circles.
|
||||
class RSuperellipse extends _RRectLike<RSuperellipse> {
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and the same radii along its horizontal axis and its vertical axis.
|
||||
///
|
||||
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
|
||||
const RSuperellipse.fromLTRBXY(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom,
|
||||
double radiusX,
|
||||
double radiusY,
|
||||
) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and the same radius in each corner.
|
||||
///
|
||||
/// Will assert in debug mode if the `radius` is negative in either x or y.
|
||||
RSuperellipse.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
|
||||
: this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and the same radii
|
||||
/// along its horizontal axis and its vertical axis.
|
||||
///
|
||||
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
|
||||
RSuperellipse.fromRectXY(Rect rect, double radiusX, double radiusY)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and a radius that is
|
||||
/// the same in each corner.
|
||||
///
|
||||
/// Will assert in debug mode if the `radius` is negative in either x or y.
|
||||
RSuperellipse.fromRectAndRadius(Rect rect, Radius radius)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
|
||||
/// and topLeft, topRight, bottomRight, and bottomLeft radii.
|
||||
///
|
||||
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
|
||||
/// assert in debug mode if any of the radii are negative in either x or y.
|
||||
RSuperellipse.fromLTRBAndCorners(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
);
|
||||
|
||||
/// Construct a rounded rectangle from its bounding box and topLeft,
|
||||
/// topRight, bottomRight, and bottomLeft radii.
|
||||
///
|
||||
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
|
||||
/// assert in debug mode if any of the radii are negative in either x or y.
|
||||
RSuperellipse.fromRectAndCorners(
|
||||
Rect rect, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
);
|
||||
|
||||
const RSuperellipse._raw({
|
||||
super.left = 0.0,
|
||||
super.top = 0.0,
|
||||
super.right = 0.0,
|
||||
super.bottom = 0.0,
|
||||
super.tlRadiusX = 0.0,
|
||||
super.tlRadiusY = 0.0,
|
||||
super.trRadiusX = 0.0,
|
||||
super.trRadiusY = 0.0,
|
||||
super.brRadiusX = 0.0,
|
||||
super.brRadiusY = 0.0,
|
||||
super.blRadiusX = 0.0,
|
||||
super.blRadiusY = 0.0,
|
||||
});
|
||||
|
||||
@override
|
||||
RSuperellipse _create({
|
||||
required double left,
|
||||
required double top,
|
||||
required double right,
|
||||
required double bottom,
|
||||
required double tlRadiusX,
|
||||
required double tlRadiusY,
|
||||
required double trRadiusX,
|
||||
required double trRadiusY,
|
||||
required double brRadiusX,
|
||||
required double brRadiusY,
|
||||
required double blRadiusX,
|
||||
required double blRadiusY,
|
||||
}) => RSuperellipse._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
|
||||
/// A rounded rectangle with all the values set to zero.
|
||||
static const RSuperellipse zero = RSuperellipse._raw();
|
||||
|
||||
/// Linearly interpolate between two rounded superellipses.
|
||||
///
|
||||
/// If either is null, this function substitutes [RSuperellipse.zero] instead.
|
||||
///
|
||||
/// The `t` argument represents position on the timeline, with 0.0 meaning
|
||||
/// that the interpolation has not started, returning `a` (or something
|
||||
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
|
||||
/// returning `b` (or something equivalent to `b`), and values in between
|
||||
/// meaning that the interpolation is at the relevant point on the timeline
|
||||
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
|
||||
/// 1.0, so negative values and values greater than 1.0 are valid (and can
|
||||
/// easily be generated by curves such as [Curves.elasticInOut]).
|
||||
///
|
||||
/// Values for `t` are usually obtained from an [Animation<double>], such as
|
||||
/// an [AnimationController].
|
||||
static RSuperellipse? lerp(RSuperellipse? a, RSuperellipse? b, double t) {
|
||||
if (a == null) {
|
||||
if (b == null) {
|
||||
return null;
|
||||
}
|
||||
return b._lerpTo(null, 1 - t);
|
||||
}
|
||||
return a._lerpTo(b, t);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _toString(className: 'RSuperellipse');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,11 @@ bool _rrectIsValid(RRect rrect) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _rseIsValid(RSuperellipse rse) {
|
||||
assert(!rse.hasNaN, 'RSuperellipse argument contained a NaN value.');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _offsetIsValid(Offset offset) {
|
||||
assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.');
|
||||
return true;
|
||||
@ -5873,6 +5878,18 @@ abstract class Canvas {
|
||||
/// discussion of how to address that and some examples of using [clipRRect].
|
||||
void clipRRect(RRect rrect, {bool doAntiAlias = true});
|
||||
|
||||
/// Reduces the clip region to the intersection of the current clip and the
|
||||
/// given rounded superellipse.
|
||||
///
|
||||
/// 
|
||||
///
|
||||
/// If [doAntiAlias] is true, then the clip will be anti-aliased.
|
||||
///
|
||||
/// If multiple draw commands intersect with the clip boundary, this can result
|
||||
/// in incorrect blending at the clip boundary. See [saveLayer] for a
|
||||
/// discussion of how to address that and some examples of using [clipRSuperellipse].
|
||||
void clipRSuperellipse(RSuperellipse rse, {bool doAntiAlias = true});
|
||||
|
||||
/// Reduces the clip region to the intersection of the current clip and the
|
||||
/// given [Path].
|
||||
///
|
||||
@ -5996,6 +6013,13 @@ abstract class Canvas {
|
||||
/// This shape is almost but not quite entirely unlike an annulus.
|
||||
void drawDRRect(RRect outer, RRect inner, Paint paint);
|
||||
|
||||
/// Draws a rounded superellipse with the given [Paint]. The shape is filled,
|
||||
/// and the value of the [Paint.style] is ignored for this call.
|
||||
///
|
||||
/// 
|
||||
/// 
|
||||
void drawRSuperellipse(RSuperellipse rse, Paint paint);
|
||||
|
||||
/// Draws an axis-aligned oval that fills the given axis-aligned rectangle
|
||||
/// with the given [Paint]. Whether the oval is filled or stroked (or both) is
|
||||
/// controlled by [Paint.style].
|
||||
@ -6607,6 +6631,15 @@ base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas {
|
||||
@Native<Void Function(Pointer<Void>, Handle, Bool)>(symbol: 'Canvas::clipRRect')
|
||||
external void _clipRRect(Float32List rrect, bool doAntiAlias);
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(RSuperellipse rse, {bool doAntiAlias = true}) {
|
||||
assert(_rseIsValid(rse));
|
||||
_clipRSuperellipse(rse._getValue32(), doAntiAlias);
|
||||
}
|
||||
|
||||
@Native<Void Function(Pointer<Void>, Handle, Bool)>(symbol: 'Canvas::clipRSuperellipse')
|
||||
external void _clipRSuperellipse(Float32List rse, bool doAntiAlias);
|
||||
|
||||
@override
|
||||
void clipPath(Path path, {bool doAntiAlias = true}) {
|
||||
_clipPath(path as _NativePath, doAntiAlias);
|
||||
@ -6717,6 +6750,19 @@ base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas {
|
||||
ByteData paintData,
|
||||
);
|
||||
|
||||
@override
|
||||
void drawRSuperellipse(RSuperellipse rse, Paint paint) {
|
||||
assert(_rseIsValid(rse));
|
||||
_drawRSuperellipse(rse._getValue32(), paint._objects, paint._data);
|
||||
}
|
||||
|
||||
@Native<Void Function(Pointer<Void>, Handle, Handle, Handle)>(symbol: 'Canvas::drawRSuperellipse')
|
||||
external void _drawRSuperellipse(
|
||||
Float32List rse,
|
||||
List<Object?>? paintObjects,
|
||||
ByteData paintData,
|
||||
);
|
||||
|
||||
@override
|
||||
void drawOval(Rect rect, Paint paint) {
|
||||
assert(_rectIsValid(rect));
|
||||
|
@ -177,6 +177,13 @@ void Canvas::clipRRect(const RRect& rrect, bool doAntiAlias) {
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::clipRSuperellipse(const RSuperellipse& rse, bool doAntiAlias) {
|
||||
if (display_list_builder_) {
|
||||
builder()->ClipRoundSuperellipse(rse.rsuperellipse, DlClipOp::kIntersect,
|
||||
doAntiAlias);
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::clipPath(const CanvasPath* path, bool doAntiAlias) {
|
||||
if (!path) {
|
||||
Dart_ThrowException(
|
||||
@ -295,6 +302,19 @@ void Canvas::drawDRRect(const RRect& outer,
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::drawRSuperellipse(const RSuperellipse& rse,
|
||||
Dart_Handle paint_objects,
|
||||
Dart_Handle paint_data) {
|
||||
Paint paint(paint_objects, paint_data);
|
||||
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawDRRectFlags, DlTileMode::kDecal);
|
||||
builder()->DrawRoundSuperellipse(rse.rsuperellipse, dl_paint);
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::drawOval(double left,
|
||||
double top,
|
||||
double right,
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "flutter/lib/ui/painting/picture.h"
|
||||
#include "flutter/lib/ui/painting/picture_recorder.h"
|
||||
#include "flutter/lib/ui/painting/rrect.h"
|
||||
#include "flutter/lib/ui/painting/rsuperellipse.h"
|
||||
#include "flutter/lib/ui/painting/vertices.h"
|
||||
#include "third_party/tonic/typed_data/typed_list.h"
|
||||
|
||||
@ -61,6 +62,7 @@ class Canvas : public RefCountedDartWrappable<Canvas>, DisplayListOpFlags {
|
||||
DlClipOp clipOp,
|
||||
bool doAntiAlias = true);
|
||||
void clipRRect(const RRect& rrect, bool doAntiAlias = true);
|
||||
void clipRSuperellipse(const RSuperellipse& rse, bool doAntiAlias = true);
|
||||
void clipPath(const CanvasPath* path, bool doAntiAlias = true);
|
||||
void getDestinationClipBounds(Dart_Handle rect_handle);
|
||||
void getLocalClipBounds(Dart_Handle rect_handle);
|
||||
@ -92,6 +94,10 @@ class Canvas : public RefCountedDartWrappable<Canvas>, DisplayListOpFlags {
|
||||
Dart_Handle paint_objects,
|
||||
Dart_Handle paint_data);
|
||||
|
||||
void drawRSuperellipse(const RSuperellipse& rse,
|
||||
Dart_Handle paint_objects,
|
||||
Dart_Handle paint_data);
|
||||
|
||||
void drawOval(double left,
|
||||
double top,
|
||||
double right,
|
||||
|
59
engine/src/flutter/lib/ui/painting/rsuperellipse.cc
Normal file
59
engine/src/flutter/lib/ui/painting/rsuperellipse.cc
Normal file
@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
#include "flutter/lib/ui/painting/rsuperellipse.h"
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "third_party/tonic/logging/dart_error.h"
|
||||
#include "third_party/tonic/typed_data/typed_list.h"
|
||||
|
||||
using flutter::RSuperellipse;
|
||||
|
||||
namespace tonic {
|
||||
|
||||
// Construct an DlRoundSuperellipse from a Dart RSuperellipse object.
|
||||
// The Dart RSuperellipse is a Float32List containing
|
||||
// [left, top, right, bottom, xRadius, yRadius]
|
||||
RSuperellipse DartConverter<flutter::RSuperellipse>::FromDart(
|
||||
Dart_Handle value) {
|
||||
Float32List buffer(value);
|
||||
|
||||
RSuperellipse result;
|
||||
result.is_null = true;
|
||||
if (buffer.data() == nullptr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// The Flutter rect may be inverted (upside down, backward, or both)
|
||||
// Historically, Skia would normalize such rects but we will do that
|
||||
// manually below when we construct the Impeller RoundRect
|
||||
flutter::DlRect raw_rect =
|
||||
flutter::DlRect::MakeLTRB(buffer[0], buffer[1], buffer[2], buffer[3]);
|
||||
|
||||
// Flutter has radii in TL,TR,BR,BL (clockwise) order,
|
||||
// but Impeller uses TL,TR,BL,BR (zig-zag) order
|
||||
impeller::RoundingRadii radii = {
|
||||
.top_left = flutter::DlSize(buffer[4], buffer[5]),
|
||||
.top_right = flutter::DlSize(buffer[6], buffer[7]),
|
||||
.bottom_left = flutter::DlSize(buffer[10], buffer[11]),
|
||||
.bottom_right = flutter::DlSize(buffer[8], buffer[9]),
|
||||
};
|
||||
|
||||
result.rsuperellipse = flutter::DlRoundSuperellipse::MakeRectRadii(
|
||||
raw_rect.GetPositive(), radii);
|
||||
|
||||
result.is_null = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
RSuperellipse DartConverter<flutter::RSuperellipse>::FromArguments(
|
||||
Dart_NativeArguments args,
|
||||
int index,
|
||||
Dart_Handle& exception) {
|
||||
Dart_Handle value = Dart_GetNativeArgument(args, index);
|
||||
FML_DCHECK(!CheckAndHandleError(value));
|
||||
return FromDart(value);
|
||||
}
|
||||
|
||||
} // namespace tonic
|
45
engine/src/flutter/lib/ui/painting/rsuperellipse.h
Normal file
45
engine/src/flutter/lib/ui/painting/rsuperellipse.h
Normal file
@ -0,0 +1,45 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_LIB_UI_PAINTING_RSUPERELLIPSE_H_
|
||||
#define FLUTTER_LIB_UI_PAINTING_RSUPERELLIPSE_H_
|
||||
|
||||
#include "flutter/display_list/geometry/dl_geometry_types.h"
|
||||
#include "third_party/dart/runtime/include/dart_api.h"
|
||||
#include "third_party/tonic/converter/dart_converter.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class RSuperellipse {
|
||||
public:
|
||||
DlRoundSuperellipse rsuperellipse;
|
||||
bool is_null;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
namespace tonic {
|
||||
|
||||
template <>
|
||||
struct DartConverter<flutter::RSuperellipse> {
|
||||
using NativeType = flutter::RSuperellipse;
|
||||
using FfiType = Dart_Handle;
|
||||
static constexpr const char* kFfiRepresentation = "Handle";
|
||||
static constexpr const char* kDartRepresentation = "Object";
|
||||
static constexpr bool kAllowedInLeafCall = false;
|
||||
|
||||
static NativeType FromDart(Dart_Handle handle);
|
||||
static NativeType FromArguments(Dart_NativeArguments args,
|
||||
int index,
|
||||
Dart_Handle& exception);
|
||||
|
||||
static NativeType FromFfi(FfiType val) { return FromDart(val); }
|
||||
static const char* GetFfiRepresentation() { return kFfiRepresentation; }
|
||||
static const char* GetDartRepresentation() { return kDartRepresentation; }
|
||||
static bool AllowedInLeafCall() { return kAllowedInLeafCall; }
|
||||
};
|
||||
|
||||
} // namespace tonic
|
||||
|
||||
#endif // FLUTTER_LIB_UI_PAINTING_RSUPERELLIPSE_H_
|
@ -68,6 +68,7 @@ abstract class Canvas {
|
||||
Float64List getTransform();
|
||||
void clipRect(Rect rect, {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true});
|
||||
void clipRRect(RRect rrect, {bool doAntiAlias = true});
|
||||
void clipRSuperellipse(RSuperellipse rse, {bool doAntiAlias = true});
|
||||
void clipPath(Path path, {bool doAntiAlias = true});
|
||||
Rect getLocalClipBounds();
|
||||
Rect getDestinationClipBounds();
|
||||
@ -76,6 +77,7 @@ abstract class Canvas {
|
||||
void drawPaint(Paint paint);
|
||||
void drawRect(Rect rect, Paint paint);
|
||||
void drawRRect(RRect rrect, Paint paint);
|
||||
void drawRSuperellipse(RSuperellipse rse, Paint paint);
|
||||
void drawDRRect(RRect outer, RRect inner, Paint paint);
|
||||
void drawOval(Rect rect, Paint paint);
|
||||
void drawCircle(Offset c, double radius, Paint paint);
|
||||
|
@ -18,6 +18,8 @@ abstract class ClipRectEngineLayer implements EngineLayer {}
|
||||
|
||||
abstract class ClipRRectEngineLayer implements EngineLayer {}
|
||||
|
||||
abstract class ClipRSuperellipseEngineLayer implements EngineLayer {}
|
||||
|
||||
abstract class ClipPathEngineLayer implements EngineLayer {}
|
||||
|
||||
abstract class OpacityEngineLayer implements EngineLayer {}
|
||||
@ -45,6 +47,11 @@ abstract class SceneBuilder {
|
||||
required Clip clipBehavior,
|
||||
ClipRRectEngineLayer? oldLayer,
|
||||
});
|
||||
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
|
||||
RSuperellipse rse, {
|
||||
required Clip clipBehavior,
|
||||
ClipRSuperellipseEngineLayer? oldLayer,
|
||||
});
|
||||
ClipPathEngineLayer pushClipPath(
|
||||
Path path, {
|
||||
Clip clipBehavior = Clip.antiAlias,
|
||||
|
@ -405,7 +405,337 @@ class Radius {
|
||||
}
|
||||
}
|
||||
|
||||
class RRect {
|
||||
abstract class _RRectLike<T extends _RRectLike<T>> {
|
||||
const _RRectLike({
|
||||
required this.left,
|
||||
required this.top,
|
||||
required this.right,
|
||||
required this.bottom,
|
||||
required this.tlRadiusX,
|
||||
required this.tlRadiusY,
|
||||
required this.trRadiusX,
|
||||
required this.trRadiusY,
|
||||
required this.brRadiusX,
|
||||
required this.brRadiusY,
|
||||
required this.blRadiusX,
|
||||
required this.blRadiusY,
|
||||
required bool uniformRadii,
|
||||
}) : assert(tlRadiusX >= 0),
|
||||
assert(tlRadiusY >= 0),
|
||||
assert(trRadiusX >= 0),
|
||||
assert(trRadiusY >= 0),
|
||||
assert(brRadiusX >= 0),
|
||||
assert(brRadiusY >= 0),
|
||||
assert(blRadiusX >= 0),
|
||||
assert(blRadiusY >= 0),
|
||||
webOnlyUniformRadii = uniformRadii;
|
||||
|
||||
T _create({
|
||||
required double left,
|
||||
required double top,
|
||||
required double right,
|
||||
required double bottom,
|
||||
required double tlRadiusX,
|
||||
required double tlRadiusY,
|
||||
required double trRadiusX,
|
||||
required double trRadiusY,
|
||||
required double brRadiusX,
|
||||
required double brRadiusY,
|
||||
required double blRadiusX,
|
||||
required double blRadiusY,
|
||||
});
|
||||
|
||||
final double left;
|
||||
final double top;
|
||||
final double right;
|
||||
final double bottom;
|
||||
final double tlRadiusX;
|
||||
final double tlRadiusY;
|
||||
Radius get tlRadius => Radius.elliptical(tlRadiusX, tlRadiusY);
|
||||
final double trRadiusX;
|
||||
final double trRadiusY;
|
||||
Radius get trRadius => Radius.elliptical(trRadiusX, trRadiusY);
|
||||
final double brRadiusX;
|
||||
final double brRadiusY;
|
||||
Radius get brRadius => Radius.elliptical(brRadiusX, brRadiusY);
|
||||
final double blRadiusX;
|
||||
final double blRadiusY;
|
||||
// webOnly
|
||||
final bool webOnlyUniformRadii;
|
||||
Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
|
||||
|
||||
T shift(Offset offset) {
|
||||
return _create(
|
||||
left: left + offset.dx,
|
||||
top: top + offset.dy,
|
||||
right: right + offset.dx,
|
||||
bottom: bottom + offset.dy,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
}
|
||||
|
||||
T inflate(double delta) {
|
||||
return _create(
|
||||
left: left - delta,
|
||||
top: top - delta,
|
||||
right: right + delta,
|
||||
bottom: bottom + delta,
|
||||
tlRadiusX: math.max(0, tlRadiusX + delta),
|
||||
tlRadiusY: math.max(0, tlRadiusY + delta),
|
||||
trRadiusX: math.max(0, trRadiusX + delta),
|
||||
trRadiusY: math.max(0, trRadiusY + delta),
|
||||
blRadiusX: math.max(0, blRadiusX + delta),
|
||||
blRadiusY: math.max(0, blRadiusY + delta),
|
||||
brRadiusX: math.max(0, brRadiusX + delta),
|
||||
brRadiusY: math.max(0, brRadiusY + delta),
|
||||
);
|
||||
}
|
||||
|
||||
T deflate(double delta) => inflate(-delta);
|
||||
double get width => right - left;
|
||||
double get height => bottom - top;
|
||||
Rect get outerRect => Rect.fromLTRB(left, top, right, bottom);
|
||||
Rect get safeInnerRect {
|
||||
const double kInsetFactor = 0.29289321881; // 1-cos(pi/4)
|
||||
|
||||
final double leftRadius = math.max(blRadiusX, tlRadiusX);
|
||||
final double topRadius = math.max(tlRadiusY, trRadiusY);
|
||||
final double rightRadius = math.max(trRadiusX, brRadiusX);
|
||||
final double bottomRadius = math.max(brRadiusY, blRadiusY);
|
||||
|
||||
return Rect.fromLTRB(
|
||||
left + leftRadius * kInsetFactor,
|
||||
top + topRadius * kInsetFactor,
|
||||
right - rightRadius * kInsetFactor,
|
||||
bottom - bottomRadius * kInsetFactor,
|
||||
);
|
||||
}
|
||||
|
||||
Rect get middleRect {
|
||||
final double leftRadius = math.max(blRadiusX, tlRadiusX);
|
||||
final double topRadius = math.max(tlRadiusY, trRadiusY);
|
||||
final double rightRadius = math.max(trRadiusX, brRadiusX);
|
||||
final double bottomRadius = math.max(brRadiusY, blRadiusY);
|
||||
return Rect.fromLTRB(
|
||||
left + leftRadius,
|
||||
top + topRadius,
|
||||
right - rightRadius,
|
||||
bottom - bottomRadius,
|
||||
);
|
||||
}
|
||||
|
||||
Rect get wideMiddleRect {
|
||||
final double topRadius = math.max(tlRadiusY, trRadiusY);
|
||||
final double bottomRadius = math.max(brRadiusY, blRadiusY);
|
||||
return Rect.fromLTRB(left, top + topRadius, right, bottom - bottomRadius);
|
||||
}
|
||||
|
||||
Rect get tallMiddleRect {
|
||||
final double leftRadius = math.max(blRadiusX, tlRadiusX);
|
||||
final double rightRadius = math.max(trRadiusX, brRadiusX);
|
||||
return Rect.fromLTRB(left + leftRadius, top, right - rightRadius, bottom);
|
||||
}
|
||||
|
||||
bool get isEmpty => left >= right || top >= bottom;
|
||||
bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
|
||||
bool get isRect {
|
||||
return (tlRadiusX == 0.0 || tlRadiusY == 0.0) &&
|
||||
(trRadiusX == 0.0 || trRadiusY == 0.0) &&
|
||||
(blRadiusX == 0.0 || blRadiusY == 0.0) &&
|
||||
(brRadiusX == 0.0 || brRadiusY == 0.0);
|
||||
}
|
||||
|
||||
bool get isStadium {
|
||||
return tlRadius == trRadius &&
|
||||
trRadius == brRadius &&
|
||||
brRadius == blRadius &&
|
||||
(width <= 2.0 * tlRadiusX || height <= 2.0 * tlRadiusY);
|
||||
}
|
||||
|
||||
bool get isEllipse {
|
||||
return tlRadius == trRadius &&
|
||||
trRadius == brRadius &&
|
||||
brRadius == blRadius &&
|
||||
width <= 2.0 * tlRadiusX &&
|
||||
height <= 2.0 * tlRadiusY;
|
||||
}
|
||||
|
||||
bool get isCircle => width == height && isEllipse;
|
||||
double get shortestSide => math.min(width.abs(), height.abs());
|
||||
double get longestSide => math.max(width.abs(), height.abs());
|
||||
bool get hasNaN =>
|
||||
left.isNaN ||
|
||||
top.isNaN ||
|
||||
right.isNaN ||
|
||||
bottom.isNaN ||
|
||||
trRadiusX.isNaN ||
|
||||
trRadiusY.isNaN ||
|
||||
tlRadiusX.isNaN ||
|
||||
tlRadiusY.isNaN ||
|
||||
brRadiusX.isNaN ||
|
||||
brRadiusY.isNaN ||
|
||||
blRadiusX.isNaN ||
|
||||
blRadiusY.isNaN;
|
||||
Offset get center => Offset(left + width / 2.0, top + height / 2.0);
|
||||
|
||||
// Returns the minimum between min and scale to which radius1 and radius2
|
||||
// should be scaled with in order not to exceed the limit.
|
||||
double _getMin(double min, double radius1, double radius2, double limit) {
|
||||
final double sum = radius1 + radius2;
|
||||
if (sum > limit && sum != 0.0) {
|
||||
return math.min(min, limit / sum);
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
T scaleRadii() {
|
||||
double scale = 1.0;
|
||||
final double absWidth = width.abs();
|
||||
final double absHeight = height.abs();
|
||||
scale = _getMin(scale, blRadiusY, tlRadiusY, absHeight);
|
||||
scale = _getMin(scale, tlRadiusX, trRadiusX, absWidth);
|
||||
scale = _getMin(scale, trRadiusY, brRadiusY, absHeight);
|
||||
scale = _getMin(scale, brRadiusX, blRadiusX, absWidth);
|
||||
|
||||
if (scale < 1.0) {
|
||||
return _create(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX * scale,
|
||||
tlRadiusY: tlRadiusY * scale,
|
||||
trRadiusX: trRadiusX * scale,
|
||||
trRadiusY: trRadiusY * scale,
|
||||
blRadiusX: blRadiusX * scale,
|
||||
blRadiusY: blRadiusY * scale,
|
||||
brRadiusX: brRadiusX * scale,
|
||||
brRadiusY: brRadiusY * scale,
|
||||
);
|
||||
}
|
||||
|
||||
return _create(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
}
|
||||
|
||||
// Linearly interpolate between this object and another of the same shape.
|
||||
T _lerpTo(T? b, double t) {
|
||||
assert(runtimeType == T);
|
||||
if (b == null) {
|
||||
final double k = 1.0 - t;
|
||||
return _create(
|
||||
left: left * k,
|
||||
top: top * k,
|
||||
right: right * k,
|
||||
bottom: bottom * k,
|
||||
tlRadiusX: math.max(0, tlRadiusX * k),
|
||||
tlRadiusY: math.max(0, tlRadiusY * k),
|
||||
trRadiusX: math.max(0, trRadiusX * k),
|
||||
trRadiusY: math.max(0, trRadiusY * k),
|
||||
brRadiusX: math.max(0, brRadiusX * k),
|
||||
brRadiusY: math.max(0, brRadiusY * k),
|
||||
blRadiusX: math.max(0, blRadiusX * k),
|
||||
blRadiusY: math.max(0, blRadiusY * k),
|
||||
);
|
||||
} else {
|
||||
return _create(
|
||||
left: _lerpDouble(left, b.left, t),
|
||||
top: _lerpDouble(top, b.top, t),
|
||||
right: _lerpDouble(right, b.right, t),
|
||||
bottom: _lerpDouble(bottom, b.bottom, t),
|
||||
tlRadiusX: math.max(0, _lerpDouble(tlRadiusX, b.tlRadiusX, t)),
|
||||
tlRadiusY: math.max(0, _lerpDouble(tlRadiusY, b.tlRadiusY, t)),
|
||||
trRadiusX: math.max(0, _lerpDouble(trRadiusX, b.trRadiusX, t)),
|
||||
trRadiusY: math.max(0, _lerpDouble(trRadiusY, b.trRadiusY, t)),
|
||||
brRadiusX: math.max(0, _lerpDouble(brRadiusX, b.brRadiusX, t)),
|
||||
brRadiusY: math.max(0, _lerpDouble(brRadiusY, b.brRadiusY, t)),
|
||||
blRadiusX: math.max(0, _lerpDouble(blRadiusX, b.blRadiusX, t)),
|
||||
blRadiusY: math.max(0, _lerpDouble(blRadiusY, b.blRadiusY, t)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is _RRectLike &&
|
||||
other.left == left &&
|
||||
other.top == top &&
|
||||
other.right == right &&
|
||||
other.bottom == bottom &&
|
||||
other.tlRadiusX == tlRadiusX &&
|
||||
other.tlRadiusY == tlRadiusY &&
|
||||
other.trRadiusX == trRadiusX &&
|
||||
other.trRadiusY == trRadiusY &&
|
||||
other.blRadiusX == blRadiusX &&
|
||||
other.blRadiusY == blRadiusY &&
|
||||
other.brRadiusX == brRadiusX &&
|
||||
other.brRadiusY == brRadiusY;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
tlRadiusX,
|
||||
tlRadiusY,
|
||||
trRadiusX,
|
||||
trRadiusY,
|
||||
blRadiusX,
|
||||
blRadiusY,
|
||||
brRadiusX,
|
||||
brRadiusY,
|
||||
);
|
||||
|
||||
String _toString({required String className}) {
|
||||
final String rect =
|
||||
'${left.toStringAsFixed(1)}, '
|
||||
'${top.toStringAsFixed(1)}, '
|
||||
'${right.toStringAsFixed(1)}, '
|
||||
'${bottom.toStringAsFixed(1)}';
|
||||
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) {
|
||||
if (tlRadius.x == tlRadius.y) {
|
||||
return '$className.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
|
||||
}
|
||||
return '$className.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
|
||||
}
|
||||
return '$className.fromLTRBAndCorners('
|
||||
'$rect, '
|
||||
'topLeft: $tlRadius, '
|
||||
'topRight: $trRadius, '
|
||||
'bottomRight: $brRadius, '
|
||||
'bottomLeft: $blRadius'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
class RRect extends _RRectLike<RRect> {
|
||||
const RRect.fromLTRBXY(
|
||||
double left,
|
||||
double top,
|
||||
@ -542,222 +872,52 @@ class RRect {
|
||||
);
|
||||
|
||||
const RRect._raw({
|
||||
this.left = 0.0,
|
||||
this.top = 0.0,
|
||||
this.right = 0.0,
|
||||
this.bottom = 0.0,
|
||||
this.tlRadiusX = 0.0,
|
||||
this.tlRadiusY = 0.0,
|
||||
this.trRadiusX = 0.0,
|
||||
this.trRadiusY = 0.0,
|
||||
this.brRadiusX = 0.0,
|
||||
this.brRadiusY = 0.0,
|
||||
this.blRadiusX = 0.0,
|
||||
this.blRadiusY = 0.0,
|
||||
bool uniformRadii = false,
|
||||
}) : assert(tlRadiusX >= 0),
|
||||
assert(tlRadiusY >= 0),
|
||||
assert(trRadiusX >= 0),
|
||||
assert(trRadiusY >= 0),
|
||||
assert(brRadiusX >= 0),
|
||||
assert(brRadiusY >= 0),
|
||||
assert(blRadiusX >= 0),
|
||||
assert(blRadiusY >= 0),
|
||||
webOnlyUniformRadii = uniformRadii;
|
||||
super.left = 0.0,
|
||||
super.top = 0.0,
|
||||
super.right = 0.0,
|
||||
super.bottom = 0.0,
|
||||
super.tlRadiusX = 0.0,
|
||||
super.tlRadiusY = 0.0,
|
||||
super.trRadiusX = 0.0,
|
||||
super.trRadiusY = 0.0,
|
||||
super.brRadiusX = 0.0,
|
||||
super.brRadiusY = 0.0,
|
||||
super.blRadiusX = 0.0,
|
||||
super.blRadiusY = 0.0,
|
||||
super.uniformRadii = false,
|
||||
});
|
||||
|
||||
@override
|
||||
RRect _create({
|
||||
required double left,
|
||||
required double top,
|
||||
required double right,
|
||||
required double bottom,
|
||||
required double tlRadiusX,
|
||||
required double tlRadiusY,
|
||||
required double trRadiusX,
|
||||
required double trRadiusY,
|
||||
required double brRadiusX,
|
||||
required double brRadiusY,
|
||||
required double blRadiusX,
|
||||
required double blRadiusY,
|
||||
}) => RRect._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
|
||||
final double left;
|
||||
final double top;
|
||||
final double right;
|
||||
final double bottom;
|
||||
final double tlRadiusX;
|
||||
final double tlRadiusY;
|
||||
Radius get tlRadius => Radius.elliptical(tlRadiusX, tlRadiusY);
|
||||
final double trRadiusX;
|
||||
final double trRadiusY;
|
||||
Radius get trRadius => Radius.elliptical(trRadiusX, trRadiusY);
|
||||
final double brRadiusX;
|
||||
final double brRadiusY;
|
||||
Radius get brRadius => Radius.elliptical(brRadiusX, brRadiusY);
|
||||
final double blRadiusX;
|
||||
final double blRadiusY;
|
||||
// webOnly
|
||||
final bool webOnlyUniformRadii;
|
||||
Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
|
||||
static const RRect zero = RRect._raw();
|
||||
|
||||
RRect shift(Offset offset) {
|
||||
return RRect._raw(
|
||||
left: left + offset.dx,
|
||||
top: top + offset.dy,
|
||||
right: right + offset.dx,
|
||||
bottom: bottom + offset.dy,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
}
|
||||
|
||||
RRect inflate(double delta) {
|
||||
return RRect._raw(
|
||||
left: left - delta,
|
||||
top: top - delta,
|
||||
right: right + delta,
|
||||
bottom: bottom + delta,
|
||||
tlRadiusX: math.max(0, tlRadiusX + delta),
|
||||
tlRadiusY: math.max(0, tlRadiusY + delta),
|
||||
trRadiusX: math.max(0, trRadiusX + delta),
|
||||
trRadiusY: math.max(0, trRadiusY + delta),
|
||||
blRadiusX: math.max(0, blRadiusX + delta),
|
||||
blRadiusY: math.max(0, blRadiusY + delta),
|
||||
brRadiusX: math.max(0, brRadiusX + delta),
|
||||
brRadiusY: math.max(0, brRadiusY + delta),
|
||||
);
|
||||
}
|
||||
|
||||
RRect deflate(double delta) => inflate(-delta);
|
||||
double get width => right - left;
|
||||
double get height => bottom - top;
|
||||
Rect get outerRect => Rect.fromLTRB(left, top, right, bottom);
|
||||
Rect get safeInnerRect {
|
||||
const double kInsetFactor = 0.29289321881; // 1-cos(pi/4)
|
||||
|
||||
final double leftRadius = math.max(blRadiusX, tlRadiusX);
|
||||
final double topRadius = math.max(tlRadiusY, trRadiusY);
|
||||
final double rightRadius = math.max(trRadiusX, brRadiusX);
|
||||
final double bottomRadius = math.max(brRadiusY, blRadiusY);
|
||||
|
||||
return Rect.fromLTRB(
|
||||
left + leftRadius * kInsetFactor,
|
||||
top + topRadius * kInsetFactor,
|
||||
right - rightRadius * kInsetFactor,
|
||||
bottom - bottomRadius * kInsetFactor,
|
||||
);
|
||||
}
|
||||
|
||||
Rect get middleRect {
|
||||
final double leftRadius = math.max(blRadiusX, tlRadiusX);
|
||||
final double topRadius = math.max(tlRadiusY, trRadiusY);
|
||||
final double rightRadius = math.max(trRadiusX, brRadiusX);
|
||||
final double bottomRadius = math.max(brRadiusY, blRadiusY);
|
||||
return Rect.fromLTRB(
|
||||
left + leftRadius,
|
||||
top + topRadius,
|
||||
right - rightRadius,
|
||||
bottom - bottomRadius,
|
||||
);
|
||||
}
|
||||
|
||||
Rect get wideMiddleRect {
|
||||
final double topRadius = math.max(tlRadiusY, trRadiusY);
|
||||
final double bottomRadius = math.max(brRadiusY, blRadiusY);
|
||||
return Rect.fromLTRB(left, top + topRadius, right, bottom - bottomRadius);
|
||||
}
|
||||
|
||||
Rect get tallMiddleRect {
|
||||
final double leftRadius = math.max(blRadiusX, tlRadiusX);
|
||||
final double rightRadius = math.max(trRadiusX, brRadiusX);
|
||||
return Rect.fromLTRB(left + leftRadius, top, right - rightRadius, bottom);
|
||||
}
|
||||
|
||||
bool get isEmpty => left >= right || top >= bottom;
|
||||
bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
|
||||
bool get isRect {
|
||||
return (tlRadiusX == 0.0 || tlRadiusY == 0.0) &&
|
||||
(trRadiusX == 0.0 || trRadiusY == 0.0) &&
|
||||
(blRadiusX == 0.0 || blRadiusY == 0.0) &&
|
||||
(brRadiusX == 0.0 || brRadiusY == 0.0);
|
||||
}
|
||||
|
||||
bool get isStadium {
|
||||
return tlRadius == trRadius &&
|
||||
trRadius == brRadius &&
|
||||
brRadius == blRadius &&
|
||||
(width <= 2.0 * tlRadiusX || height <= 2.0 * tlRadiusY);
|
||||
}
|
||||
|
||||
bool get isEllipse {
|
||||
return tlRadius == trRadius &&
|
||||
trRadius == brRadius &&
|
||||
brRadius == blRadius &&
|
||||
width <= 2.0 * tlRadiusX &&
|
||||
height <= 2.0 * tlRadiusY;
|
||||
}
|
||||
|
||||
bool get isCircle => width == height && isEllipse;
|
||||
double get shortestSide => math.min(width.abs(), height.abs());
|
||||
double get longestSide => math.max(width.abs(), height.abs());
|
||||
bool get hasNaN =>
|
||||
left.isNaN ||
|
||||
top.isNaN ||
|
||||
right.isNaN ||
|
||||
bottom.isNaN ||
|
||||
trRadiusX.isNaN ||
|
||||
trRadiusY.isNaN ||
|
||||
tlRadiusX.isNaN ||
|
||||
tlRadiusY.isNaN ||
|
||||
brRadiusX.isNaN ||
|
||||
brRadiusY.isNaN ||
|
||||
blRadiusX.isNaN ||
|
||||
blRadiusY.isNaN;
|
||||
Offset get center => Offset(left + width / 2.0, top + height / 2.0);
|
||||
|
||||
// Returns the minimum between min and scale to which radius1 and radius2
|
||||
// should be scaled with in order not to exceed the limit.
|
||||
double _getMin(double min, double radius1, double radius2, double limit) {
|
||||
final double sum = radius1 + radius2;
|
||||
if (sum > limit && sum != 0.0) {
|
||||
return math.min(min, limit / sum);
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
RRect scaleRadii() {
|
||||
double scale = 1.0;
|
||||
final double absWidth = width.abs();
|
||||
final double absHeight = height.abs();
|
||||
scale = _getMin(scale, blRadiusY, tlRadiusY, absHeight);
|
||||
scale = _getMin(scale, tlRadiusX, trRadiusX, absWidth);
|
||||
scale = _getMin(scale, trRadiusY, brRadiusY, absHeight);
|
||||
scale = _getMin(scale, brRadiusX, blRadiusX, absWidth);
|
||||
|
||||
if (scale < 1.0) {
|
||||
return RRect._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX * scale,
|
||||
tlRadiusY: tlRadiusY * scale,
|
||||
trRadiusX: trRadiusX * scale,
|
||||
trRadiusY: trRadiusY * scale,
|
||||
blRadiusX: blRadiusX * scale,
|
||||
blRadiusY: blRadiusY * scale,
|
||||
brRadiusX: brRadiusX * scale,
|
||||
brRadiusY: brRadiusY * scale,
|
||||
);
|
||||
}
|
||||
|
||||
return RRect._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
}
|
||||
|
||||
bool contains(Offset point) {
|
||||
if (point.dx < left || point.dx >= right || point.dy < top || point.dy >= bottom) {
|
||||
return false;
|
||||
@ -805,120 +965,242 @@ class RRect {
|
||||
}
|
||||
|
||||
static RRect? lerp(RRect? a, RRect? b, double t) {
|
||||
if (a == null) {
|
||||
if (b == null) {
|
||||
if (a == null) {
|
||||
return null;
|
||||
} else {
|
||||
final double k = 1.0 - t;
|
||||
return RRect._raw(
|
||||
left: a.left * k,
|
||||
top: a.top * k,
|
||||
right: a.right * k,
|
||||
bottom: a.bottom * k,
|
||||
tlRadiusX: math.max(0, a.tlRadiusX * k),
|
||||
tlRadiusY: math.max(0, a.tlRadiusY * k),
|
||||
trRadiusX: math.max(0, a.trRadiusX * k),
|
||||
trRadiusY: math.max(0, a.trRadiusY * k),
|
||||
brRadiusX: math.max(0, a.brRadiusX * k),
|
||||
brRadiusY: math.max(0, a.brRadiusY * k),
|
||||
blRadiusX: math.max(0, a.blRadiusX * k),
|
||||
blRadiusY: math.max(0, a.blRadiusY * k),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (a == null) {
|
||||
return RRect._raw(
|
||||
left: b.left * t,
|
||||
top: b.top * t,
|
||||
right: b.right * t,
|
||||
bottom: b.bottom * t,
|
||||
tlRadiusX: math.max(0, b.tlRadiusX * t),
|
||||
tlRadiusY: math.max(0, b.tlRadiusY * t),
|
||||
trRadiusX: math.max(0, b.trRadiusX * t),
|
||||
trRadiusY: math.max(0, b.trRadiusY * t),
|
||||
brRadiusX: math.max(0, b.brRadiusX * t),
|
||||
brRadiusY: math.max(0, b.brRadiusY * t),
|
||||
blRadiusX: math.max(0, b.blRadiusX * t),
|
||||
blRadiusY: math.max(0, b.blRadiusY * t),
|
||||
);
|
||||
} else {
|
||||
return RRect._raw(
|
||||
left: _lerpDouble(a.left, b.left, t),
|
||||
top: _lerpDouble(a.top, b.top, t),
|
||||
right: _lerpDouble(a.right, b.right, t),
|
||||
bottom: _lerpDouble(a.bottom, b.bottom, t),
|
||||
tlRadiusX: math.max(0, _lerpDouble(a.tlRadiusX, b.tlRadiusX, t)),
|
||||
tlRadiusY: math.max(0, _lerpDouble(a.tlRadiusY, b.tlRadiusY, t)),
|
||||
trRadiusX: math.max(0, _lerpDouble(a.trRadiusX, b.trRadiusX, t)),
|
||||
trRadiusY: math.max(0, _lerpDouble(a.trRadiusY, b.trRadiusY, t)),
|
||||
brRadiusX: math.max(0, _lerpDouble(a.brRadiusX, b.brRadiusX, t)),
|
||||
brRadiusY: math.max(0, _lerpDouble(a.brRadiusY, b.brRadiusY, t)),
|
||||
blRadiusX: math.max(0, _lerpDouble(a.blRadiusX, b.blRadiusX, t)),
|
||||
blRadiusY: math.max(0, _lerpDouble(a.blRadiusY, b.blRadiusY, t)),
|
||||
);
|
||||
return b._lerpTo(null, 1 - t);
|
||||
}
|
||||
return a._lerpTo(b, t);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (runtimeType != other.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is RRect &&
|
||||
other.left == left &&
|
||||
other.top == top &&
|
||||
other.right == right &&
|
||||
other.bottom == bottom &&
|
||||
other.tlRadiusX == tlRadiusX &&
|
||||
other.tlRadiusY == tlRadiusY &&
|
||||
other.trRadiusX == trRadiusX &&
|
||||
other.trRadiusY == trRadiusY &&
|
||||
other.blRadiusX == blRadiusX &&
|
||||
other.blRadiusY == blRadiusY &&
|
||||
other.brRadiusX == brRadiusX &&
|
||||
other.brRadiusY == brRadiusY;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
tlRadiusX,
|
||||
tlRadiusY,
|
||||
trRadiusX,
|
||||
trRadiusY,
|
||||
blRadiusX,
|
||||
blRadiusY,
|
||||
brRadiusX,
|
||||
brRadiusY,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final String rect =
|
||||
'${left.toStringAsFixed(1)}, '
|
||||
'${top.toStringAsFixed(1)}, '
|
||||
'${right.toStringAsFixed(1)}, '
|
||||
'${bottom.toStringAsFixed(1)}';
|
||||
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) {
|
||||
if (tlRadius.x == tlRadius.y) {
|
||||
return 'RRect.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
|
||||
return _toString(className: 'RRect');
|
||||
}
|
||||
return 'RRect.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
|
||||
}
|
||||
return 'RRect.fromLTRBAndCorners('
|
||||
'$rect, '
|
||||
'topLeft: $tlRadius, '
|
||||
'topRight: $trRadius, '
|
||||
'bottomRight: $brRadius, '
|
||||
'bottomLeft: $blRadius'
|
||||
')';
|
||||
|
||||
class RSuperellipse extends _RRectLike<RSuperellipse> {
|
||||
const RSuperellipse.fromLTRBXY(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom,
|
||||
double radiusX,
|
||||
double radiusY,
|
||||
) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
uniformRadii: radiusX == radiusY,
|
||||
);
|
||||
|
||||
RSuperellipse.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
|
||||
: this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
uniformRadii: radius.x == radius.y,
|
||||
);
|
||||
|
||||
RSuperellipse.fromRectXY(Rect rect, double radiusX, double radiusY)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radiusX,
|
||||
tlRadiusY: radiusY,
|
||||
trRadiusX: radiusX,
|
||||
trRadiusY: radiusY,
|
||||
blRadiusX: radiusX,
|
||||
blRadiusY: radiusY,
|
||||
brRadiusX: radiusX,
|
||||
brRadiusY: radiusY,
|
||||
uniformRadii: radiusX == radiusY,
|
||||
);
|
||||
|
||||
RSuperellipse.fromRectAndRadius(Rect rect, Radius radius)
|
||||
: this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: radius.x,
|
||||
tlRadiusY: radius.y,
|
||||
trRadiusX: radius.x,
|
||||
trRadiusY: radius.y,
|
||||
blRadiusX: radius.x,
|
||||
blRadiusY: radius.y,
|
||||
brRadiusX: radius.x,
|
||||
brRadiusY: radius.y,
|
||||
uniformRadii: radius.x == radius.y,
|
||||
);
|
||||
|
||||
RSuperellipse.fromLTRBAndCorners(
|
||||
double left,
|
||||
double top,
|
||||
double right,
|
||||
double bottom, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
uniformRadii:
|
||||
topLeft.x == topLeft.y &&
|
||||
topLeft.x == topRight.x &&
|
||||
topLeft.x == topRight.y &&
|
||||
topLeft.x == bottomLeft.x &&
|
||||
topLeft.x == bottomLeft.y &&
|
||||
topLeft.x == bottomRight.x &&
|
||||
topLeft.x == bottomRight.y,
|
||||
);
|
||||
|
||||
RSuperellipse.fromRectAndCorners(
|
||||
Rect rect, {
|
||||
Radius topLeft = Radius.zero,
|
||||
Radius topRight = Radius.zero,
|
||||
Radius bottomRight = Radius.zero,
|
||||
Radius bottomLeft = Radius.zero,
|
||||
}) : this._raw(
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
tlRadiusX: topLeft.x,
|
||||
tlRadiusY: topLeft.y,
|
||||
trRadiusX: topRight.x,
|
||||
trRadiusY: topRight.y,
|
||||
blRadiusX: bottomLeft.x,
|
||||
blRadiusY: bottomLeft.y,
|
||||
brRadiusX: bottomRight.x,
|
||||
brRadiusY: bottomRight.y,
|
||||
uniformRadii:
|
||||
topLeft.x == topLeft.y &&
|
||||
topLeft.x == topRight.x &&
|
||||
topLeft.x == topRight.y &&
|
||||
topLeft.x == bottomLeft.x &&
|
||||
topLeft.x == bottomLeft.y &&
|
||||
topLeft.x == bottomRight.x &&
|
||||
topLeft.x == bottomRight.y,
|
||||
);
|
||||
|
||||
const RSuperellipse._raw({
|
||||
super.left = 0.0,
|
||||
super.top = 0.0,
|
||||
super.right = 0.0,
|
||||
super.bottom = 0.0,
|
||||
super.tlRadiusX = 0.0,
|
||||
super.tlRadiusY = 0.0,
|
||||
super.trRadiusX = 0.0,
|
||||
super.trRadiusY = 0.0,
|
||||
super.brRadiusX = 0.0,
|
||||
super.brRadiusY = 0.0,
|
||||
super.blRadiusX = 0.0,
|
||||
super.blRadiusY = 0.0,
|
||||
super.uniformRadii = false,
|
||||
});
|
||||
|
||||
@override
|
||||
RSuperellipse _create({
|
||||
required double left,
|
||||
required double top,
|
||||
required double right,
|
||||
required double bottom,
|
||||
required double tlRadiusX,
|
||||
required double tlRadiusY,
|
||||
required double trRadiusX,
|
||||
required double trRadiusY,
|
||||
required double brRadiusX,
|
||||
required double brRadiusY,
|
||||
required double blRadiusX,
|
||||
required double blRadiusY,
|
||||
}) => RSuperellipse._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
|
||||
// Approximates a rounded superellipse with a round rectangle to the
|
||||
// best practical accuracy.
|
||||
//
|
||||
// This workaround is needed until the rounded superellipse is implemented on
|
||||
// Web. https://github.com/flutter/flutter/issues/163718
|
||||
RRect toApproximateRRect() {
|
||||
// Experiments have shown that using the same corner radii for the RRect
|
||||
// provides an approximation that is close to optimal, as achieving a perfect
|
||||
// match is not feasible.
|
||||
return RRect._raw(
|
||||
top: top,
|
||||
left: left,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
tlRadiusX: tlRadiusX,
|
||||
tlRadiusY: tlRadiusY,
|
||||
trRadiusX: trRadiusX,
|
||||
trRadiusY: trRadiusY,
|
||||
blRadiusX: blRadiusX,
|
||||
blRadiusY: blRadiusY,
|
||||
brRadiusX: brRadiusX,
|
||||
brRadiusY: brRadiusY,
|
||||
);
|
||||
}
|
||||
|
||||
static const RSuperellipse zero = RSuperellipse._raw();
|
||||
|
||||
static RSuperellipse? lerp(RSuperellipse? a, RSuperellipse? b, double t) {
|
||||
if (a == null) {
|
||||
if (b == null) {
|
||||
return null;
|
||||
}
|
||||
return b._lerpTo(null, 1 - t);
|
||||
}
|
||||
return a._lerpTo(b, t);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _toString(className: 'RSuperellipse');
|
||||
}
|
||||
}
|
||||
// Modeled after Skia's SkRSXform.
|
||||
|
@ -520,6 +520,17 @@ class CanvasPool extends _SaveStackTracking {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse) {
|
||||
// TODO(dkwingsmt): Properly implement clipRSuperellipse on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
final ui.RRect rrect = rse.toApproximateRRect();
|
||||
super.clipRSuperellipse(rse);
|
||||
if (_canvas != null) {
|
||||
_clipRRect(context, rrect);
|
||||
}
|
||||
}
|
||||
|
||||
void _clipRRect(DomCanvasRenderingContext2D ctx, ui.RRect rrect) {
|
||||
final ui.Path path = ui.Path()..addRRect(rrect);
|
||||
_runPath(ctx, path as SurfacePath);
|
||||
@ -1244,6 +1255,14 @@ class _SaveStackTracking {
|
||||
clipStack!.add(SaveClipEntry.rrect(rrect, _currentTransform.clone()));
|
||||
}
|
||||
|
||||
/// Adds a round rectangle to clipping stack.
|
||||
@mustCallSuper
|
||||
void clipRSuperellipse(ui.RSuperellipse rse) {
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
clipRRect(rse.toApproximateRRect());
|
||||
}
|
||||
|
||||
/// Adds a path to clipping stack.
|
||||
@mustCallSuper
|
||||
void clipPath(ui.Path path) {
|
||||
|
@ -52,6 +52,12 @@ class CkCanvas {
|
||||
skCanvas.clipRRect(toSkRRect(rrect), _clipOpIntersect, doAntiAlias);
|
||||
}
|
||||
|
||||
void clipRSuperellipse(ui.RSuperellipse rse, bool doAntiAlias) {
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
skCanvas.clipRRect(toSkRRect(rse.toApproximateRRect()), _clipOpIntersect, doAntiAlias);
|
||||
}
|
||||
|
||||
void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) {
|
||||
skCanvas.clipRect(toSkRect(rect), toSkClipOp(clipOp), doAntiAlias);
|
||||
}
|
||||
|
@ -130,6 +130,14 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
_canvas.clipRRect(rrect, doAntiAlias);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
|
||||
assert(rsuperellipseIsValid(rse));
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
_clipRRect(rse.toApproximateRRect(), doAntiAlias);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(ui.Path path, {bool doAntiAlias = true}) {
|
||||
_canvas.clipPath(path as CkPath, doAntiAlias);
|
||||
@ -199,6 +207,14 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
_canvas.drawRRect(rrect, paint as CkPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {
|
||||
assert(rsuperellipseIsValid(rse));
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
_drawRRect(rse.toApproximateRRect(), paint);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {
|
||||
assert(rrectIsValid(outer));
|
||||
|
@ -116,6 +116,22 @@ class ClipRRectEngineLayer extends ContainerLayer implements ui.ClipRRectEngineL
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer that clips its child layers by a given [RRect].
|
||||
class ClipRSuperellipseEngineLayer extends ContainerLayer
|
||||
implements ui.ClipRSuperellipseEngineLayer {
|
||||
ClipRSuperellipseEngineLayer(this.clipRSuperellipse, this.clipBehavior)
|
||||
: assert(clipBehavior != ui.Clip.none);
|
||||
|
||||
/// The rounded superellipse used to clip child layers.
|
||||
final ui.RSuperellipse clipRSuperellipse;
|
||||
final ui.Clip? clipBehavior;
|
||||
|
||||
@override
|
||||
void accept(LayerVisitor visitor) {
|
||||
visitor.visitClipRSuperellipse(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer that paints its children with the given opacity.
|
||||
class OpacityEngineLayer extends ContainerLayer implements ui.OpacityEngineLayer {
|
||||
OpacityEngineLayer(this.alpha, this.offset);
|
||||
|
@ -126,6 +126,15 @@ class LayerSceneBuilder implements ui.SceneBuilder {
|
||||
return pushLayer<ClipRRectEngineLayer>(ClipRRectEngineLayer(rrect, clipBehavior));
|
||||
}
|
||||
|
||||
@override
|
||||
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
|
||||
ui.RSuperellipse rse, {
|
||||
ui.Clip? clipBehavior,
|
||||
ui.EngineLayer? oldLayer,
|
||||
}) {
|
||||
return pushLayer<ClipRSuperellipseEngineLayer>(ClipRSuperellipseEngineLayer(rse, clipBehavior));
|
||||
}
|
||||
|
||||
@override
|
||||
ClipRectEngineLayer pushClipRect(
|
||||
ui.Rect rect, {
|
||||
|
@ -13,6 +13,7 @@ abstract class LayerVisitor {
|
||||
void visitClipPath(ClipPathEngineLayer clipPath);
|
||||
void visitClipRect(ClipRectEngineLayer clipRect);
|
||||
void visitClipRRect(ClipRRectEngineLayer clipRRect);
|
||||
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse);
|
||||
void visitOpacity(OpacityEngineLayer opacity);
|
||||
void visitTransform(TransformEngineLayer transform);
|
||||
void visitOffset(OffsetEngineLayer offset);
|
||||
@ -109,6 +110,20 @@ class PrerollVisitor extends LayerVisitor {
|
||||
mutatorsStack.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
mutatorsStack.pushClipRRect(clipRSuperellipse.clipRSuperellipse.toApproximateRRect());
|
||||
final ui.Rect childPaintBounds = prerollChildren(clipRSuperellipse);
|
||||
if (childPaintBounds.overlaps(clipRSuperellipse.clipRSuperellipse.outerRect)) {
|
||||
clipRSuperellipse.paintBounds = childPaintBounds.intersect(
|
||||
clipRSuperellipse.clipRSuperellipse.outerRect,
|
||||
);
|
||||
}
|
||||
mutatorsStack.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClipRect(ClipRectEngineLayer clipRect) {
|
||||
mutatorsStack.pushClipRect(clipRect.clipRect);
|
||||
@ -310,6 +325,25 @@ class MeasureVisitor extends LayerVisitor {
|
||||
measuringCanvas.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
|
||||
assert(clipRSuperellipse.needsPainting);
|
||||
|
||||
measuringCanvas.save();
|
||||
measuringCanvas.clipRSuperellipse(
|
||||
clipRSuperellipse.clipRSuperellipse,
|
||||
clipRSuperellipse.clipBehavior != ui.Clip.hardEdge,
|
||||
);
|
||||
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
|
||||
measuringCanvas.saveLayer(clipRSuperellipse.paintBounds, null);
|
||||
}
|
||||
measureChildren(clipRSuperellipse);
|
||||
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
|
||||
measuringCanvas.restore();
|
||||
}
|
||||
measuringCanvas.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitOpacity(OpacityEngineLayer opacity) {
|
||||
assert(opacity.needsPainting);
|
||||
@ -532,6 +566,27 @@ class PaintVisitor extends LayerVisitor {
|
||||
nWayCanvas.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
|
||||
assert(clipRSuperellipse.needsPainting);
|
||||
|
||||
nWayCanvas.save();
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
nWayCanvas.clipRRect(
|
||||
clipRSuperellipse.clipRSuperellipse.toApproximateRRect(),
|
||||
clipRSuperellipse.clipBehavior != ui.Clip.hardEdge,
|
||||
);
|
||||
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
|
||||
nWayCanvas.saveLayer(clipRSuperellipse.paintBounds, null);
|
||||
}
|
||||
paintChildren(clipRSuperellipse);
|
||||
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
|
||||
nWayCanvas.restore();
|
||||
}
|
||||
nWayCanvas.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitOpacity(OpacityEngineLayer opacity) {
|
||||
assert(opacity.needsPainting);
|
||||
|
@ -47,6 +47,8 @@ abstract class EngineCanvas {
|
||||
|
||||
void clipRRect(ui.RRect rrect);
|
||||
|
||||
void clipRSuperellipse(ui.RSuperellipse rse);
|
||||
|
||||
void clipPath(ui.Path path);
|
||||
|
||||
void drawColor(ui.Color color, ui.BlendMode blendMode);
|
||||
@ -234,6 +236,17 @@ mixin SaveStackTracking on EngineCanvas {
|
||||
_clipStack!.add(SaveClipEntry.rrect(rrect, _currentTransform.clone()));
|
||||
}
|
||||
|
||||
/// Adds a round superellipse to clipping stack.
|
||||
///
|
||||
/// Classes that override this method must call `super.clipRSuperellipse()`.
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse) {
|
||||
_clipStack ??= <SaveClipEntry>[];
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
_clipStack!.add(SaveClipEntry.rrect(rse.toApproximateRRect(), _currentTransform.clone()));
|
||||
}
|
||||
|
||||
/// Adds a path to clipping stack.
|
||||
///
|
||||
/// Classes that override this method must call `super.clipPath()`.
|
||||
|
@ -343,6 +343,13 @@ class BitmapCanvas extends EngineCanvas {
|
||||
_canvasPool.clipRRect(rrect);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse) {
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
_canvasPool.clipRRect(rse.toApproximateRRect());
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(ui.Path path) {
|
||||
_canvasPool.clipPath(path);
|
||||
|
@ -123,6 +123,14 @@ class SurfaceCanvas implements ui.Canvas {
|
||||
_canvas.clipRRect(rrect);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
|
||||
assert(rsuperellipseIsValid(rse));
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
_clipRRect(rse.toApproximateRRect(), doAntiAlias);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(ui.Path path, {bool doAntiAlias = true}) {
|
||||
_clipPath(path, doAntiAlias);
|
||||
@ -220,6 +228,14 @@ class SurfaceCanvas implements ui.Canvas {
|
||||
_canvas.drawDRRect(outer, inner, paint as SurfacePaint);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {
|
||||
assert(rsuperellipseIsValid(rse));
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
_drawRRect(rse.toApproximateRRect(), paint);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawOval(ui.Rect rect, ui.Paint paint) {
|
||||
assert(rectIsValid(rect));
|
||||
|
@ -186,6 +186,71 @@ class PersistedClipRRect extends PersistedContainerSurface
|
||||
bool get isClipping => true;
|
||||
}
|
||||
|
||||
/// A surface that creates a rounded superellipse clip.
|
||||
///
|
||||
/// Implemented by falling back to RRect.
|
||||
class PersistedClipRSuperellipse extends PersistedContainerSurface
|
||||
with _DomClip
|
||||
implements ui.ClipRSuperellipseEngineLayer {
|
||||
PersistedClipRSuperellipse(ui.EngineLayer? oldLayer, this.rse, this.clipBehavior)
|
||||
: super(oldLayer as PersistedSurface?);
|
||||
|
||||
final ui.RSuperellipse rse;
|
||||
// TODO(yjbanov): can this be controlled in the browser?
|
||||
final ui.Clip? clipBehavior;
|
||||
|
||||
@override
|
||||
void recomputeTransformAndClip() {
|
||||
transform = parent!.transform;
|
||||
if (clipBehavior != ui.Clip.none) {
|
||||
localClipBounds = rse.outerRect;
|
||||
} else {
|
||||
localClipBounds = null;
|
||||
}
|
||||
projectedClip = null;
|
||||
}
|
||||
|
||||
@override
|
||||
DomElement createElement() {
|
||||
// Fall back to rrect.
|
||||
return super.createElement()..setAttribute('clip-type', 'rrect');
|
||||
}
|
||||
|
||||
@override
|
||||
void apply() {
|
||||
final DomCSSStyleDeclaration style = rootElement!.style;
|
||||
style
|
||||
..left = '${rse.left}px'
|
||||
..top = '${rse.top}px'
|
||||
..width = '${rse.width}px'
|
||||
..height = '${rse.height}px'
|
||||
..borderTopLeftRadius = '${rse.tlRadiusX}px'
|
||||
..borderTopRightRadius = '${rse.trRadiusX}px'
|
||||
..borderBottomRightRadius = '${rse.brRadiusX}px'
|
||||
..borderBottomLeftRadius = '${rse.blRadiusX}px';
|
||||
applyOverflow(rootElement!, clipBehavior);
|
||||
|
||||
// Translate the child container in the opposite direction to compensate for
|
||||
// the shift in the coordinate system introduced by the translation of the
|
||||
// rootElement. Clipping in Flutter has no effect on the coordinate system.
|
||||
childContainer!.style
|
||||
..left = '${-rse.left}px'
|
||||
..top = '${-rse.top}px';
|
||||
}
|
||||
|
||||
@override
|
||||
void update(PersistedClipRSuperellipse oldSurface) {
|
||||
super.update(oldSurface);
|
||||
if (rse != oldSurface.rse || clipBehavior != oldSurface.clipBehavior) {
|
||||
localClipBounds = null;
|
||||
apply();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isClipping => true;
|
||||
}
|
||||
|
||||
/// A surface that clips it's children.
|
||||
class PersistedClipPath extends PersistedContainerSurface implements ui.ClipPathEngineLayer {
|
||||
PersistedClipPath(PersistedClipPath? super.oldLayer, this.clipPath, this.clipBehavior);
|
||||
|
@ -44,6 +44,11 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(ui.Path path) {
|
||||
throw UnimplementedError();
|
||||
|
@ -147,6 +147,22 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
return _pushSurface<PersistedClipRRect>(PersistedClipRRect(oldLayer, rrect, clipBehavior));
|
||||
}
|
||||
|
||||
/// Pushes a rounded-superellipse clip operation onto the operation stack.
|
||||
///
|
||||
/// Rasterization outside the given rounded rectangle is discarded.
|
||||
///
|
||||
/// See [pop] for details about the operation stack.
|
||||
@override
|
||||
ui.ClipRSuperellipseEngineLayer pushClipRSuperellipse(
|
||||
ui.RSuperellipse rse, {
|
||||
ui.Clip? clipBehavior,
|
||||
ui.ClipRSuperellipseEngineLayer? oldLayer,
|
||||
}) {
|
||||
return _pushSurface<PersistedClipRSuperellipse>(
|
||||
PersistedClipRSuperellipse(oldLayer, rse, clipBehavior),
|
||||
);
|
||||
}
|
||||
|
||||
/// Pushes a path clip operation onto the operation stack.
|
||||
///
|
||||
/// Rasterization outside the given path is discarded.
|
||||
|
@ -226,6 +226,56 @@ class ClipRRectOperation implements LayerOperation {
|
||||
String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)';
|
||||
}
|
||||
|
||||
class ClipRSuperellipseLayer with PictureEngineLayer implements ui.ClipRSuperellipseEngineLayer {
|
||||
ClipRSuperellipseLayer(this.operation);
|
||||
|
||||
@override
|
||||
final ClipRSuperellipseOperation operation;
|
||||
|
||||
@override
|
||||
ClipRSuperellipseLayer emptyClone() => ClipRSuperellipseLayer(operation);
|
||||
}
|
||||
|
||||
class ClipRSuperellipseOperation implements LayerOperation {
|
||||
const ClipRSuperellipseOperation(this.rse, this.clip);
|
||||
|
||||
final ui.RSuperellipse rse;
|
||||
final ui.Clip clip;
|
||||
|
||||
@override
|
||||
ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rse.outerRect);
|
||||
|
||||
@override
|
||||
void pre(SceneCanvas canvas) {
|
||||
canvas.save();
|
||||
canvas.clipRSuperellipse(rse, doAntiAlias: clip != ui.Clip.hardEdge);
|
||||
if (clip == ui.Clip.antiAliasWithSaveLayer) {
|
||||
canvas.saveLayer(rse.outerRect, ui.Paint());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void post(SceneCanvas canvas) {
|
||||
if (clip == ui.Clip.antiAliasWithSaveLayer) {
|
||||
canvas.restore();
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
PlatformViewStyling createPlatformViewStyling() {
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
return PlatformViewStyling(clip: PlatformViewRRectClip(rse.toApproximateRRect()));
|
||||
}
|
||||
|
||||
@override
|
||||
bool get affectsBackdrop => false;
|
||||
|
||||
@override
|
||||
String toString() => 'ClipRSuperellipseOperation(rse: $rse, clip: $clip)';
|
||||
}
|
||||
|
||||
class ColorFilterLayer with PictureEngineLayer implements ui.ColorFilterEngineLayer {
|
||||
ColorFilterLayer(this.operation);
|
||||
|
||||
|
@ -385,6 +385,17 @@ class EngineSceneBuilder implements ui.SceneBuilder {
|
||||
ui.ClipRRectEngineLayer? oldLayer,
|
||||
}) => pushLayer<ClipRRectLayer>(ClipRRectLayer(ClipRRectOperation(rrect, clipBehavior)));
|
||||
|
||||
@override
|
||||
ui.ClipRSuperellipseEngineLayer pushClipRSuperellipse(
|
||||
ui.RSuperellipse rse, {
|
||||
required ui.Clip clipBehavior,
|
||||
ui.ClipRSuperellipseEngineLayer? oldLayer,
|
||||
}) {
|
||||
return pushLayer<ClipRSuperellipseLayer>(
|
||||
ClipRSuperellipseLayer(ClipRSuperellipseOperation(rse, clipBehavior)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ui.ClipRectEngineLayer pushClipRect(
|
||||
ui.Rect rect, {
|
||||
|
@ -134,6 +134,13 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
clipRRect(rse.toApproximateRRect(), doAntiAlias: doAntiAlias);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(ui.Path path, {bool doAntiAlias = true}) {
|
||||
path as SkwasmPath;
|
||||
@ -176,6 +183,13 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
paintDispose(paintHandle);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {
|
||||
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
|
||||
// back to RRect. https://github.com/flutter/flutter/issues/163718
|
||||
drawRRect(rse.toApproximateRRect(), paint);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
|
@ -22,6 +22,14 @@ bool rrectIsValid(ui.RRect rrect) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rsuperellipseIsValid(ui.RSuperellipse rse) {
|
||||
assert(
|
||||
!(rse.left.isNaN || rse.right.isNaN || rse.top.isNaN || rse.bottom.isNaN),
|
||||
'RSuperellipse argument contained a NaN value.',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool offsetIsValid(ui.Offset offset) {
|
||||
assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.');
|
||||
return true;
|
||||
|
@ -92,6 +92,11 @@ class MockEngineCanvas implements EngineCanvas {
|
||||
_called('clipRRect', arguments: rrect);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(RSuperellipse rse) {
|
||||
_called('clipRSuperellipse', arguments: rse);
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(Path path) {
|
||||
_called('clipPath', arguments: path);
|
||||
|
@ -89,6 +89,9 @@ class StubSceneCanvas implements SceneCanvas {
|
||||
@override
|
||||
void clipRRect(ui.RRect rrect, {bool doAntiAlias = true}) {}
|
||||
|
||||
@override
|
||||
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {}
|
||||
|
||||
@override
|
||||
void clipRect(ui.Rect rect, {ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true}) {}
|
||||
|
||||
@ -151,6 +154,9 @@ class StubSceneCanvas implements SceneCanvas {
|
||||
@override
|
||||
void drawRRect(ui.RRect rrect, ui.Paint paint) {}
|
||||
|
||||
@override
|
||||
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {}
|
||||
|
||||
@override
|
||||
void drawRawAtlas(
|
||||
ui.Image atlas,
|
||||
|
@ -65,6 +65,9 @@ void DlOpSpy::drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) {
|
||||
did_draw_ |= will_draw_;
|
||||
}
|
||||
void DlOpSpy::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
|
||||
did_draw_ |= will_draw_;
|
||||
}
|
||||
void DlOpSpy::drawPath(const DlPath& path) {
|
||||
did_draw_ |= will_draw_;
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ class DlOpSpy final : public virtual DlOpReceiver,
|
||||
void drawRoundRect(const DlRoundRect& rrect) override;
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override;
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
|
||||
void drawPath(const DlPath& path) override;
|
||||
void drawArc(const DlRect& oval_bounds,
|
||||
DlScalar start_degrees,
|
||||
|
@ -207,8 +207,8 @@ extern std::ostream& operator<<(std::ostream& os, const DlPath& path) {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const flutter::DlClipOp& op) {
|
||||
switch (op) {
|
||||
case flutter::DlClipOp::kDifference: return os << "ClipOp::kDifference";
|
||||
case flutter::DlClipOp::kIntersect: return os << "ClipOp::kIntersect";
|
||||
case flutter::DlClipOp::kDifference: return os << "DlClipOp::kDifference";
|
||||
case flutter::DlClipOp::kIntersect: return os << "DlClipOp::kIntersect";
|
||||
}
|
||||
}
|
||||
|
||||
@ -784,6 +784,15 @@ void DisplayListStreamDispatcher::clipRoundRect(const DlRoundRect& rrect,
|
||||
<< "isaa: " << is_aa
|
||||
<< ");" << std::endl;
|
||||
}
|
||||
void DisplayListStreamDispatcher::clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
startl() << "clipRoundSuperellipse("
|
||||
<< rse << ", "
|
||||
<< clip_op << ", "
|
||||
<< "isaa: " << is_aa
|
||||
<< ");" << std::endl;
|
||||
}
|
||||
void DisplayListStreamDispatcher::clipPath(const DlPath& path, DlClipOp clip_op,
|
||||
bool is_aa) {
|
||||
startl() << "clipPath("
|
||||
@ -835,6 +844,9 @@ void DisplayListStreamDispatcher::drawDiffRoundRect(const DlRoundRect& outer,
|
||||
startl() << "drawDRRect(outer: " << outer << ", " << std::endl;
|
||||
startl() << " inner: " << inner << ");" << std::endl;
|
||||
}
|
||||
void DisplayListStreamDispatcher::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
|
||||
startl() << "drawRSuperellipse(" << rse << ");" << std::endl;
|
||||
}
|
||||
void DisplayListStreamDispatcher::drawPath(const DlPath& path) {
|
||||
startl() << "drawPath(" << path << ");" << std::endl;
|
||||
}
|
||||
|
@ -135,6 +135,9 @@ class DisplayListStreamDispatcher final : public DlOpReceiver {
|
||||
void clipRoundRect(const DlRoundRect& rrect,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override;
|
||||
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override;
|
||||
|
||||
void drawColor(DlColor color, DlBlendMode mode) override;
|
||||
@ -150,6 +153,7 @@ class DisplayListStreamDispatcher final : public DlOpReceiver {
|
||||
void drawRoundRect(const DlRoundRect& rrect) override;
|
||||
void drawDiffRoundRect(const DlRoundRect& outer,
|
||||
const DlRoundRect& inner) override;
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
|
||||
void drawPath(const DlPath& path) override;
|
||||
void drawArc(const DlRect& oval_bounds,
|
||||
DlScalar start_degrees,
|
||||
@ -382,6 +386,18 @@ class DisplayListGeneralReceiver : public DlOpReceiver {
|
||||
break;
|
||||
}
|
||||
}
|
||||
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
|
||||
DlClipOp clip_op,
|
||||
bool is_aa) override {
|
||||
switch (clip_op) {
|
||||
case DlClipOp::kIntersect:
|
||||
RecordByType(DisplayListOpType::kClipIntersectRoundSuperellipse);
|
||||
break;
|
||||
case DlClipOp::kDifference:
|
||||
RecordByType(DisplayListOpType::kClipDifferenceRoundSuperellipse);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {
|
||||
switch (clip_op) {
|
||||
case DlClipOp::kIntersect:
|
||||
@ -435,6 +451,9 @@ class DisplayListGeneralReceiver : public DlOpReceiver {
|
||||
const DlRoundRect& inner) override {
|
||||
RecordByType(DisplayListOpType::kDrawDiffRoundRect);
|
||||
}
|
||||
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override {
|
||||
RecordByType(DisplayListOpType::kDrawRoundSuperellipse);
|
||||
}
|
||||
void drawPath(const DlPath& path) override {
|
||||
RecordByType(DisplayListOpType::kDrawPath);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user