mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[fuchsia][scenic] Use infinite hit region (flutter/engine#38647)
fxbug.dev/118729
This commit is contained in:
parent
76be652682
commit
0720c7f6dc
@ -13,11 +13,6 @@
|
||||
namespace flutter_runner {
|
||||
namespace {
|
||||
|
||||
// Since the flatland hit-region can be transformed (rotated, scaled or
|
||||
// translated), we must ensure that the size of the hit-region will not cause
|
||||
// overflows on operations (like FLT_MAX would).
|
||||
constexpr float kMaxHitRegionSize = 1'000'000.f;
|
||||
|
||||
void AttachClipTransformChild(
|
||||
FlatlandConnection* flatland,
|
||||
FlatlandExternalViewEmbedder::ClipTransform* parent_clip_transform,
|
||||
@ -59,19 +54,13 @@ FlatlandExternalViewEmbedder::FlatlandExternalViewEmbedder(
|
||||
if (intercept_all_input) {
|
||||
input_interceptor_transform_ = flatland_->NextTransformId();
|
||||
flatland_->flatland()->CreateTransform(*input_interceptor_transform_);
|
||||
flatland_->flatland()->SetInfiniteHitRegion(
|
||||
*input_interceptor_transform_,
|
||||
fuchsia::ui::composition::HitTestInteraction::SEMANTICALLY_INVISIBLE);
|
||||
|
||||
flatland_->flatland()->AddChild(root_transform_id_,
|
||||
*input_interceptor_transform_);
|
||||
child_transforms_.emplace_back(*input_interceptor_transform_);
|
||||
|
||||
// Attach full-screen hit testing shield. Note that since the hit-region
|
||||
// may be transformed (translated, rotated), we do not want to set
|
||||
// width/height to FLT_MAX. This will cause a numeric overflow.
|
||||
flatland_->flatland()->SetHitRegions(
|
||||
*input_interceptor_transform_,
|
||||
{{{0, 0, kMaxHitRegionSize, kMaxHitRegionSize},
|
||||
fuchsia::ui::composition::HitTestInteraction::
|
||||
SEMANTICALLY_INVISIBLE}});
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,9 +442,9 @@ void FlatlandExternalViewEmbedder::SubmitFrame(
|
||||
flatland_layer_index++;
|
||||
}
|
||||
|
||||
// Set up the input interceptor at the top of the
|
||||
// scene, if applicable. It will capture all input, and any unwanted input
|
||||
// will be reinjected into embedded views.
|
||||
// Set up the input interceptor at the top of the scene, if applicable. It
|
||||
// will capture all input, and any unwanted input will be reinjected into
|
||||
// embedded views.
|
||||
if (input_interceptor_transform_.has_value()) {
|
||||
flatland_->flatland()->AddChild(root_transform_id_,
|
||||
*input_interceptor_transform_);
|
||||
|
||||
@ -853,6 +853,29 @@ void FakeFlatland::SetHitRegions(
|
||||
transform->hit_regions = std::move(regions);
|
||||
}
|
||||
|
||||
void FakeFlatland::SetInfiniteHitRegion(
|
||||
fuchsia::ui::composition::TransformId transform_id,
|
||||
fuchsia::ui::composition::HitTestInteraction hit_test) {
|
||||
if (transform_id.value == 0) {
|
||||
// TODO(fxb/85619): Raise a FlatlandError here
|
||||
FML_CHECK(false)
|
||||
<< "FakeFlatland::SetTranslation: 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::SetTranslation: TransformId "
|
||||
<< transform_id.value << " does not exist.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto& transform = found_transform->second;
|
||||
ZX_ASSERT(transform);
|
||||
transform->hit_regions = {kInfiniteHitRegion};
|
||||
}
|
||||
|
||||
void FakeFlatland::Clear() {
|
||||
parents_map_.clear();
|
||||
pending_graph_.Clear();
|
||||
|
||||
@ -310,6 +310,11 @@ class FakeFlatland
|
||||
fuchsia::ui::composition::TransformId transform_id,
|
||||
std::vector<fuchsia::ui::composition::HitRegion> regions) override;
|
||||
|
||||
// |fuchsia::ui::composition::Flatland|
|
||||
void SetInfiniteHitRegion(
|
||||
fuchsia::ui::composition::TransformId transform_id,
|
||||
fuchsia::ui::composition::HitTestInteraction hit_test) override;
|
||||
|
||||
// |fuchsia::ui::composition::Flatland|
|
||||
void Clear() override;
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include <zircon/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
@ -138,6 +139,8 @@ namespace flutter_runner::testing {
|
||||
|
||||
constexpr static fuchsia::ui::composition::TransformId kInvalidTransformId{0};
|
||||
constexpr static fuchsia::ui::composition::ContentId kInvalidContentId{0};
|
||||
constexpr static fuchsia::ui::composition::HitRegion kInfiniteHitRegion = {
|
||||
.region = {-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX}};
|
||||
|
||||
// Convenience structure which allows clients to easily create a valid
|
||||
// `ViewCreationToken` / `ViewportCreationToken` pair for use with Flatland
|
||||
|
||||
@ -38,8 +38,10 @@ using fuchsia::scenic::scheduling::FuturePresentationTimes;
|
||||
using fuchsia::scenic::scheduling::PresentReceivedInfo;
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::Contains;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Field;
|
||||
using ::testing::FieldsAre;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Matcher;
|
||||
@ -297,6 +299,16 @@ Matcher<std::shared_ptr<FakeTransform>> IsClipTransformLayer(
|
||||
/*content*/ _,
|
||||
/*hit_regions*/ _));
|
||||
}
|
||||
|
||||
Matcher<std::shared_ptr<FakeTransform>> IsInputShield() {
|
||||
return Pointee(AllOf(
|
||||
// Must not clip the hit region.
|
||||
Field("clip_bounds", &FakeTransform::clip_bounds, Eq(std::nullopt)),
|
||||
// Hit region must be "infinite".
|
||||
Field("hit_regions", &FakeTransform::hit_regions,
|
||||
Contains(flutter_runner::testing::kInfiniteHitRegion))));
|
||||
}
|
||||
|
||||
fuchsia::ui::composition::OnNextFrameBeginValues WithPresentCredits(
|
||||
uint32_t additional_present_credits) {
|
||||
fuchsia::ui::composition::OnNextFrameBeginValues values;
|
||||
@ -396,15 +408,18 @@ class FlatlandExternalViewEmbedderTest : public ::testing::Test {
|
||||
fuchsia::ui::composition::FlatlandHandle flatland =
|
||||
fake_flatland_.ConnectFlatland(session_subloop_->dispatcher());
|
||||
|
||||
auto test_name =
|
||||
const auto test_name =
|
||||
::testing::UnitTest::GetInstance()->current_test_info()->name();
|
||||
const auto max_frames_in_flight = 1;
|
||||
const auto vsync_offset = fml::TimeDelta::Zero();
|
||||
return std::make_shared<FlatlandConnection>(
|
||||
std::move(test_name), std::move(flatland), []() { FAIL(); },
|
||||
[](auto...) {}, 1, fml::TimeDelta::Zero());
|
||||
std::move(test_name), std::move(flatland),
|
||||
/*error_callback=*/[] { FAIL(); }, /*ofpe_callback=*/[](auto...) {},
|
||||
max_frames_in_flight, vsync_offset);
|
||||
}
|
||||
|
||||
// Primary loop and subloop for the FakeFlatland instance to process its
|
||||
// messages. The subloop allocates it's own zx_port_t, allowing us to use a
|
||||
// messages. The subloop allocates its own zx_port_t, allowing us to use a
|
||||
// separate port for each end of the message channel, rather than sharing a
|
||||
// single one. Dual ports allow messages and responses to be intermingled,
|
||||
// which is how production code behaves; this improves test realism.
|
||||
@ -1476,4 +1491,143 @@ TEST_F(FlatlandExternalViewEmbedderTest, SimpleScene_OverlappingHitRegions) {
|
||||
fuchsia::ui::composition::HitTestInteraction::DEFAULT)})}));
|
||||
}
|
||||
|
||||
TEST_F(FlatlandExternalViewEmbedderTest, ViewportCoveredWithInputInterceptor) {
|
||||
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(),
|
||||
/*intercept_all_input=*/true // Enables the interceptor.
|
||||
);
|
||||
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, {IsInputShield()}));
|
||||
|
||||
// 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};
|
||||
|
||||
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,
|
||||
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, {IsInputShield()}));
|
||||
|
||||
// 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)}),
|
||||
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)}),
|
||||
IsInputShield()},
|
||||
{kInvDPR, kInvDPR}));
|
||||
}
|
||||
|
||||
} // namespace flutter_runner::testing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user