| // Copyright 2020 The Fuchsia 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 <fuchsia/ui/input/cpp/fidl.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/ui/scenic/lib/input/tests/util.h" |
| |
| // These tests exercise the context View Space to target View Space coordinate transform logic |
| // applied to pointer events sent to sessions using the input injection API. |
| // Setup: |
| // - Injection done in context View Space, with fuchsia.ui.pointerinjector |
| // - Target(s) specified by View (using view ref koids) |
| // - Dispatch done in fuchsia.ui.scenic.SessionListener (legacy) |
| |
| namespace lib_ui_input_tests { |
| namespace { |
| |
| using fuchsia::ui::input::PointerEventPhase; |
| |
| // Sets up a 9x9 "display". |
| class CoordinateTransformTest2 : public InputSystemTest { |
| // clang-format off |
| static constexpr std::array<float, 9> kIdentityMatrix = { |
| 1, 0, 0, // column one |
| 0, 1, 0, // column two |
| 0, 0, 1, // column three |
| }; |
| // clang-format on |
| |
| protected: |
| void SetUp() override { InputSystemTest::SetUp(); } |
| |
| void TearDown() override { |
| injector_ = {}; |
| InputSystemTest::TearDown(); |
| } |
| |
| void Inject(float x, float y, fuchsia::ui::pointerinjector::EventPhase phase) { |
| FX_CHECK(injector_); |
| fuchsia::ui::pointerinjector::Event event; |
| event.set_timestamp(0); |
| { |
| fuchsia::ui::pointerinjector::PointerSample pointer_sample; |
| pointer_sample.set_pointer_id(1); |
| pointer_sample.set_phase(phase); |
| pointer_sample.set_position_in_viewport({x, y}); |
| fuchsia::ui::pointerinjector::Data data; |
| data.set_pointer_sample(std::move(pointer_sample)); |
| event.set_data(std::move(data)); |
| } |
| std::vector<fuchsia::ui::pointerinjector::Event> events; |
| events.emplace_back(std::move(event)); |
| injector_->Inject(std::move(events), [] {}); |
| } |
| |
| void RegisterInjector(fuchsia::ui::views::ViewRef context_view_ref, |
| fuchsia::ui::views::ViewRef target_view_ref, |
| fuchsia::ui::pointerinjector::DispatchPolicy dispatch_policy = |
| fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET, |
| std::array<float, 9> viewport_to_context_transform = kIdentityMatrix) { |
| fuchsia::ui::pointerinjector::Config config; |
| config.set_device_id(1); |
| config.set_device_type(fuchsia::ui::pointerinjector::DeviceType::TOUCH); |
| config.set_dispatch_policy(dispatch_policy); |
| { |
| { |
| fuchsia::ui::pointerinjector::Context context; |
| context.set_view(std::move(context_view_ref)); |
| config.set_context(std::move(context)); |
| } |
| { |
| fuchsia::ui::pointerinjector::Target target; |
| target.set_view(std::move(target_view_ref)); |
| config.set_target(std::move(target)); |
| } |
| { |
| fuchsia::ui::pointerinjector::Viewport viewport; |
| viewport.set_extents(FullScreenExtents()); |
| viewport.set_viewport_to_context_transform(viewport_to_context_transform); |
| config.set_viewport(std::move(viewport)); |
| } |
| } |
| |
| bool error_callback_fired = false; |
| injector_.set_error_handler([&error_callback_fired](zx_status_t) { |
| FX_LOGS(ERROR) << "Channel closed."; |
| error_callback_fired = true; |
| }); |
| bool register_callback_fired = false; |
| input_system()->RegisterPointerinjector( |
| std::move(config), injector_.NewRequest(), |
| [®ister_callback_fired] { register_callback_fired = true; }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(register_callback_fired); |
| ASSERT_FALSE(error_callback_fired); |
| } |
| |
| uint32_t test_display_width_px() const override { return 9; } |
| uint32_t test_display_height_px() const override { return 9; } |
| std::array<std::array<float, 2>, 2> FullScreenExtents() const { |
| return {{{0, 0}, |
| {static_cast<float>(test_display_width_px()), |
| static_cast<float>(test_display_height_px())}}}; |
| } |
| |
| private: |
| fuchsia::ui::pointerinjector::DevicePtr injector_; |
| }; |
| |
| // In this test we set up the context and the target. We apply a scale, rotation and translation |
| // transform to both of their view holder nodes, and then inject pointer events to confirm that |
| // the coordinates received by the listener are correctly transformed. |
| // Only the transformation of the target, relative to the context, should have any effect on |
| // the output. |
| // The viewport-to-context transform here is the identity. |
| // |
| // Below are ASCII diagrams showing the transformation *difference* between target and context. |
| // Note that the dashes represent the context view and notated X,Y coordinate system is the |
| // context's coordinate system. The target view's coordinate system has its origin at corner '1'. |
| // |
| // Scene pre-transformation |
| // 1,2,3,4 denote the corners of the target view: |
| // X -> |
| // Y 1 O O O O 2 |
| // | O O O O O O |
| // v O O O O O O |
| // O O O O O O |
| // O O O O O O |
| // 4 O O O O 3 |
| // |
| // After scale: |
| // X -> |
| // Y 1 - O - O - O O 2 |
| // | - - - - - - - |
| // V - - - - - - - |
| // O - O - O - O O O |
| // - - - - - - - |
| // - - - - - - - |
| // O O O O O O |
| // |
| // |
| // O O O O O O |
| // |
| // |
| // O O O O O O |
| // |
| // |
| // 4 O O O O 3 |
| // |
| // After rotation: |
| // X -> |
| // Y 4 O O O O 1 - - - - - - |
| // | - - - - - - |
| // V O O O O O O - - - - - - |
| // - - - - - - |
| // O O O O O O - - - - - - |
| // - - - - - - |
| // O O O O O O |
| // |
| // O O O O O O |
| // |
| // 3 O O O O 2 |
| // |
| // After translation: |
| // X -> |
| // Y 4 O O O O D 1 - - - C1 |
| // | - - - - - - |
| // V O O O O O - O - - - - |
| // - - - - - - |
| // O O O O O - O - - - - |
| // U - - - - C2 |
| // O O O O O O |
| // |
| // O O O O O O |
| // |
| // 3 O O O O 2 |
| // |
| TEST_F(CoordinateTransformTest2, InjectedInput_ShouldBeCorrectlyTransformed) { |
| auto [v1, vh1] = scenic::ViewTokenPair::New(); |
| auto [v2, vh2] = scenic::ViewTokenPair::New(); |
| |
| // Set up a scene with two ViewHolders, one a child of the other. |
| auto [root_session, root_resources] = CreateScene(); |
| scenic::ViewHolder holder_1(root_session.session(), std::move(vh1), "holder_1"); |
| { |
| holder_1.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(holder_1); |
| // Scale, rotate and translate the context to verify that it has no effect on the outcome. |
| holder_1.SetScale(2, 3, 1); |
| const auto rotation_quaternion = glm::angleAxis(glm::pi<float>() / 2.f, glm::vec3(0, 0, 1)); |
| holder_1.SetRotation(rotation_quaternion.x, rotation_quaternion.y, rotation_quaternion.z, |
| rotation_quaternion.w); |
| holder_1.SetTranslation(1, 0, 0); |
| |
| RequestToPresent(root_session.session()); |
| } |
| |
| SessionWrapper client_1 = CreateClient("view_1", std::move(v1)); |
| scenic::ViewHolder holder_2(client_1.session(), std::move(vh2), "holder_2"); |
| { |
| holder_2.SetViewProperties(k5x5x1); |
| |
| client_1.view()->AddChild(holder_2); |
| |
| // Scale, rotate and translate target. |
| // Scale X by 2 and Y by 3. |
| holder_2.SetScale(2, 3, 1); |
| // Rotate 90 degrees counter clockwise around Z-axis (Z-axis points into screen, so appears as |
| // clockwise rotation). |
| const auto rotation_quaternion = glm::angleAxis(glm::pi<float>() / 2.f, glm::vec3(0, 0, 1)); |
| holder_2.SetRotation(rotation_quaternion.x, rotation_quaternion.y, rotation_quaternion.z, |
| rotation_quaternion.w); |
| // Translate by 1 in the X direction. |
| holder_2.SetTranslation(1, 0, 0); |
| |
| RequestToPresent(client_1.session()); |
| } |
| |
| SessionWrapper client_2 = CreateClient("view_2", std::move(v2)); |
| |
| // Scene is now set up, send in the input. One event for where each corner of the view was |
| // pre-transformation. |
| { |
| RegisterInjector(client_1.view_ref(), client_2.view_ref()); |
| Inject(0, 0, fuchsia::ui::pointerinjector::EventPhase::ADD); |
| Inject(5, 0, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(5, 5, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(0, 5, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| RunLoopUntilIdle(); |
| } |
| |
| { |
| // Context should receive no events. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_1.events(); |
| EXPECT_EQ(events.size(), 0u); |
| } |
| |
| { // Target should receive events correctly transformed to its Local Space. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_2.events(); |
| ASSERT_EQ(events.size(), 6u); |
| |
| // Targets gets properly transformed input coordinates. |
| EXPECT_TRUE( |
| PointerMatches(events[0].pointer(), 1u, PointerEventPhase::ADD, 0.0 / 2.0, 1.0 / 3.0)); |
| EXPECT_TRUE( |
| PointerMatches(events[1].pointer(), 1u, PointerEventPhase::DOWN, 0.0 / 2.0, 1.0 / 3.0)); |
| EXPECT_TRUE( |
| PointerMatches(events[2].pointer(), 1u, PointerEventPhase::MOVE, 0.0 / 2.0, -4.0 / 3.0)); |
| EXPECT_TRUE( |
| PointerMatches(events[3].pointer(), 1u, PointerEventPhase::MOVE, 5.0 / 2.0, -4.0 / 3.0)); |
| EXPECT_TRUE( |
| PointerMatches(events[4].pointer(), 1u, PointerEventPhase::UP, 5.0 / 2.0, 1.0 / 3.0)); |
| EXPECT_TRUE( |
| PointerMatches(events[5].pointer(), 1u, PointerEventPhase::REMOVE, 5.0 / 2.0, 1.0 / 3.0)); |
| } |
| } |
| |
| // In this test the context and the target have identical coordinate systems, but the viewport |
| // no longer matches the context's coordinate system. |
| // |
| // Below is an ASCII diagram showing the resulting setup. |
| // O represents the views, - the viewport. |
| // X -> |
| // Y O O O O O O |
| // | |
| // V D - - - - C1- - - - |
| // O - O - O - O - O - O |
| // - - - - - - - - - - |
| // - - - - - - - - - - |
| // O - O - O - O - O - O |
| // U - - - - C2- - - - |
| // - - - - - - - - - - |
| // O - O - O - O - O - O |
| // - - - - - - - - - - |
| // - - - - - - - - - - |
| // O O O O O O |
| // |
| // |
| // O O O O O O |
| // |
| TEST_F(CoordinateTransformTest2, InjectedInput_ShouldBeCorrectlyViewportTransformed) { |
| auto [v1, vh1] = scenic::ViewTokenPair::New(); |
| auto [v2, vh2] = scenic::ViewTokenPair::New(); |
| |
| // Set up a scene with two ViewHolders, one a child of the other. |
| auto [root_session, root_resources] = CreateScene(); |
| scenic::ViewHolder holder_1(root_session.session(), std::move(vh1), "holder_1"); |
| { |
| holder_1.SetViewProperties(k5x5x1); |
| root_resources.scene.AddChild(holder_1); |
| RequestToPresent(root_session.session()); |
| } |
| |
| SessionWrapper client_1 = CreateClient("view_1", std::move(v1)); |
| scenic::ViewHolder holder_2(client_1.session(), std::move(vh2), "holder_2"); |
| { |
| holder_2.SetViewProperties(k5x5x1); |
| client_1.view()->AddChild(holder_2); |
| RequestToPresent(client_1.session()); |
| } |
| |
| SessionWrapper client_2 = CreateClient("view_2", std::move(v2)); |
| |
| // Scene is now set up, send in the input. One event for where each corner of the view was |
| // pre-transformation. |
| { |
| // Transform to scale the viewport by 1/2 in the x-direction, 1/3 in the y-direction, |
| // and then translate by (1, 2). |
| // clang-format off |
| static constexpr std::array<float, 9> kViewportToContextTransform = { |
| 1./2., 0, 0, // first column |
| 0, 1./3., 0, // second column |
| 1, 2, 1, // third column |
| }; |
| // clang-format on |
| |
| RegisterInjector(client_1.view_ref(), client_2.view_ref(), |
| fuchsia::ui::pointerinjector::DispatchPolicy::EXCLUSIVE_TARGET, |
| kViewportToContextTransform); |
| Inject(0, 0, fuchsia::ui::pointerinjector::EventPhase::ADD); |
| Inject(5, 0, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(5, 5, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(0, 5, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| RunLoopUntilIdle(); |
| } |
| |
| { |
| // Context should receive no events. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_1.events(); |
| EXPECT_EQ(events.size(), 0u); |
| } |
| |
| { // Target should receive events correctly transformed to its Local Space. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_2.events(); |
| ASSERT_EQ(events.size(), 6u); |
| |
| // Targets gets properly transformed input coordinates. |
| EXPECT_TRUE(PointerMatches(events[0].pointer(), 1u, PointerEventPhase::ADD, 0.0 / 2.0 + 1, |
| 0.0 / 3.0 + 2)); |
| EXPECT_TRUE(PointerMatches(events[1].pointer(), 1u, PointerEventPhase::DOWN, 0.0 / 2.0 + 1, |
| 0.0 / 3.0 + 2)); |
| EXPECT_TRUE(PointerMatches(events[2].pointer(), 1u, PointerEventPhase::MOVE, 5.0 / 2.0 + 1, |
| 0.0 / 3.0 + 2)); |
| EXPECT_TRUE(PointerMatches(events[3].pointer(), 1u, PointerEventPhase::MOVE, 5.0 / 2.0 + 1, |
| 5.0 / 3.0 + 2)); |
| EXPECT_TRUE(PointerMatches(events[4].pointer(), 1u, PointerEventPhase::UP, 0.0 / 2.0 + 1, |
| 5.0 / 3.0 + 2)); |
| EXPECT_TRUE(PointerMatches(events[5].pointer(), 1u, PointerEventPhase::REMOVE, 0.0 / 2.0 + 1, |
| 5.0 / 3.0 + 2)); |
| } |
| } |
| |
| // In this test the context and the target have identical coordinate systems except for a 90 degree |
| // rotation. Check that all corners still generate hits. This confirms that small floating point |
| // errors don't cause misses. |
| // |
| // Scene pre-transformation |
| // 1,2,3,4 denote the corners of the target view: |
| // X -> |
| // Y 1 O O O O 2 |
| // | O O O O O O |
| // v O O O O O O |
| // O O O O O O |
| // O O O O O O |
| // 4 O O O O 3 |
| // |
| // Post-rotation |
| // X -> |
| // Y 4 O O O O 1 |
| // | O O O O O O |
| // v O O O O O O |
| // O O O O O O |
| // O O O O O O |
| // 3 O O O O 2 |
| TEST_F(CoordinateTransformTest2, InjectedInput_OnRotatedChild_ShouldHitEdges) { |
| auto [v1, vh1] = scenic::ViewTokenPair::New(); |
| auto [v2, vh2] = scenic::ViewTokenPair::New(); |
| |
| // Set up a scene with two ViewHolders, one a child of the other. |
| auto [root_session, root_resources] = CreateScene(); |
| scenic::ViewHolder holder_1(root_session.session(), std::move(vh1), "holder_1"); |
| { |
| holder_1.SetViewProperties(k5x5x1); |
| root_resources.scene.AddChild(holder_1); |
| RequestToPresent(root_session.session()); |
| } |
| |
| SessionWrapper client_1 = CreateClient("view_1", std::move(v1)); |
| scenic::ViewHolder holder_2(client_1.session(), std::move(vh2), "holder_2"); |
| { |
| holder_2.SetViewProperties(k5x5x1); |
| client_1.view()->AddChild(holder_2); |
| // Rotate 90 degrees counter clockwise around Z-axis (Z-axis points into screen, so appears as |
| // clockwise rotation). |
| holder_2.SetAnchor(2.5f, 2.5f, 0); |
| const auto rotation_quaternion = glm::angleAxis(glm::pi<float>() / 2.f, glm::vec3(0, 0, 1)); |
| holder_2.SetRotation(rotation_quaternion.x, rotation_quaternion.y, rotation_quaternion.z, |
| rotation_quaternion.w); |
| RequestToPresent(client_1.session()); |
| } |
| |
| SessionWrapper client_2 = CreateClient("view_2", std::move(v2)); |
| |
| // Scene is now set up, send in the input. One interaction for each corner. |
| { |
| RegisterInjector(client_1.view_ref(), client_2.view_ref(), |
| fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET); |
| Inject(0, 0, fuchsia::ui::pointerinjector::EventPhase::ADD); |
| Inject(0, 0, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| Inject(0, 5, fuchsia::ui::pointerinjector::EventPhase::ADD); |
| Inject(0, 5, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| Inject(5, 5, fuchsia::ui::pointerinjector::EventPhase::ADD); |
| Inject(5, 5, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| Inject(5, 0, fuchsia::ui::pointerinjector::EventPhase::ADD); |
| Inject(5, 0, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| RunLoopUntilIdle(); |
| } |
| |
| { // Target should receive all events rotated 90 degrees. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_2.events(); |
| ASSERT_EQ(events.size(), 17u); |
| |
| EXPECT_TRUE(PointerMatches(events[0].pointer(), 1u, PointerEventPhase::ADD, 0.0, 5.0)); |
| EXPECT_TRUE(events[1].is_focus()); |
| EXPECT_TRUE(PointerMatches(events[2].pointer(), 1u, PointerEventPhase::DOWN, 0.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[3].pointer(), 1u, PointerEventPhase::UP, 0.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[4].pointer(), 1u, PointerEventPhase::REMOVE, 0.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[5].pointer(), 1u, PointerEventPhase::ADD, 5.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[6].pointer(), 1u, PointerEventPhase::DOWN, 5.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[7].pointer(), 1u, PointerEventPhase::UP, 5.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[8].pointer(), 1u, PointerEventPhase::REMOVE, 5.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[9].pointer(), 1u, PointerEventPhase::ADD, 5.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[10].pointer(), 1u, PointerEventPhase::DOWN, 5.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[11].pointer(), 1u, PointerEventPhase::UP, 5.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[12].pointer(), 1u, PointerEventPhase::REMOVE, 5.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[13].pointer(), 1u, PointerEventPhase::ADD, 0.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[14].pointer(), 1u, PointerEventPhase::DOWN, 0.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[15].pointer(), 1u, PointerEventPhase::UP, 0.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[16].pointer(), 1u, PointerEventPhase::REMOVE, 0.0, 0.0)); |
| } |
| } |
| |
| // In this test we set up the context and the target. We apply clip space transform to the camera |
| // and then inject pointer events to confirm that the coordinates received by the listener are |
| // not impacted by the clip space transform. |
| TEST_F(CoordinateTransformTest2, ClipSpaceTransformedScene_ShouldHaveNoImpactOnOutput) { |
| auto [v1, vh1] = scenic::ViewTokenPair::New(); |
| auto [v2, vh2] = scenic::ViewTokenPair::New(); |
| |
| // Set up a scene with two ViewHolders, one a child of the other. |
| auto [root_session, root_resources] = CreateScene(); |
| |
| // Set the clip space transform on the camera. |
| // Camera zooms in by 3x, and the camera is moved to (24,54) in the scene's coordinate space. |
| root_resources.camera.SetClipSpaceTransform(/*x offset*/ 24, /*y offset*/ 54, /*scale*/ 3); |
| |
| // Set up the scene. |
| scenic::ViewHolder holder_1(root_session.session(), std::move(vh1), "holder_1"); |
| { |
| holder_1.SetViewProperties(k5x5x1); |
| root_resources.scene.AddChild(holder_1); |
| RequestToPresent(root_session.session()); |
| } |
| |
| SessionWrapper client_1 = CreateClient("view_1", std::move(v1)); |
| scenic::ViewHolder holder_2(client_1.session(), std::move(vh2), "holder_2"); |
| { |
| holder_2.SetViewProperties(k5x5x1); |
| client_1.view()->AddChild(holder_2); |
| RequestToPresent(client_1.session()); |
| } |
| |
| SessionWrapper client_2 = CreateClient("view_2", std::move(v2)); |
| |
| // Scene is now set up, send in the input. One event for where each corner of the view was |
| // pre-transformation. |
| { |
| RegisterInjector(client_1.view_ref(), client_2.view_ref()); |
| Inject(0, 0, fuchsia::ui::pointerinjector::EventPhase::ADD); |
| Inject(5, 0, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(5, 5, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(0, 5, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| RunLoopUntilIdle(); |
| } |
| |
| { |
| // Context should receive no events. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_1.events(); |
| EXPECT_EQ(events.size(), 0u); |
| } |
| |
| { // Target should receive identical events to injected, since their coordinate spaces are the |
| // same. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_2.events(); |
| ASSERT_EQ(events.size(), 6u); |
| |
| EXPECT_TRUE(PointerMatches(events[0].pointer(), 1u, PointerEventPhase::ADD, 0.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[1].pointer(), 1u, PointerEventPhase::DOWN, 0.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[2].pointer(), 1u, PointerEventPhase::MOVE, 5.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[3].pointer(), 1u, PointerEventPhase::MOVE, 5.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[4].pointer(), 1u, PointerEventPhase::UP, 0.0, 5.0)); |
| EXPECT_TRUE(PointerMatches(events[5].pointer(), 1u, PointerEventPhase::REMOVE, 0.0, 5.0)); |
| } |
| } |
| |
| // Basic scene (no transformations) where the Viewport is smaller than the Views. |
| // We then inject two streams: The first has an ADD outside the Viewport, which counts as a miss and |
| // should not be seen by anyone. The second stream has the ADD inside the Viewport and subseqeuent |
| // events outside, and this full stream should be seen by the target. |
| TEST_F(CoordinateTransformTest2, InjectionOutsideViewport_ShouldLimitOnADD) { |
| auto [v1, vh1] = scenic::ViewTokenPair::New(); |
| auto [v2, vh2] = scenic::ViewTokenPair::New(); |
| |
| // Set up a scene with two ViewHolders, one a child of the other. |
| auto [root_session, root_resources] = CreateScene(); |
| // Make the Views bigger than the Viewport. |
| static constexpr fuchsia::ui::gfx::ViewProperties k100x100x1 = { |
| .bounding_box = {.max = {100, 100, 1}}}; |
| scenic::ViewHolder holder_1(root_session.session(), std::move(vh1), "holder_1"); |
| { |
| holder_1.SetViewProperties(k100x100x1); |
| root_resources.scene.AddChild(holder_1); |
| RequestToPresent(root_session.session()); |
| } |
| |
| SessionWrapper client_1 = CreateClient("view_1", std::move(v1)); |
| scenic::ViewHolder holder_2(client_1.session(), std::move(vh2), "holder_2"); |
| { |
| holder_2.SetViewProperties(k100x100x1); |
| client_1.view()->AddChild(holder_2); |
| RequestToPresent(client_1.session()); |
| } |
| |
| SessionWrapper client_2 = CreateClient("view_2", std::move(v2)); |
| |
| // Scene is now set up, send in the input. The initial input is outside the viewport and |
| // the stream should therefore not be seen by anyone. |
| { |
| RegisterInjector(client_1.view_ref(), client_2.view_ref(), |
| fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET); |
| Inject(10, 10, fuchsia::ui::pointerinjector::EventPhase::ADD); // Outside viewport. |
| // Rest inside viewport, but should not be delivered. |
| Inject(5, 0, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(5, 5, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(0, 5, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| RunLoopUntilIdle(); |
| |
| // Neither client should receive any events. |
| EXPECT_TRUE(client_1.events().empty()); |
| EXPECT_TRUE(client_2.events().empty()); |
| } |
| |
| // Send in input starting in the viewport and moving outside. |
| { |
| Inject(1, 1, fuchsia::ui::pointerinjector::EventPhase::ADD); // Inside viewport. |
| // Rest outside viewport, but should still be delivered. |
| Inject(50, 0, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(50, 50, fuchsia::ui::pointerinjector::EventPhase::CHANGE); |
| Inject(0, 50, fuchsia::ui::pointerinjector::EventPhase::REMOVE); |
| RunLoopUntilIdle(); |
| |
| // Context should still not receive any events. |
| EXPECT_TRUE(client_1.events().empty()); |
| |
| // But target should receive all of them. |
| const std::vector<fuchsia::ui::input::InputEvent>& events = client_2.events(); |
| ASSERT_EQ(events.size(), 7u); |
| EXPECT_TRUE(PointerMatches(events[0].pointer(), 1u, PointerEventPhase::ADD, 1.0, 1.0)); |
| EXPECT_TRUE(events[1].is_focus()); |
| EXPECT_TRUE(PointerMatches(events[2].pointer(), 1u, PointerEventPhase::DOWN, 1.0, 1.0)); |
| EXPECT_TRUE(PointerMatches(events[3].pointer(), 1u, PointerEventPhase::MOVE, 50.0, 0.0)); |
| EXPECT_TRUE(PointerMatches(events[4].pointer(), 1u, PointerEventPhase::MOVE, 50.0, 50.0)); |
| EXPECT_TRUE(PointerMatches(events[5].pointer(), 1u, PointerEventPhase::UP, 0.0, 50.0)); |
| EXPECT_TRUE(PointerMatches(events[6].pointer(), 1u, PointerEventPhase::REMOVE, 0.0, 50.0)); |
| } |
| } |
| |
| } // namespace |
| } // namespace lib_ui_input_tests |