| // Copyright 2019 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/ui/scenic/cpp/session.h> |
| #include <lib/ui/scenic/cpp/view_ref_pair.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| #include <zircon/status.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/ui/scenic/lib/input/input_system.h" |
| #include "src/ui/scenic/lib/input/tests/util.h" |
| #include "src/ui/scenic/lib/utils/helpers.h" |
| |
| // This tests the functionality of the pointer capture API. |
| // |
| // The geometry of the display and layer are constrained to a 5x5 square. |
| // |
| // Input should always be delivered to the correct session, as well as the listener. In view-local |
| // coordinates. |
| // |
| // NOTE: This test is carefully constructed to avoid Vulkan functionality. |
| |
| namespace lib_ui_input_tests { |
| namespace { |
| |
| using fuchsia::ui::input::InputEvent; |
| using fuchsia::ui::input::PointerEventPhase; |
| using fuchsia::ui::input::PointerEventType; |
| using fuchsia::ui::views::ViewRef; |
| |
| // Class fixture for TEST_F. Sets up a 5x5 "display" for GfxSystem. |
| class PointerCaptureTest : public InputSystemTest { |
| public: |
| struct Listener : public fuchsia::ui::input::PointerCaptureListener { |
| Listener() : binding_(this) {} |
| ~Listener() { binding_.Close(ZX_OK); } |
| // |fuchsia::ui::input::PointerCaptureListener| |
| void OnPointerEvent(fuchsia::ui::input::PointerEvent event, |
| OnPointerEventCallback callback) override { |
| events_.push_back(std::move(event)); |
| callback(); |
| } |
| |
| fidl::Binding<fuchsia::ui::input::PointerCaptureListener> binding_; |
| std::vector<fuchsia::ui::input::PointerEvent> events_; |
| }; |
| |
| class ListenerSessionWrapper : public SessionWrapper { |
| public: |
| ListenerSessionWrapper(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {} |
| |
| void Register(scenic_impl::input::InputSystem* pointer_capture_registry, ViewRef view_ref, |
| fit::function<void()> run_loop_until_idle) { |
| listener_.binding_.set_error_handler([](zx_status_t err) { |
| FX_LOGS(ERROR) << "Binding Error: " << zx_status_get_string(err); |
| }); |
| |
| bool register_returned = false; |
| pointer_capture_registry->RegisterListener(listener_.binding_.NewBinding(), |
| std::move(view_ref), |
| [this, ®ister_returned](bool success) { |
| register_returned = true; |
| register_successful_ = success; |
| }); |
| |
| run_loop_until_idle(); |
| ASSERT_TRUE(register_returned); |
| } |
| |
| Listener listener_; |
| bool register_successful_ = false; |
| }; |
| |
| protected: |
| uint32_t test_display_width_px() const override { return 9; } |
| uint32_t test_display_height_px() const override { return 9; } |
| |
| std::unique_ptr<ListenerSessionWrapper> CreatePointerCaptureListener( |
| const std::string& name, fuchsia::ui::views::ViewToken view_token) { |
| auto listener_wrapper = std::make_unique<ListenerSessionWrapper>(scenic()); |
| auto pair = scenic::ViewRefPair::New(); |
| ViewRef view_ref_clone1, view_ref_clone2; |
| pair.view_ref.Clone(&view_ref_clone1); |
| pair.view_ref.Clone(&view_ref_clone2); |
| listener_wrapper->SetViewRef(std::move(view_ref_clone1)); |
| scenic::View view(listener_wrapper->session(), std::move(view_token), |
| std::move(pair.control_ref), std::move(pair.view_ref), name); |
| SetUpTestView(&view); |
| listener_wrapper->Register(input_system(), std::move(view_ref_clone2), |
| [this] { RunLoopUntilIdle(); }); |
| return listener_wrapper; |
| } |
| }; |
| |
| TEST_F(PointerCaptureTest, SingleRegisterAttempt_ShouldSucceed) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| |
| std::unique_ptr<ListenerSessionWrapper> client = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| EXPECT_TRUE(client->register_successful_); |
| } |
| |
| TEST_F(PointerCaptureTest, SecondRegisterAttempt_ShouldFail) { |
| auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New(); |
| auto [view_token2, view_holder_token2] = scenic::ViewTokenPair::New(); |
| |
| std::unique_ptr<ListenerSessionWrapper> client1 = |
| CreatePointerCaptureListener("view1", std::move(view_token1)); |
| std::unique_ptr<ListenerSessionWrapper> client2 = |
| CreatePointerCaptureListener("view2", std::move(view_token2)); |
| EXPECT_FALSE(client2->register_successful_); |
| } |
| |
| TEST_F(PointerCaptureTest, RegisterAttemptAfterDisconnect_ShouldSucceed) { |
| auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New(); |
| auto [view_token2, view_holder_token2] = scenic::ViewTokenPair::New(); |
| |
| { |
| // Initial registration. |
| std::unique_ptr<ListenerSessionWrapper> client = |
| CreatePointerCaptureListener("view", std::move(view_token1)); |
| } // Disconnect when out of scope. |
| { |
| // Re-register. |
| std::unique_ptr<ListenerSessionWrapper> client = |
| CreatePointerCaptureListener("view", std::move(view_token2)); |
| EXPECT_TRUE(client->register_successful_); |
| } |
| } |
| |
| // Sets up a scene with a single view, which listens to the pointer capture protocol. The test then |
| // checks that events (except for focus) are delivered on both channels. |
| TEST_F(PointerCaptureTest, IfNoOtherView_ThenListenerShouldGetAllInput) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder holder(session, std::move(view_holder_token), "view holder"); |
| |
| holder.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(holder); |
| RequestToPresent(session); |
| } |
| |
| std::unique_ptr<ListenerSessionWrapper> client = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| |
| // Scene is now set up, send in the input. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(2, 2)); |
| RequestToPresent(session); |
| } |
| |
| // Regular input path. |
| EXPECT_EQ(client->events().size(), 2u); |
| EXPECT_TRUE(client->events()[0].is_pointer()); |
| EXPECT_TRUE(client->events()[1].is_focus()); |
| |
| // Pointer capture listener path. |
| EXPECT_EQ(client->listener_.events_.size(), 1u); |
| } |
| |
| // Sets up a scene with two views that can receive input. One view is positioned to receive |
| // all input through the normal path. The other is moved offscreen and registered to receive input |
| // through the pointer capture. This test checks that the latter client only gets input on the |
| // capture path. |
| TEST_F(PointerCaptureTest, IfAnotherViewGetsInput_ListenerShouldOnlyGetCapturedInput) { |
| auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New(); |
| auto [view_token2, view_holder_token2] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder holder_1(session, std::move(view_holder_token1), "holder_1"), |
| holder_2(session, std::move(view_holder_token2), "holder_2"); |
| |
| holder_1.SetViewProperties(k5x5x1); |
| holder_2.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(holder_1); |
| root_resources.scene.AddChild(holder_2); |
| |
| // Translate capture listener client entirely off screen. |
| holder_2.SetTranslation(test_display_width_px(), test_display_height_px(), 0); |
| |
| RequestToPresent(session); |
| } |
| |
| SessionWrapper regularClient = CreateClient("view", std::move(view_token1)); |
| std::unique_ptr<ListenerSessionWrapper> pointerCaptureClient = |
| CreatePointerCaptureListener("view", std::move(view_token2)); |
| |
| // Scene is now set up, send in the input. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(2, 2)); |
| RequestToPresent(session); |
| } |
| |
| EXPECT_EQ(regularClient.events().size(), 2u); |
| EXPECT_TRUE(regularClient.events()[0].is_pointer()); |
| EXPECT_TRUE(regularClient.events()[1].is_focus()); |
| EXPECT_TRUE(pointerCaptureClient->events().empty()); |
| EXPECT_EQ(pointerCaptureClient->listener_.events_.size(), 1u); |
| } |
| |
| TEST_F(PointerCaptureTest, WhenListenerDisconnects_OtherClientsShouldStillWork) { |
| auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New(); |
| auto [view_token2, view_holder_token2] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| scenic::Session* const session = root_session.session(); |
| |
| { |
| scenic::ViewHolder holder_1(session, std::move(view_holder_token1), "holder_1"), |
| holder_2(session, std::move(view_holder_token2), "holder_2"); |
| |
| holder_1.SetViewProperties(k5x5x1); |
| holder_2.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(holder_1); |
| root_resources.scene.AddChild(holder_2); |
| |
| // Translate capture client so it doesn't get input. |
| holder_2.SetTranslation(test_display_width_px(), test_display_height_px(), 0); |
| |
| RequestToPresent(session); |
| } |
| |
| SessionWrapper regularClient = CreateClient("view", std::move(view_token1)); |
| { |
| std::unique_ptr<ListenerSessionWrapper> pointerCaptureClient = |
| CreatePointerCaptureListener("view", std::move(view_token2)); |
| |
| // Scene is now set up, send in the input. |
| { |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(4, 4)); |
| |
| RequestToPresent(session); |
| } |
| |
| EXPECT_EQ(regularClient.events().size(), 2u); |
| EXPECT_TRUE(regularClient.events()[0].is_pointer()); |
| EXPECT_TRUE(regularClient.events()[1].is_focus()); |
| EXPECT_EQ(pointerCaptureClient->listener_.events_.size(), 1u); |
| } // pointerCaptureClient goes out of scope |
| |
| // Get ready for new input. |
| regularClient.events().clear(); |
| |
| // Send more input |
| { |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 2, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(2, 2)); |
| RequestToPresent(session); |
| } |
| |
| EXPECT_EQ(regularClient.events().size(), 1u); |
| } |
| |
| // Sets up a scene with a single view capturing input both throught he normal channel and the |
| // pointer capture. Then checks that the values in both channels match. |
| TEST_F(PointerCaptureTest, CapturedInputCoordinates_ShouldMatch_RegularInputCoordinates) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder holder(session, std::move(view_holder_token), "view holder"); |
| |
| holder.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(holder); |
| RequestToPresent(session); |
| } |
| |
| std::unique_ptr<ListenerSessionWrapper> client = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| |
| // Scene is now set up, send in the input. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(2, 2)); |
| session->Enqueue(pointer.Down(3, 6)); |
| RequestToPresent(session); |
| } |
| |
| // Verify client gets all expected touch events through both channels. |
| { |
| const std::vector<InputEvent>& events = client->events(); |
| |
| ASSERT_EQ(events.size(), 3u); |
| |
| EXPECT_TRUE(events[0].is_pointer()); |
| EXPECT_TRUE(PointerMatches(events[0].pointer(), 1u, PointerEventPhase::ADD, 2, 2)); |
| |
| EXPECT_TRUE(events[1].is_focus()); |
| EXPECT_TRUE(events[1].focus().focused); |
| |
| EXPECT_TRUE(events[2].is_pointer()); |
| EXPECT_TRUE(PointerMatches(events[2].pointer(), 1u, PointerEventPhase::DOWN, 3, 6)); |
| } |
| |
| { |
| const std::vector<fuchsia::ui::input::PointerEvent>& events = client->listener_.events_; |
| ASSERT_EQ(events.size(), 2u); |
| // View covers display exactly, so view coordinates match display coordinates. |
| EXPECT_TRUE(PointerMatches(events[0], 1u, PointerEventPhase::ADD, 2, 2)); |
| EXPECT_TRUE(PointerMatches(events[1], 1u, PointerEventPhase::DOWN, 3, 6)); |
| } |
| } |
| |
| // Sets up a scene and creates a view for capturing input events, but never attaches it to the |
| // scene. Checks that no events are captured. |
| TEST_F(PointerCaptureTest, IfViewUnattached_ListenerShouldGetNoInput) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder holder(session, std::move(view_holder_token), "view holder"); |
| RequestToPresent(session); |
| } |
| |
| std::unique_ptr<ListenerSessionWrapper> client = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| |
| // Scene is now set up, send in the input. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(2, 2)); |
| RequestToPresent(session); |
| } |
| |
| EXPECT_TRUE(client->listener_.events_.empty()); |
| } |
| |
| // Sets up a scene, attaches and then detaches a view for capturing input events. |
| // Checks that no events are captured. |
| TEST_F(PointerCaptureTest, IfViewDetached_ListenerShouldGetNoInput) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder holder(session, std::move(view_holder_token), "view holder"); |
| |
| holder.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(holder); |
| RequestToPresent(session); |
| holder.Detach(); |
| RequestToPresent(session); |
| } |
| |
| std::unique_ptr<ListenerSessionWrapper> client = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| |
| // Scene is now set up, send in the input. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(2, 2)); |
| RequestToPresent(session); |
| } |
| |
| EXPECT_TRUE(client->listener_.events_.empty()); |
| } |
| |
| // In this test we set up a view, apply a transform to the view holder node, and then send pointer |
| // events to confirm that the coordinates received by the listener are correctly transformed. |
| // |
| // Below are ASCII diagrams showing the scene setup. |
| // Note that the notated X,Y coordinate system is the screen coordinate system. The view's |
| // coordinate system has its origin at corner '1'. |
| // |
| // Scene pre-transformation (1,2,3,4 denote the corners of the view): |
| // Note that the view's coordinate system is the same as the screen coordinate system. |
| // 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 - 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 |
| |
| TEST_F(PointerCaptureTest, TransformedListenerView_ShouldGetTransformedInput) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder view_holder(session, std::move(view_holder_token), "view_holder"); |
| |
| view_holder.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(view_holder); |
| |
| // Scale, rotate and translate capture listener client. |
| // Scale X by 2 and Y by 3. |
| view_holder.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)); |
| view_holder.SetRotation(rotation_quaternion.x, rotation_quaternion.y, rotation_quaternion.z, |
| rotation_quaternion.w); |
| // Translate by 1 in the X direction. |
| view_holder.SetTranslation(1, 0, 0); |
| |
| RequestToPresent(session); |
| } |
| |
| std::unique_ptr<ListenerSessionWrapper> pointerCaptureClient = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| |
| // Scene is now set up, send in the input. One event for where each corner of the view was |
| // pre-transformation. Injected events are in the screen coordinate space. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(0.5, 0.5)); |
| session->Enqueue(pointer.Down(5, 0)); |
| session->Enqueue(pointer.Move(5, 5)); |
| session->Enqueue(pointer.Up(0, 5)); |
| RequestToPresent(session); |
| } |
| |
| { // Received events should be in the coordinate space of the view. |
| const std::vector<fuchsia::ui::input::PointerEvent>& events = |
| pointerCaptureClient->listener_.events_; |
| ASSERT_EQ(events.size(), 4u); |
| |
| // Verify capture client gets properly transformed input coordinates. |
| EXPECT_TRUE(PointerMatches(events[0], 1u, PointerEventPhase::ADD, 0.5 / 2.0, 0.5 / 3.0)); |
| EXPECT_TRUE(PointerMatches(events[1], 1u, PointerEventPhase::DOWN, 0.0 / 2.0, -4.0 / 3.0)); |
| EXPECT_TRUE(PointerMatches(events[2], 1u, PointerEventPhase::MOVE, 5.0 / 2.0, -4.0 / 3.0)); |
| EXPECT_TRUE(PointerMatches(events[3], 1u, PointerEventPhase::UP, 5.0 / 2.0, 1.0 / 3.0)); |
| } |
| } |
| |
| // In this test we set up a view, apply a ClipSpaceTransform to the camera, and then send pointer |
| // events to confirm that the coordinates received by the listener are correctly transformed. |
| // |
| // Below are ASCII diagrams showing the scene setup. |
| // Note that the notated X,Y coordinate system is the screen coordinate system. The view's |
| // coordinate system has its origin at corner '1'. |
| // |
| // Scene pre-transformation (1,2,3,4 denote the corners of the view): |
| // Note that the view's coordinate system is the same as the screen coordinate system. |
| // X -> |
| // Y 1 O O O 2 - - - - |
| // | O O O O O - - - - |
| // v O O O O O - - - - |
| // O O O O O - - - - |
| // 4 O O O 3 - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // |
| // Scene after scale, before offset: |
| // 1 O O O 2 |
| // |
| // O O O O O |
| // |
| // O O O - O - O - - - - |
| // - - - - - - - - - |
| // O O O - O - O - - - - |
| // - - - - - - - - - |
| // 4 O O - O - 3 - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // |
| // Scene post-scale, post-offset: |
| // The X and Y dimensions of the view are now effectively scaled up to 10x10 |
| // (compared to the 9x9 of the screen), with origin at screen space origin. |
| // X -> |
| // Y 1D- O - M1- O - 2 |
| // | - - - - - - - - - |
| // V O O O O O |
| // - - - - - - - - - |
| // U - O - M2- O - O |
| // - - - - - - - - - |
| // O O - O - O - O |
| // - - - - - - - - - |
| // 4 - O - O - O - 3 |
| // |
| // D - Down event |
| // M1,M2 - Move events |
| // U - Up event |
| TEST_F(PointerCaptureTest, ClipSpaceTransformedListenerView_ShouldGetTransformedInput) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| static constexpr float kScaleX = 0.5f; |
| static constexpr float kScaleY = 1.5f; |
| const float width = static_cast<float>(test_display_width_px()); |
| const float height = static_cast<float>(test_display_height_px()); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder view_holder(session, std::move(view_holder_token), "view_holder"); |
| |
| view_holder.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(view_holder); |
| |
| // Set the clip space transform on the camera. |
| // The transform scales everything by 2 around the center of the screen (4.5, 4.5) and then |
| // applies offsets in Vulkan normalized device coordinates to bring the origin back |
| // to where it was originally. (Parameters are in Vulkan Normalized Device Coordinates.) |
| root_resources.camera.SetClipSpaceTransform(/*x offset*/ 1, /*y offset*/ 1, /*scale*/ 2); |
| |
| RequestToPresent(session); |
| } |
| |
| std::unique_ptr<ListenerSessionWrapper> pointerCaptureClient = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| |
| // Scene is now set up, send in the input. One event for where each corner of the view was |
| // pre-transformation. Injected events are in the screen coordinate space. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(0.5, 0.5)); |
| session->Enqueue(pointer.Down(5, 0)); |
| session->Enqueue(pointer.Move(5, 5)); |
| session->Enqueue(pointer.Up(0, 5)); |
| RequestToPresent(session); |
| } |
| |
| { // Received events should be in the coordinate space of the view. |
| const std::vector<fuchsia::ui::input::PointerEvent>& events = |
| pointerCaptureClient->listener_.events_; |
| ASSERT_EQ(events.size(), 4u); |
| |
| // Verify capture client gets properly transformed input coordinates. |
| EXPECT_TRUE(PointerMatches(events[0], 1u, PointerEventPhase::ADD, 0.25, 0.25)); |
| EXPECT_TRUE(PointerMatches(events[1], 1u, PointerEventPhase::DOWN, 2.5, 0)); |
| EXPECT_TRUE(PointerMatches(events[2], 1u, PointerEventPhase::MOVE, 2.5, 2.5)); |
| EXPECT_TRUE(PointerMatches(events[3], 1u, PointerEventPhase::UP, 0, 2.5)); |
| } |
| } |
| |
| // In this test we set up a view, apply a ClipSpaceTransform scale to the camera as well as a |
| // translation on the view holder, and confirm that the delivered coordinates are correctly |
| // transformed. |
| // |
| // Below are ASCII diagrams showing the scene setup. |
| // Note that the notated X,Y coordinate system is the screen coordinate system. The view's |
| // coordinate system has its origin at corner '1'. |
| // |
| // Scene pre-transformation (1,2,3,4 denote the corners of the view): |
| // Note that the view's coordinate system is the same as the screen coordinate system. |
| // X -> |
| // Y 1 O O O 2 - - - - |
| // | O O O O O - - - - |
| // v O O O O O - - - - |
| // O O O O O - - - - |
| // 4 O O O 3 - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // |
| // Scene after post-clip space transform, pre-translation: |
| // 1 O O O 2 |
| // |
| // O O O O O |
| // |
| // O O O - O - O - - - - |
| // - - - - - - - - - |
| // O O O - O - O - - - - |
| // - - - - - - - - - |
| // 4 O O - O - 3 - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // |
| // Scene after post-clip space transform, post-translation: |
| // Size of view is effectively 10x10, translated by (1,1). |
| // X -> |
| // Y 1 O O O 2 |
| // | |
| // V O D - O - O M1- - |
| // - - - - - - - - - |
| // O O - O - O - O - - |
| // - - - - - - - - - |
| // O O - O - O - O - - |
| // U - - - - M2- - - |
| // 4 O - O - O - 3 - - |
| // - - - - - - - - - |
| // - - - - - - - - - |
| // |
| // D - Down event |
| // M1,M2 - Move events |
| // U - Up event |
| TEST_F(PointerCaptureTest, |
| ClipSpaceAndNodeTransformedListenerView_ShouldGetCorrectlyTransformedInput) { |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [root_session, root_resources] = CreateScene(); |
| uint32_t compositor_id = root_resources.compositor.id(); |
| |
| { |
| scenic::Session* const session = root_session.session(); |
| scenic::ViewHolder view_holder(session, std::move(view_holder_token), "view_holder"); |
| |
| view_holder.SetViewProperties(k5x5x1); |
| |
| root_resources.scene.AddChild(view_holder); |
| |
| // Set the clip space transform to zoom in on the center of the screen. |
| root_resources.camera.SetClipSpaceTransform(0, 0, /*scale*/ 2); |
| // Translate view holder. |
| view_holder.SetTranslation(1, 1, 0); |
| |
| RequestToPresent(session); |
| } |
| |
| std::unique_ptr<ListenerSessionWrapper> pointerCaptureClient = |
| CreatePointerCaptureListener("view", std::move(view_token)); |
| |
| // Scene is now set up, send in the input. One event for where each corner of the view was |
| // pre-transformation. Injected events are in the screen coordinate space. |
| { |
| scenic::Session* const session = root_session.session(); |
| |
| PointerCommandGenerator pointer(compositor_id, /*device id*/ 1, |
| /*pointer id*/ 1, PointerEventType::TOUCH); |
| // Sent in as device (display) coordinates. |
| session->Enqueue(pointer.Add(0, 0)); |
| session->Enqueue(pointer.Down(5, 0)); |
| session->Enqueue(pointer.Move(5, 5)); |
| session->Enqueue(pointer.Up(0, 5)); |
| RequestToPresent(session); |
| } |
| |
| { // Received events should be in the coordinate space of the view. |
| const std::vector<fuchsia::ui::input::PointerEvent>& events = |
| pointerCaptureClient->listener_.events_; |
| ASSERT_EQ(events.size(), 4u); |
| |
| // Verify capture client gets properly transformed input coordinates. |
| EXPECT_TRUE(PointerMatches(events[0], 1u, PointerEventPhase::ADD, 2.25 - 1, 2.25 - 1)); |
| EXPECT_TRUE(PointerMatches(events[1], 1u, PointerEventPhase::DOWN, 4.75 - 1, 2.25 - 1)); |
| EXPECT_TRUE(PointerMatches(events[2], 1u, PointerEventPhase::MOVE, 4.75 - 1, 4.75 - 1)); |
| EXPECT_TRUE(PointerMatches(events[3], 1u, PointerEventPhase::UP, 2.25 - 1, 4.75 - 1)); |
| } |
| } |
| |
| } // namespace |
| } // namespace lib_ui_input_tests |