Set nested clip nodes (#37850)
diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc
index 2746db7..617f22b 100644
--- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc
+++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "flatland_external_view_embedder.h"
+#include <algorithm>
#include <cstdint>
#include "flutter/fml/trace_event.h"
@@ -17,6 +18,24 @@
// overflows on operations (like FLT_MAX would).
constexpr float kMaxHitRegionSize = 1'000'000.f;
+void AttachClipTransformChild(
+ FlatlandConnection* flatland,
+ FlatlandExternalViewEmbedder::ClipTransform* parent_clip_transform,
+ const fuchsia::ui::composition::TransformId& child_transform_id) {
+ flatland->flatland()->AddChild(parent_clip_transform->transform_id,
+ child_transform_id);
+ parent_clip_transform->children.push_back(child_transform_id);
+}
+
+void DetachClipTransformChildren(
+ FlatlandConnection* flatland,
+ FlatlandExternalViewEmbedder::ClipTransform* clip_transform) {
+ for (auto& child : clip_transform->children) {
+ flatland->flatland()->RemoveChild(clip_transform->transform_id, child);
+ }
+ clip_transform->children.clear();
+}
+
} // namespace
FlatlandExternalViewEmbedder::FlatlandExternalViewEmbedder(
@@ -263,7 +282,6 @@
viewport.transform_id,
{static_cast<int32_t>(view_mutators.transform.getTranslateX()),
static_cast<int32_t>(view_mutators.transform.getTranslateY())});
-
flatland_->flatland()->SetScale(
viewport.transform_id, {view_mutators.transform.getScaleX(),
view_mutators.transform.getScaleY()});
@@ -271,7 +289,54 @@
}
// TODO(fxbug.dev/94000): Set HitTestBehavior.
- // TODO(fxbug.dev/94000): Set ClipRegions.
+
+ // Set clip regions.
+ if (view_mutators.clips != viewport.mutators.clips) {
+ // Expand the clip_transforms array to fit any new transforms.
+ while (viewport.clip_transforms.size() < view_mutators.clips.size()) {
+ ClipTransform clip_transform;
+ clip_transform.transform_id = flatland_->NextTransformId();
+ flatland_->flatland()->CreateTransform(clip_transform.transform_id);
+ viewport.clip_transforms.emplace_back(std::move(clip_transform));
+ }
+ FML_CHECK(viewport.clip_transforms.size() >=
+ view_mutators.clips.size());
+
+ // Adjust and re-parent all clip transforms.
+ for (auto& clip_transform : viewport.clip_transforms) {
+ DetachClipTransformChildren(flatland_.get(), &clip_transform);
+ }
+
+ for (size_t c = 0; c < view_mutators.clips.size(); c++) {
+ const SkMatrix& clip_matrix = view_mutators.clips[c].transform;
+ const SkRect& clip_rect = view_mutators.clips[c].rect;
+
+ flatland_->flatland()->SetTranslation(
+ viewport.clip_transforms[c].transform_id,
+ {static_cast<int32_t>(clip_matrix.getTranslateX()),
+ static_cast<int32_t>(clip_matrix.getTranslateY())});
+ flatland_->flatland()->SetScale(
+ viewport.clip_transforms[c].transform_id,
+ {clip_matrix.getScaleX(), clip_matrix.getScaleY()});
+ fuchsia::math::Rect rect = {
+ static_cast<int32_t>(clip_rect.x()),
+ static_cast<int32_t>(clip_rect.y()),
+ static_cast<int32_t>(clip_rect.width()),
+ static_cast<int32_t>(clip_rect.height())};
+ flatland_->flatland()->SetClipBoundary(
+ viewport.clip_transforms[c].transform_id,
+ std::make_unique<fuchsia::math::Rect>(std::move(rect)));
+
+ const auto child_transform_id =
+ c != (view_mutators.clips.size() - 1)
+ ? viewport.clip_transforms[c + 1].transform_id
+ : viewport.transform_id;
+ AttachClipTransformChild(flatland_.get(),
+ &(viewport.clip_transforms[c]),
+ child_transform_id);
+ }
+ viewport.mutators.clips = view_mutators.clips;
+ }
// Set opacity.
if (view_mutators.opacity != viewport.mutators.opacity) {
@@ -299,9 +364,13 @@
}
// Attach the FlatlandView to the main scene graph.
+ const auto main_child_transform =
+ viewport.mutators.clips.empty()
+ ? viewport.transform_id
+ : viewport.clip_transforms[0].transform_id;
flatland_->flatland()->AddChild(root_transform_id_,
- viewport.transform_id);
- child_transforms_.emplace_back(viewport.transform_id);
+ main_child_transform);
+ child_transforms_.emplace_back(main_child_transform);
}
// Acquire the surface associated with the layer.
@@ -485,19 +554,29 @@
auto viewport_id = flatland_view->second.viewport_id;
auto transform_id = flatland_view->second.transform_id;
+ auto& clip_transforms = flatland_view->second.clip_transforms;
if (!flatland_view->second.pending_create_viewport_callback) {
flatland_->flatland()->ReleaseViewport(viewport_id, [](auto) {});
}
- auto itr =
- std::find_if(child_transforms_.begin(), child_transforms_.end(),
- [transform_id](fuchsia::ui::composition::TransformId id) {
- return id.value == transform_id.value;
- });
+ auto itr = std::find_if(
+ child_transforms_.begin(), child_transforms_.end(),
+ [transform_id,
+ &clip_transforms](fuchsia::ui::composition::TransformId id) {
+ return id.value == transform_id.value ||
+ (!clip_transforms.empty() &&
+ (id.value == clip_transforms[0].transform_id.value));
+ });
if (itr != child_transforms_.end()) {
- flatland_->flatland()->RemoveChild(root_transform_id_, transform_id);
+ flatland_->flatland()->RemoveChild(root_transform_id_, *itr);
child_transforms_.erase(itr);
}
+ for (auto& clip_transform : clip_transforms) {
+ DetachClipTransformChildren(flatland_.get(), &clip_transform);
+ }
flatland_->flatland()->ReleaseTransform(transform_id);
+ for (auto& clip_transform : clip_transforms) {
+ flatland_->flatland()->ReleaseTransform(clip_transform.transform_id);
+ }
flatland_views_.erase(flatland_view);
on_view_unbound(viewport_id);
diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h
index c6d6707..b12ef0f 100644
--- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h
+++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h
@@ -114,6 +114,12 @@
bool hit_testable,
bool focusable);
+ // Holds the clip transform that may be applied on a FlatlandView.
+ struct ClipTransform {
+ fuchsia::ui::composition::TransformId transform_id;
+ std::vector<fuchsia::ui::composition::TransformId> children;
+ };
+
private:
void Reset(); // Reset state for a new frame.
@@ -173,6 +179,7 @@
constexpr static EmbedderLayerId kRootLayerId = EmbedderLayerId{};
struct FlatlandView {
+ std::vector<ClipTransform> clip_transforms;
fuchsia::ui::composition::TransformId transform_id;
fuchsia::ui::composition::ContentId viewport_id;
ViewMutators mutators;
diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h
index 1b0fc40..40f1648 100644
--- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h
+++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h
@@ -124,6 +124,16 @@
return true;
}
+inline bool operator==(const std::optional<fuchsia::math::Rect>& a,
+ const std::optional<fuchsia::math::Rect>& b) {
+ if (a.has_value() != b.has_value()) {
+ return false;
+ }
+ if (!a.has_value()) {
+ }
+ return a.value() == b.value();
+}
+
namespace flutter_runner::testing {
constexpr static fuchsia::ui::composition::TransformId kInvalidTransformId{0};
diff --git a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc
index 213efaa..eb9b79d 100644
--- a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc
+++ b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc
@@ -284,6 +284,19 @@
/*hit_regions*/ _));
}
+Matcher<std::shared_ptr<FakeTransform>> IsClipTransformLayer(
+ const fuchsia::math::Vec& transform_translation,
+ const fuchsia::math::VecF& transform_scale,
+ std::optional<fuchsia::math::Rect> clip_bounds,
+ Matcher<std::shared_ptr<FakeTransform>> viewport_matcher) {
+ return Pointee(FieldsAre(
+ /* id */ _, transform_translation, transform_scale,
+ FakeTransform::kDefaultOrientation, /*clip_bounds*/ clip_bounds,
+ FakeTransform::kDefaultOpacity,
+ /*children*/ ElementsAre(viewport_matcher),
+ /*content*/ _,
+ /*hit_regions*/ _));
+}
fuchsia::ui::composition::OnNextFrameBeginValues WithPresentCredits(
uint32_t additional_present_credits) {
fuchsia::ui::composition::OnNextFrameBeginValues values;
@@ -725,6 +738,252 @@
fuchsia::ui::composition::HitTestInteraction::DEFAULT)})}));
}
+TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneClippedView) {
+ fuchsia::ui::composition::ParentViewportWatcherPtr parent_viewport_watcher;
+ fuchsia::ui::views::ViewportCreationToken viewport_creation_token;
+ fuchsia::ui::views::ViewCreationToken view_creation_token;
+ fuchsia::ui::views::ViewRef view_ref;
+ auto view_creation_token_status = zx::channel::create(
+ 0u, &viewport_creation_token.value, &view_creation_token.value);
+ ASSERT_EQ(view_creation_token_status, ZX_OK);
+ auto view_ref_pair = scenic::ViewRefPair::New();
+ view_ref_pair.view_ref.Clone(&view_ref);
+
+ // Create the `FlatlandExternalViewEmbedder` and pump the message loop until
+ // the initial scene graph is setup.
+ FlatlandExternalViewEmbedder external_view_embedder(
+ std::move(view_creation_token),
+ fuchsia::ui::views::ViewIdentityOnCreation{
+ .view_ref = std::move(view_ref_pair.view_ref),
+ .view_ref_control = std::move(view_ref_pair.control_ref),
+ },
+ fuchsia::ui::composition::ViewBoundProtocols{},
+ parent_viewport_watcher.NewRequest(), flatland_connection(),
+ fake_surface_producer());
+ flatland_connection()->Present();
+ loop().RunUntilIdle();
+ fake_flatland().FireOnNextFrameBeginEvent(WithPresentCredits(1u));
+ loop().RunUntilIdle();
+ EXPECT_THAT(fake_flatland().graph(),
+ IsFlutterGraph(parent_viewport_watcher, viewport_creation_token,
+ view_ref));
+
+ // Create the view before drawing the scene.
+ const SkSize child_view_size_signed = SkSize::Make(256.f, 512.f);
+ const fuchsia::math::SizeU child_view_size{
+ static_cast<uint32_t>(child_view_size_signed.width()),
+ static_cast<uint32_t>(child_view_size_signed.height())};
+ auto [child_view_token, child_viewport_token] = ViewTokenPair::New();
+ const uint32_t child_view_id = child_viewport_token.value.get();
+
+ const int kOpacity = 200;
+ const float kOpacityFloat = 200 / 255.0f;
+ const fuchsia::math::VecF kScale{3.0f, 4.0f};
+ const int kTranslateX = 10;
+ const int kTranslateY = 20;
+
+ auto matrix = SkMatrix::I();
+ matrix.setScaleX(kScale.x);
+ matrix.setScaleY(kScale.y);
+ matrix.setTranslateX(kTranslateX);
+ matrix.setTranslateY(kTranslateY);
+
+ SkRect kClipRect =
+ SkRect::MakeXYWH(30, 40, child_view_size_signed.width() - 50,
+ child_view_size_signed.height() - 60);
+ fuchsia::math::Rect kClipInMathRect = {
+ static_cast<int32_t>(kClipRect.x()), static_cast<int32_t>(kClipRect.y()),
+ static_cast<int32_t>(kClipRect.width()),
+ static_cast<int32_t>(kClipRect.height())};
+
+ auto mutators_stack = flutter::MutatorsStack();
+ mutators_stack.PushOpacity(kOpacity);
+ mutators_stack.PushTransform(matrix);
+ mutators_stack.PushClipRect(kClipRect);
+
+ flutter::EmbeddedViewParams child_view_params(matrix, child_view_size_signed,
+ mutators_stack);
+ external_view_embedder.CreateView(
+ child_view_id, []() {},
+ [](fuchsia::ui::composition::ContentId,
+ fuchsia::ui::composition::ChildViewWatcherHandle) {});
+ const SkRect child_view_occlusion_hint = SkRect::MakeLTRB(1, 2, 3, 4);
+ const fuchsia::math::Inset child_view_inset{
+ static_cast<int32_t>(child_view_occlusion_hint.top()),
+ static_cast<int32_t>(child_view_occlusion_hint.right()),
+ static_cast<int32_t>(child_view_occlusion_hint.bottom()),
+ static_cast<int32_t>(child_view_occlusion_hint.left())};
+ external_view_embedder.SetViewProperties(
+ child_view_id, child_view_occlusion_hint, /*hit_testable=*/false,
+ /*focusable=*/false);
+
+ // We must take into account the effect of DPR on the view scale.
+ const float kDPR = 2.0f;
+ const float kInvDPR = 1.f / kDPR;
+
+ // Draw the scene. The scene graph shouldn't change yet.
+ const SkISize frame_size_signed = SkISize::Make(512, 512);
+ const fuchsia::math::SizeU frame_size{
+ static_cast<uint32_t>(frame_size_signed.width()),
+ static_cast<uint32_t>(frame_size_signed.height())};
+ DrawFrameWithView(
+ external_view_embedder, frame_size_signed, kDPR, child_view_id,
+ child_view_params,
+ [](SkCanvas* canvas) {
+ const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(),
+ canvas->imageInfo().height());
+ SkPaint rect_paint;
+ rect_paint.setColor(SK_ColorGREEN);
+ canvas->translate(canvas_size.width() / 4.f,
+ canvas_size.height() / 2.f);
+ canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f,
+ canvas_size.height() / 32.f),
+ rect_paint);
+ },
+ [](SkCanvas* canvas) {
+ const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(),
+ canvas->imageInfo().height());
+ SkPaint rect_paint;
+ rect_paint.setColor(SK_ColorRED);
+ canvas->translate(canvas_size.width() * 3.f / 4.f,
+ canvas_size.height() / 2.f);
+ canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f,
+ canvas_size.height() / 32.f),
+ rect_paint);
+ });
+ EXPECT_THAT(fake_flatland().graph(),
+ IsFlutterGraph(parent_viewport_watcher, viewport_creation_token,
+ view_ref));
+
+ // Pump the message loop. The scene updates should propagate to flatland.
+ loop().RunUntilIdle();
+ fake_flatland().FireOnNextFrameBeginEvent(WithPresentCredits(1u));
+ loop().RunUntilIdle();
+
+ EXPECT_THAT(
+ fake_flatland().graph(),
+ IsFlutterGraph(
+ parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/
+ {IsImageLayer(
+ frame_size, kFirstLayerBlendMode,
+ {IsHitRegion(
+ /* x */ 128.f,
+ /* y */ 256.f,
+ /* width */ 16.f,
+ /* height */ 16.f,
+ /* hit_test */
+ fuchsia::ui::composition::HitTestInteraction::DEFAULT)}),
+ IsClipTransformLayer(
+ {kTranslateX, kTranslateY}, kScale, kClipInMathRect,
+ IsViewportLayer(child_view_token, child_view_size,
+ child_view_inset, {0, 0},
+ FakeTransform::kDefaultScale, kOpacityFloat)),
+ IsImageLayer(
+ frame_size, kUpperLayerBlendMode,
+ {IsHitRegion(
+ /* x */ 384.f,
+ /* y */ 256.f,
+ /* width */ 16.f,
+ /* height */ 16.f,
+ /* hit_test */
+ fuchsia::ui::composition::HitTestInteraction::DEFAULT)})},
+ {kInvDPR, kInvDPR}));
+
+ // Draw another frame with view, but get rid of the clips this time. This
+ // should remove all ClipTransformLayer instances.
+ auto new_matrix = SkMatrix::I();
+ new_matrix.setScaleX(kScale.x);
+ new_matrix.setScaleY(kScale.y);
+ auto new_mutators_stack = flutter::MutatorsStack();
+ new_mutators_stack.PushOpacity(kOpacity);
+ new_mutators_stack.PushTransform(new_matrix);
+ flutter::EmbeddedViewParams new_child_view_params(
+ new_matrix, child_view_size_signed, new_mutators_stack);
+ DrawFrameWithView(
+ external_view_embedder, frame_size_signed, kDPR, child_view_id,
+ new_child_view_params,
+ [](SkCanvas* canvas) {
+ const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(),
+ canvas->imageInfo().height());
+ SkPaint rect_paint;
+ rect_paint.setColor(SK_ColorGREEN);
+ canvas->translate(canvas_size.width() / 4.f,
+ canvas_size.height() / 2.f);
+ canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f,
+ canvas_size.height() / 32.f),
+ rect_paint);
+ },
+ [](SkCanvas* canvas) {
+ const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(),
+ canvas->imageInfo().height());
+ SkPaint rect_paint;
+ rect_paint.setColor(SK_ColorRED);
+ canvas->translate(canvas_size.width() * 3.f / 4.f,
+ canvas_size.height() / 2.f);
+ canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f,
+ canvas_size.height() / 32.f),
+ rect_paint);
+ });
+ loop().RunUntilIdle();
+ fake_flatland().FireOnNextFrameBeginEvent(WithPresentCredits(1u));
+ loop().RunUntilIdle();
+ EXPECT_THAT(
+ fake_flatland().graph(),
+ IsFlutterGraph(
+ parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/
+ {IsImageLayer(
+ frame_size, kFirstLayerBlendMode,
+ {IsHitRegion(
+ /* x */ 128.f,
+ /* y */ 256.f,
+ /* width */ 16.f,
+ /* height */ 16.f,
+ /* hit_test */
+ fuchsia::ui::composition::HitTestInteraction::DEFAULT)}),
+ IsViewportLayer(child_view_token, child_view_size, child_view_inset,
+ {0, 0}, kScale, kOpacityFloat),
+ IsImageLayer(
+ frame_size, kUpperLayerBlendMode,
+ {IsHitRegion(
+ /* x */ 384.f,
+ /* y */ 256.f,
+ /* width */ 16.f,
+ /* height */ 16.f,
+ /* hit_test */
+ fuchsia::ui::composition::HitTestInteraction::DEFAULT)})},
+ {kInvDPR, kInvDPR}));
+
+ // Destroy the view and draw another frame without the view.
+ external_view_embedder.DestroyView(
+ child_view_id, [](fuchsia::ui::composition::ContentId) {});
+ DrawSimpleFrame(
+ external_view_embedder, frame_size_signed, 1.f, [](SkCanvas* canvas) {
+ const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(),
+ canvas->imageInfo().height());
+ SkPaint rect_paint;
+ rect_paint.setColor(SK_ColorGREEN);
+ canvas->translate(canvas_size.width() / 4.f,
+ canvas_size.height() / 2.f);
+ canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f,
+ canvas_size.height() / 32.f),
+ rect_paint);
+ });
+ loop().RunUntilIdle();
+ EXPECT_THAT(
+ fake_flatland().graph(),
+ IsFlutterGraph(
+ parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/
+ {IsImageLayer(
+ frame_size, kFirstLayerBlendMode,
+ {IsHitRegion(
+ /* x */ 128.f,
+ /* y */ 256.f,
+ /* width */ 16.f,
+ /* height */ 16.f,
+ /* hit_test */
+ fuchsia::ui::composition::HitTestInteraction::DEFAULT)})}));
+}
+
TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) {
fuchsia::ui::composition::ParentViewportWatcherPtr parent_viewport_watcher;
fuchsia::ui::views::ViewportCreationToken viewport_creation_token;