diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc index 71e87239126..ed0cb2384c7 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc @@ -213,20 +213,28 @@ void FlatlandExternalViewEmbedder::SubmitFrame( } } - // TODO(fxbug.dev/64201): Handle clips. - // Set transform for the viewport. - // TODO(fxbug.dev/94000): Handle scaling. if (view_mutators.transform != viewport.mutators.transform) { flatland_->flatland()->SetTranslation( viewport.transform_id, {static_cast(view_mutators.transform.getTranslateX()), static_cast(view_mutators.transform.getTranslateY())}); + + flatland_->flatland()->SetScale( + viewport.transform_id, {view_mutators.transform.getScaleX(), + view_mutators.transform.getScaleY()}); viewport.mutators.transform = view_mutators.transform; } // TODO(fxbug.dev/94000): Set HitTestBehavior. - // TODO(fxbug.dev/94000): Set opacity. + // TODO(fxbug.dev/94000): Set ClipRegions. + + // Set opacity. + if (view_mutators.opacity != viewport.mutators.opacity) { + flatland_->flatland()->SetOpacity(viewport.transform_id, + view_mutators.opacity); + viewport.mutators.opacity = view_mutators.opacity; + } // Set size and occlusion hint. if (view_size != viewport.size || diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc index 2359fcfabbf..a55d89aca2b 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc @@ -211,6 +211,39 @@ void FakeFlatland::SetTranslation( transform->translation = translation; } +void FakeFlatland::SetScale(fuchsia::ui::composition::TransformId transform_id, + fuchsia::math::VecF scale) { + if (transform_id.value == 0) { + // TODO(fxb/85619): Raise a FlatlandError here + FML_CHECK(false) << "FakeFlatland::SetScale: TransformId 0 is invalid."; + return; + } + + auto found_transform = pending_graph_.transform_map.find(transform_id.value); + if (found_transform == pending_graph_.transform_map.end()) { + // TODO(fxb/85619): Raise a FlatlandError here + FML_CHECK(false) << "FakeFlatland::SetScale: TransformId " + << transform_id.value << " does not exist."; + return; + } + + if (scale.x == 0.f || scale.y == 0.f) { + FML_CHECK(false) << "SetScale failed, zero values not allowed (" << scale.x + << ", " << scale.y << " )."; + return; + } + + if (isinf(scale.x) || isinf(scale.y) || isnan(scale.x) || isnan(scale.y)) { + FML_CHECK(false) << "SetScale failed, invalid scale values (" << scale.x + << ", " << scale.y << " )."; + return; + } + + auto& transform = found_transform->second; + FML_CHECK(transform); + transform->scale = scale; +} + void FakeFlatland::SetOrientation( fuchsia::ui::composition::TransformId transform_id, fuchsia::ui::composition::Orientation orientation) { @@ -234,6 +267,32 @@ void FakeFlatland::SetOrientation( transform->orientation = orientation; } +void FakeFlatland::SetOpacity( + fuchsia::ui::composition::TransformId transform_id, + float value) { + if (transform_id.value == 0) { + // TODO(fxb/85619): Raise a FlatlandError here + FML_CHECK(false) << "FakeFlatland::SetOpacity: TransformId 0 is invalid."; + return; + } + + auto found_transform = pending_graph_.transform_map.find(transform_id.value); + if (found_transform == pending_graph_.transform_map.end()) { + // TODO(fxb/85619): Raise a FlatlandError here + FML_CHECK(false) << "FakeFlatland::SetOpacity: TransformId " + << transform_id.value << " does not exist."; + return; + } + + if (value < 0.f || value > 1.f) { + FML_CHECK(false) << "FakeFlatland::SetOpacity: Invalid opacity value."; + } + + auto& transform = found_transform->second; + FML_CHECK(transform); + transform->opacity = value; +} + void FakeFlatland::SetClipBoundary( fuchsia::ui::composition::TransformId transform_id, std::unique_ptr bounds) { diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h index 5690939ac39..94ca6578f93 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h @@ -221,11 +221,19 @@ class FakeFlatland void SetTranslation(fuchsia::ui::composition::TransformId transform_id, fuchsia::math::Vec translation) override; + // |fuchsia::ui::composition::Flatland| + void SetScale(fuchsia::ui::composition::TransformId transform_id, + fuchsia::math::VecF scale) override; + // |fuchsia::ui::composition::Flatland| void SetOrientation( fuchsia::ui::composition::TransformId transform_id, fuchsia::ui::composition::Orientation orientation) override; + // |fuchsia::ui::composition::Flatland| + void SetOpacity(fuchsia::ui::composition::TransformId transform_id, + float value) override; + // |fuchsia::ui::composition::Flatland| void SetClipBoundary(fuchsia::ui::composition::TransformId transform_id, std::unique_ptr bounds) override; diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc index 9d72400d984..a265241f4d7 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc @@ -65,8 +65,10 @@ std::shared_ptr CloneFakeTransform( transform.get(), std::make_shared(FakeTransform{ .id = transform->id, .translation = transform->translation, - .clip_bounds = transform->clip_bounds, + .scale = transform->scale, .orientation = transform->orientation, + .clip_bounds = transform->clip_bounds, + .opacity = transform->opacity, .children = CloneFakeTransformVector( transform->children, transform_cache), .content = CloneFakeContent(transform->content), diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h index 74ae2da2803..cd2170c3b58 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h @@ -39,6 +39,11 @@ inline bool operator==(const fuchsia::math::Vec& a, return a.x == b.x && a.y == b.y; } +inline bool operator==(const fuchsia::math::VecF& a, + const fuchsia::math::VecF& b) { + return a.x == b.x && a.y == b.y; +} + inline bool operator==(const fuchsia::math::Rect& a, const fuchsia::math::Rect& b) { return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; @@ -172,15 +177,21 @@ struct FakeTransform { bool operator==(const FakeTransform& other) const; constexpr static fuchsia::math::Vec kDefaultTranslation{.x = 0, .y = 0}; + constexpr static fuchsia::math::VecF kDefaultScale{.x = 1.0f, .y = 1.0f}; constexpr static fuchsia::ui::composition::Orientation kDefaultOrientation{ fuchsia::ui::composition::Orientation::CCW_0_DEGREES}; + constexpr static float kDefaultOpacity = 1.0f; fuchsia::ui::composition::TransformId id{kInvalidTransformId}; fuchsia::math::Vec translation{kDefaultTranslation}; - std::optional clip_bounds; + fuchsia::math::VecF scale{kDefaultScale}; fuchsia::ui::composition::Orientation orientation{kDefaultOrientation}; + std::optional clip_bounds = std::nullopt; + + float opacity = kDefaultOpacity; + std::vector> children; std::shared_ptr content; size_t num_hit_regions; diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc index 38c4454683b..0298537cc24 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc @@ -220,7 +220,8 @@ Matcher IsFlutterGraph( /*content_map*/ _, /*transform_map*/ _, Pointee(FieldsAre( /*id*/ _, FakeTransform::kDefaultTranslation, - /*clip_bounds*/ _, FakeTransform::kDefaultOrientation, + FakeTransform::kDefaultScale, FakeTransform::kDefaultOrientation, + /*clip_bounds*/ _, FakeTransform::kDefaultOpacity, /*children*/ ElementsAreArray(layer_matchers), /*content*/ Eq(nullptr), /*num_hit_regions*/ _)), Eq(FakeView{ @@ -241,7 +242,8 @@ Matcher> IsImageLayer( size_t num_hit_regions) { return Pointee(FieldsAre( /*id*/ _, FakeTransform::kDefaultTranslation, - /*clip_bounds*/ _, FakeTransform::kDefaultOrientation, + FakeTransform::kDefaultScale, FakeTransform::kDefaultOrientation, + /*clip_bounds*/ _, FakeTransform::kDefaultOpacity, /*children*/ IsEmpty(), /*content*/ Pointee(VariantWith(FieldsAre( @@ -256,10 +258,12 @@ Matcher> IsViewportLayer( const fuchsia::ui::views::ViewCreationToken& view_token, const fuchsia::math::SizeU& view_logical_size, const fuchsia::math::Inset& view_inset, - const fuchsia::math::Vec& view_transform) { + const fuchsia::math::Vec& view_translation, + const fuchsia::math::VecF& view_scale, + const float view_opacity) { return Pointee(FieldsAre( - /* id */ _, view_transform, - /*clip_bounds*/ _, FakeTransform::kDefaultOrientation, + /* id */ _, view_translation, view_scale, + FakeTransform::kDefaultOrientation, /*clip_bounds*/ _, view_opacity, /*children*/ IsEmpty(), /*content*/ Pointee(VariantWith(FieldsAre( @@ -517,8 +521,21 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { static_cast(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(); - flutter::EmbeddedViewParams child_view_params( - SkMatrix::I(), child_view_size_signed, flutter::MutatorsStack()); + + const int kOpacity = 200; + const float kOpacityFloat = 200 / 255.0f; + const fuchsia::math::VecF kScale{0.5f, 0.9f}; + + auto matrix = SkMatrix::I(); + matrix.setScaleX(kScale.x); + matrix.setScaleY(kScale.y); + + auto mutators_stack = flutter::MutatorsStack(); + mutators_stack.PushOpacity(kOpacity); + mutators_stack.PushTransform(matrix); + + flutter::EmbeddedViewParams child_view_params(matrix, child_view_size_signed, + mutators_stack); external_view_embedder.CreateView( child_view_id, []() {}, [](fuchsia::ui::composition::ContentId, @@ -573,24 +590,24 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { loop().RunUntilIdle(); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - child_view_inset, {0, 0}), - IsImageLayer(frame_size, kUpperLayerBlendMode, 1)})); + IsFlutterGraph( + parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ + {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + IsViewportLayer(child_view_token, child_view_size, child_view_inset, + {0, 0}, kScale, kOpacityFloat), + IsImageLayer(frame_size, kUpperLayerBlendMode, 1)})); // Destroy the view. The scene graph shouldn't change yet. external_view_embedder.DestroyView( child_view_id, [](fuchsia::ui::composition::ContentId) {}); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - child_view_inset, {0, 0}), - IsImageLayer(frame_size, kUpperLayerBlendMode, 1)})); + IsFlutterGraph( + parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ + {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + IsViewportLayer(child_view_token, child_view_size, child_view_inset, + {0, 0}, kScale, kOpacityFloat), + IsImageLayer(frame_size, kUpperLayerBlendMode, 1)})); // Draw another frame without the view. The scene graph shouldn't change yet. DrawSimpleFrame( @@ -607,12 +624,12 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { }); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - child_view_inset, {0, 0}), - IsImageLayer(frame_size, kUpperLayerBlendMode, 1)})); + IsFlutterGraph( + parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ + {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + IsViewportLayer(child_view_token, child_view_size, child_view_inset, + {0, 0}, kScale, kOpacityFloat), + IsImageLayer(frame_size, kUpperLayerBlendMode, 1)})); // Pump the message loop. The scene updates should propagate to flatland. loop().RunUntilIdle(); @@ -660,8 +677,21 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) { static_cast(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(); - flutter::EmbeddedViewParams child_view_params( - SkMatrix::I(), child_view_size_signed, flutter::MutatorsStack()); + + const int kOpacity = 125; + const float kOpacityFloat = 125 / 255.0f; + const fuchsia::math::VecF kScale{2.f, 3.0f}; + + auto matrix = SkMatrix::I(); + matrix.setScaleX(kScale.x); + matrix.setScaleY(kScale.y); + + auto mutators_stack = flutter::MutatorsStack(); + mutators_stack.PushOpacity(kOpacity); + mutators_stack.PushTransform(matrix); + + flutter::EmbeddedViewParams child_view_params(matrix, child_view_size_signed, + mutators_stack); external_view_embedder.CreateView( child_view_id, []() {}, [](fuchsia::ui::composition::ContentId, @@ -697,22 +727,24 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) { loop().RunUntilIdle(); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph( - parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - FakeViewport::kDefaultViewportInset, {0, 0})})); + IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, + view_ref, /*layers*/ + {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + IsViewportLayer(child_view_token, child_view_size, + FakeViewport::kDefaultViewportInset, + {0, 0}, kScale, kOpacityFloat)})); // Destroy the view. The scene graph shouldn't change yet. external_view_embedder.DestroyView( child_view_id, [](fuchsia::ui::composition::ContentId) {}); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph( - parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - FakeViewport::kDefaultViewportInset, {0, 0})})); + IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, + view_ref, /*layers*/ + {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + IsViewportLayer(child_view_token, child_view_size, + FakeViewport::kDefaultViewportInset, + {0, 0}, kScale, kOpacityFloat)})); // Draw another frame without the view. The scene graph shouldn't change yet. DrawSimpleFrame( @@ -727,13 +759,15 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) { canvas_size.height() / 32.f), rect_paint); }); + EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph( - parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - FakeViewport::kDefaultViewportInset, {0, 0})})); + IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, + view_ref, /*layers*/ + {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + IsViewportLayer(child_view_token, child_view_size, + FakeViewport::kDefaultViewportInset, + {0, 0}, kScale, kOpacityFloat)})); // Pump the message loop. The scene updates should propagate to flatland. loop().RunUntilIdle();