blob: 2d4a17cfa246bb2a05bfc14ef5b05ad93322b17a [file] [log] [blame]
// Copyright 2018 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 "src/ui/scenic/lib/input/input_system.h"
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/fostr/fidl/fuchsia/ui/input/accessibility/formatting.h>
#include <lib/fostr/fidl/fuchsia/ui/input/formatting.h>
#include <lib/fostr/fidl/fuchsia/ui/pointerinjector/formatting.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include "src/ui/lib/glm_workaround/glm_workaround.h"
#include "src/ui/scenic/lib/gfx/resources/compositor/layer.h"
#include "src/ui/scenic/lib/gfx/resources/compositor/layer_stack.h"
#include "src/ui/scenic/lib/input/constants.h"
#include "src/ui/scenic/lib/input/helper.h"
#include "src/ui/scenic/lib/input/internal_pointer_event.h"
#include "src/ui/scenic/lib/utils/helpers.h"
namespace scenic_impl {
namespace input {
using AccessibilityPointerEvent = fuchsia::ui::input::accessibility::PointerEvent;
using fuchsia::ui::input::InputEvent;
using fuchsia::ui::input::PointerEvent;
using fuchsia::ui::input::PointerEventType;
namespace {
bool IsOutsideViewport(const Viewport& viewport, const glm::vec2& position_in_viewport) {
FX_DCHECK(!std::isunordered(position_in_viewport.x, viewport.extents.min.x) &&
!std::isunordered(position_in_viewport.x, viewport.extents.max.x) &&
!std::isunordered(position_in_viewport.y, viewport.extents.min.y) &&
!std::isunordered(position_in_viewport.y, viewport.extents.max.y));
return position_in_viewport.x < viewport.extents.min.x ||
position_in_viewport.y < viewport.extents.min.y ||
position_in_viewport.x > viewport.extents.max.x ||
position_in_viewport.y > viewport.extents.max.y;
}
// Helper function to build an AccessibilityPointerEvent when there is a
// registered accessibility listener.
AccessibilityPointerEvent BuildAccessibilityPointerEvent(const InternalPointerEvent& internal_event,
const glm::vec2& ndc_point,
const glm::vec2& local_point,
uint64_t viewref_koid) {
AccessibilityPointerEvent event;
event.set_event_time(internal_event.timestamp);
event.set_device_id(internal_event.device_id);
event.set_pointer_id(internal_event.pointer_id);
event.set_type(fuchsia::ui::input::PointerEventType::TOUCH);
event.set_phase(InternalPhaseToGfxPhase(internal_event.phase));
event.set_ndc_point({ndc_point.x, ndc_point.y});
event.set_viewref_koid(viewref_koid);
if (viewref_koid != ZX_KOID_INVALID) {
event.set_local_point({local_point.x, local_point.y});
}
return event;
}
// Takes an InternalPointerEvent and returns a point in (Vulkan) Normalized Device Coordinates,
// in relation to the viewport. Intended for magnification
// TODO(fxbug.dev/50549): Only here to allow the legacy a11y flow. Remove along with the legacy a11y
// code.
glm::vec2 GetViewportNDCPoint(const InternalPointerEvent& internal_event) {
const float width = internal_event.viewport.extents.max.x - internal_event.viewport.extents.min.x;
const float height =
internal_event.viewport.extents.max.y - internal_event.viewport.extents.min.y;
return {
width > 0 ? 2.f * internal_event.position_in_viewport.x / width - 1 : 0,
height > 0 ? 2.f * internal_event.position_in_viewport.y / height - 1 : 0,
};
}
void ChattyGfxLog(const fuchsia::ui::input::InputEvent& event) {
static uint32_t chatty = 0;
if (chatty++ < ChattyMax()) {
FX_LOGS(INFO) << "Ptr-GFX[" << chatty << "/" << ChattyMax() << "]: " << event;
}
}
void ChattyCaptureLog(const fuchsia::ui::input::PointerEvent& event) {
static uint32_t chatty = 0;
if (chatty++ < ChattyMax()) {
FX_LOGS(INFO) << "Ptr-Capture[" << chatty << "/" << ChattyMax() << "]: " << event;
}
}
void ChattyA11yLog(const fuchsia::ui::input::accessibility::PointerEvent& event) {
static uint32_t chatty = 0;
if (chatty++ < ChattyMax()) {
FX_LOGS(INFO) << "Ptr-A11y[" << chatty << "/" << ChattyMax() << "]: " << event;
}
}
} // namespace
const char* InputSystem::kName = "InputSystem";
InputSystem::InputSystem(SystemContext context, fxl::WeakPtr<gfx::SceneGraph> scene_graph,
fit::function<void(zx_koid_t)> request_focus)
: System(std::move(context)),
scene_graph_(scene_graph),
request_focus_(std::move(request_focus)) {
FX_CHECK(scene_graph);
a11y_pointer_event_registry_ = std::make_unique<A11yPointerEventRegistry>(
this->context()->app_context(),
/*on_register=*/
[this] {
FX_CHECK(!a11y_legacy_contender_)
<< "on_disconnect must be called before registering a new listener";
a11y_legacy_contender_ = std::make_unique<A11yLegacyContender>(
/*respond*/
[this](StreamId stream_id, GestureResponse response) {
RecordGestureDisambiguationResponse(stream_id, a11y_contender_id_, {response});
},
/*deliver_to_client*/
[this](const InternalPointerEvent& event) {
auto a11y_event = CreateAccessibilityEvent(event);
ChattyA11yLog(a11y_event);
accessibility_pointer_event_listener()->OnEvent(std::move(a11y_event));
});
FX_LOGS(INFO) << "A11yLegacyContender created.";
contenders_.emplace(a11y_contender_id_, a11y_legacy_contender_.get());
accessibility_pointer_event_listener().events().OnStreamHandled =
[this](uint32_t device_id, uint32_t pointer_id,
fuchsia::ui::input::accessibility::EventHandling handled) {
FX_DCHECK(a11y_legacy_contender_);
a11y_legacy_contender_->OnStreamHandled(pointer_id, handled);
};
},
/*on_disconnect=*/
[this] {
FX_CHECK(a11y_legacy_contender_) << "can not disconnect before registering";
// The listener disconnected. Release held events, delete the buffer.
accessibility_pointer_event_listener().events().OnStreamHandled = nullptr;
contenders_.erase(a11y_contender_id_);
a11y_legacy_contender_.reset();
FX_LOGS(INFO) << "A11yLegacyContender destroyed";
});
pointerinjector_registry_ = std::make_unique<PointerinjectorRegistry>(
this->context()->app_context(),
/*inject_touch_exclusive*/
[this](const InternalPointerEvent& event, StreamId stream_id) {
InjectTouchEventExclusive(event);
},
/*inject_touch_hit_tested*/
[this](const InternalPointerEvent& event, StreamId stream_id) {
InjectTouchEventHitTested(event, stream_id);
});
this->context()->app_context()->outgoing()->AddPublicService(
pointer_capture_registry_.GetHandler(this));
FX_LOGS(INFO) << "Scenic input system initialized.";
}
CommandDispatcherUniquePtr InputSystem::CreateCommandDispatcher(
scheduling::SessionId session_id, std::shared_ptr<EventReporter> event_reporter,
std::shared_ptr<ErrorReporter> error_reporter) {
return CommandDispatcherUniquePtr(new InputCommandDispatcher(session_id, this),
// Custom deleter.
[](CommandDispatcher* cd) { delete cd; });
}
fuchsia::ui::input::accessibility::PointerEvent InputSystem::CreateAccessibilityEvent(
const InternalPointerEvent& event) {
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
{
// Find top-hit target and send it to accessibility.
const std::vector<zx_koid_t> hits = HitTest(event, /*semantic_hit_test*/ true);
if (!hits.empty()) {
view_ref_koid = hits.front();
}
}
glm::vec2 top_hit_view_local;
if (view_ref_koid != ZX_KOID_INVALID) {
std::optional<glm::mat4> view_from_context = GetDestinationViewFromSourceViewTransform(
/*source*/ event.context, /*destination*/ view_ref_koid);
FX_DCHECK(view_from_context)
<< "could only happen if the view_tree_view_tree_snapshot_ was updated "
"between the event arriving and now";
const glm::mat4 view_from_viewport =
view_from_context.value() * event.viewport.context_from_viewport_transform;
top_hit_view_local = TransformPointerCoords(event.position_in_viewport, view_from_viewport);
}
const glm::vec2 ndc = GetViewportNDCPoint(event);
return BuildAccessibilityPointerEvent(event, ndc, top_hit_view_local, view_ref_koid);
}
ContenderId InputSystem::AddGfxLegacyContender(StreamId stream_id, zx_koid_t view_ref_koid) {
FX_DCHECK(view_ref_koid != ZX_KOID_INVALID);
const ContenderId contender_id = next_contender_id_++;
gfx_legacy_contenders_.try_emplace(
contender_id,
/*respond*/
[this, stream_id, contender_id](GestureResponse response) {
RecordGestureDisambiguationResponse(stream_id, contender_id, {response});
},
/*deliver_events_to_client*/
[this, view_ref_koid](const std::vector<InternalPointerEvent>& events) {
for (const auto& event : events) {
ReportPointerEventToPointerCaptureListener(event);
ReportPointerEventToGfxLegacyView(event, view_ref_koid,
fuchsia::ui::input::PointerEventType::TOUCH);
// Update focus if necessary.
// TODO(fxbug.dev/59858): Figure out how to handle focus with real GD clients.
if (event.phase == Phase::kAdd) {
if (view_tree_snapshot_->view_tree.count(view_ref_koid) != 0) {
if (view_tree_snapshot_->view_tree.at(view_ref_koid).is_focusable) {
request_focus_(view_ref_koid);
}
} else {
// Focus root.
request_focus_(ZX_KOID_INVALID);
}
}
}
},
/*self_destruct*/
[this, contender_id] {
contenders_.erase(contender_id);
gfx_legacy_contenders_.erase(contender_id);
});
contenders_.emplace(contender_id, &gfx_legacy_contenders_.at(contender_id));
return contender_id;
}
void InputSystem::RegisterListener(
fidl::InterfaceHandle<fuchsia::ui::input::PointerCaptureListener> listener_handle,
fuchsia::ui::views::ViewRef view_ref, RegisterListenerCallback success_callback) {
if (pointer_capture_listener_) {
// Already have a listener, decline registration.
success_callback(false);
return;
}
fuchsia::ui::input::PointerCaptureListenerPtr new_listener;
new_listener.Bind(std::move(listener_handle));
// Remove listener if the interface closes.
new_listener.set_error_handler([this](zx_status_t status) {
FX_LOGS(INFO) << "Pointer capture listener interface closed with error: "
<< zx_status_get_string(status);
pointer_capture_listener_ = std::nullopt;
});
pointer_capture_listener_ = PointerCaptureListener{.listener_ptr = std::move(new_listener),
.view_ref = std::move(view_ref)};
success_callback(true);
}
std::vector<zx_koid_t> InputSystem::HitTest(const InternalPointerEvent& event,
bool semantic_hit_test) const {
if (IsOutsideViewport(event.viewport, event.position_in_viewport)) {
return {};
}
const std::optional<glm::mat4> world_from_context_transform =
GetWorldFromViewTransform(event.context);
if (!world_from_context_transform) {
return {};
}
const auto world_from_viewport_transform =
world_from_context_transform.value() * event.viewport.context_from_viewport_transform;
const auto world_space_point =
TransformPointerCoords(event.position_in_viewport, world_from_viewport_transform);
return view_tree_snapshot_->HitTest(event.target, world_space_point, semantic_hit_test);
}
void InputSystem::DispatchPointerCommand(const fuchsia::ui::input::SendPointerInputCmd& command,
scheduling::SessionId session_id) {
TRACE_DURATION("input", "dispatch_command", "command", "PointerCmd");
if (command.pointer_event.phase == fuchsia::ui::input::PointerEventPhase::HOVER) {
FX_LOGS(WARNING) << "Injected pointer event had unexpected HOVER event.";
return;
}
if (!scene_graph_) {
FX_LOGS(INFO) << "SceneGraph wasn't set up before injecting legacy input. Dropping event.";
return;
}
// Compositor and layer stack required for dispatch.
const GlobalId compositor_id(session_id, command.compositor_id);
gfx::CompositorWeakPtr compositor = scene_graph_->GetCompositor(compositor_id);
if (!compositor) {
FX_LOGS(INFO) << "Compositor wasn't set up before injecting legacy input. Dropping event.";
return; // It's legal to race against GFX's compositor setup.
}
gfx::LayerStackPtr layer_stack = compositor->layer_stack();
if (!layer_stack) {
FX_LOGS(INFO) << "Layer stack wasn't set up before injecting legacy input. Dropping event.";
return; // It's legal to race against GFX's layer stack setup.
}
const auto layers = layer_stack->layers();
if (layers.empty()) {
FX_LOGS(INFO) << "Layer wasn't set up before injecting legacy input. Dropping event.";
return;
}
// Assume we only have one layer.
const gfx::LayerPtr first_layer = *layers.begin();
const std::optional<glm::mat4> world_from_screen_transform =
first_layer->GetWorldFromScreenTransform();
if (!world_from_screen_transform) {
FX_LOGS(INFO) << "Wasn't able to get a WorldFromScreenTransform when injecting legacy input. "
"Dropping event. Is the camera or renderer uninitialized?";
return;
}
const zx_koid_t root_koid = view_tree_snapshot_->root;
if (root_koid == ZX_KOID_INVALID) {
FX_LOGS(WARNING) << "Attempted to inject legacy input before scene setup";
return;
}
const std::optional<glm::mat4> context_from_world_transform =
GetViewFromWorldTransform(root_koid);
FX_DCHECK(context_from_world_transform);
const uint32_t screen_width = first_layer->width();
const uint32_t screen_height = first_layer->height();
if (screen_width == 0 || screen_height == 0) {
FX_LOGS(WARNING) << "Attempted to inject legacy input while Layer had 0 area";
return;
}
const glm::mat4 context_from_screen_transform =
context_from_world_transform.value() * world_from_screen_transform.value();
InternalPointerEvent internal_event = GfxPointerEventToInternalEvent(
command.pointer_event, root_koid, screen_width, screen_height, context_from_screen_transform);
switch (command.pointer_event.type) {
case PointerEventType::TOUCH: {
// Get stream id. Create one if this is a new stream.
const std::pair<uint32_t, uint32_t> stream_key{internal_event.device_id,
internal_event.pointer_id};
if (!gfx_legacy_streams_.count(stream_key)) {
if (internal_event.phase != Phase::kAdd) {
FX_LOGS(WARNING) << "Attempted to start a stream without an initial ADD.";
return;
}
gfx_legacy_streams_.emplace(stream_key, NewStreamId());
} else if (internal_event.phase == Phase::kAdd) {
FX_LOGS(WARNING) << "Attempted to ADD twice for the same stream.";
return;
}
const auto stream_id = gfx_legacy_streams_[stream_key];
// Remove from ongoing streams on stream end.
if (internal_event.phase == Phase::kRemove || internal_event.phase == Phase::kCancel) {
gfx_legacy_streams_.erase(stream_key);
}
TRACE_DURATION("input", "dispatch_command", "command", "TouchCmd");
TRACE_FLOW_END(
"input", "dispatch_event_to_scenic",
PointerTraceHACK(command.pointer_event.radius_major, command.pointer_event.radius_minor));
InjectTouchEventHitTested(internal_event, stream_id);
break;
}
case PointerEventType::MOUSE: {
TRACE_DURATION("input", "dispatch_command", "command", "MouseCmd");
if (internal_event.phase == Phase::kAdd || internal_event.phase == Phase::kRemove) {
FX_LOGS(WARNING) << "Oops, mouse device (id=" << internal_event.device_id
<< ") had an unexpected event: " << internal_event.phase;
return;
}
InjectMouseEventHitTested(internal_event);
break;
}
default:
FX_LOGS(INFO) << "Stylus not supported by legacy input injection API.";
break;
}
}
void InputSystem::InjectTouchEventExclusive(const InternalPointerEvent& event) {
ReportPointerEventToGfxLegacyView(event, event.target,
fuchsia::ui::input::PointerEventType::TOUCH);
}
// The touch state machine comprises ADD/DOWN/MOVE*/UP/REMOVE. Some notes:
// - We assume one touchscreen device, and use the device-assigned finger ID.
// - Touch ADD associates the following ADD/DOWN/MOVE*/UP/REMOVE event sequence
// with the set of clients available at that time. To enable gesture
// disambiguation, we perform parallel dispatch to all clients.
// - Touch DOWN triggers a focus change, honoring the "may receive focus" property.
// - Touch REMOVE drops the association between event stream and client.
void InputSystem::InjectTouchEventHitTested(const InternalPointerEvent& event, StreamId stream_id) {
// New stream. Collect contenders and set up a new arena.
if (event.phase == Phase::kAdd) {
std::vector<ContenderId> contenders = CollectContenders(stream_id, event);
if (!contenders.empty()) {
gesture_arenas_.emplace(stream_id, GestureArena{std::move(contenders)});
} else {
// No node was hit. Transfer focus to root.
request_focus_(ZX_KOID_INVALID);
}
}
// No arena means the contest is over and no one won.
if (!gesture_arenas_.count(stream_id)) {
return;
}
UpdateGestureContest(event, stream_id);
}
static bool IsRootOrDirectChildOfRoot(zx_koid_t koid, const view_tree::Snapshot& snapshot) {
if (snapshot.root == koid) {
return true;
}
if (snapshot.view_tree.count(koid) == 0) {
return false;
}
return snapshot.view_tree.at(koid).parent == snapshot.root;
}
std::vector<ContenderId> InputSystem::CollectContenders(StreamId stream_id,
const InternalPointerEvent& event) {
FX_DCHECK(event.phase == Phase::kAdd);
std::vector<ContenderId> contenders;
// Add an A11yLegacyContender if the injection context is the root of the ViewTree.
// TODO(fxbug.dev/50549): Remove when a11y is a native GD client.
if (a11y_legacy_contender_ && IsRootOrDirectChildOfRoot(event.context, *view_tree_snapshot_)) {
contenders.push_back(a11y_contender_id_);
}
{ // Add a GfxLegacyContender based on the closest hit.
// TODO(fxbug.dev/64206): Remove when we no longer have any legacy clients.
const std::vector<zx_koid_t> hits = HitTest(event, /*semantic_hit_test*/ false);
if (!hits.empty()) {
const zx_koid_t hit_view_koid = hits.front();
FX_VLOGS(1) << "View hit: [ViewRefKoid=" << hit_view_koid << "]";
const ContenderId contender_id = AddGfxLegacyContender(stream_id, hit_view_koid);
contenders.push_back(contender_id);
}
}
return contenders;
}
void InputSystem::UpdateGestureContest(const InternalPointerEvent& event, StreamId stream_id) {
auto arena_it = gesture_arenas_.find(stream_id);
if (arena_it == gesture_arenas_.end())
return; // Contest already ended, with no winner.
auto& arena = arena_it->second;
const bool is_end_of_stream = event.phase == Phase::kRemove || event.phase == Phase::kCancel;
arena.UpdateStream(/*length*/ 1, is_end_of_stream);
// Update remaining contenders.
// Copy the vector to avoid problems if the arena is destroyed inside of UpdateStream().
const std::vector<ContenderId> contenders = arena.contenders();
for (const auto contender_id : contenders) {
auto contender_it = contenders_.find(contender_id);
if (contender_it != contenders_.end()) {
contender_it->second->UpdateStream(stream_id, event, is_end_of_stream);
}
}
DestroyArenaIfComplete(stream_id);
}
void InputSystem::RecordGestureDisambiguationResponse(
StreamId stream_id, ContenderId contender_id, const std::vector<GestureResponse>& responses) {
auto arena_it = gesture_arenas_.find(stream_id);
if (arena_it == gesture_arenas_.end() || !arena_it->second.contains(contender_id)) {
FX_LOGS(ERROR) << "Failed to record GestureResponse";
return;
}
auto& arena = arena_it->second;
// No need to record after the contest has ended.
if (!arena.contest_has_ended()) {
// Update the arena.
const ContestResults result = arena.RecordResponse(contender_id, responses);
for (auto loser_id : result.losers) {
auto contender = contenders_.find(loser_id);
if (contender != contenders_.end()) {
contenders_.at(loser_id)->EndContest(stream_id, /*awarded_win*/ false);
}
}
if (result.winner) {
auto contender_it = contenders_.find(result.winner.value());
if (contender_it != contenders_.end()) {
contender_it->second->EndContest(stream_id, /*awarded_win*/ true);
}
FX_DCHECK(arena.contenders().size() == 1u);
}
}
DestroyArenaIfComplete(stream_id);
}
void InputSystem::DestroyArenaIfComplete(StreamId stream_id) {
const auto arena_it = gesture_arenas_.find(stream_id);
if (arena_it == gesture_arenas_.end()) {
return;
}
const auto& arena = arena_it->second;
if (arena.contenders().empty()) {
// If no one won the contest then it will appear as if nothing was hit. Transfer focus to root.
// TODO(fxbug.dev/59858): This probably needs to change when we figure out the exact semantics
// we want.
request_focus_(ZX_KOID_INVALID);
gesture_arenas_.erase(stream_id);
} else if (arena.contest_has_ended() && arena.stream_has_ended()) {
// If both the contest and the stream is over, destroy the arena.
// This branch will always be reached eventually.
gesture_arenas_.erase(stream_id);
}
}
// The mouse state machine is simpler, comprising MOVE*-DOWN/MOVE*/UP-MOVE*. Its
// behavior is similar to touch events, but with some differences.
// - There can be multiple mouse devices, so we track each device individually.
// - Mouse DOWN associates the following DOWN/MOVE*/UP event sequence with one
// particular client: the top-hit View. Mouse events aren't associated with
// gestures, so there is no parallel dispatch.
// - Mouse DOWN triggers a focus change, honoring the "may receive focus" property.
// - Mouse UP drops the association between event stream and client.
// - For an unlatched MOVE event, we perform a hit test, and send the
// top-most client this MOVE event. Focus does not change for unlatched
// MOVEs.
// - The hit test must account for the mouse cursor itself, which today is
// owned by the root presenter. The nodes associated with visible mouse
// cursors(!) do not roll up to any View (as expected), but may appear in the
// hit test; our dispatch needs to account for such behavior.
// TODO(fxbug.dev/24288): Enhance trackpad support.
void InputSystem::InjectMouseEventHitTested(const InternalPointerEvent& event) {
const uint32_t device_id = event.device_id;
const Phase pointer_phase = event.phase;
if (pointer_phase == Phase::kDown) {
// Find top-hit target and associated properties.
const std::vector<zx_koid_t> hit_views = HitTest(event, /*semantic_hit_test*/ false);
FX_VLOGS(1) << "View hits: ";
for (auto view_ref_koid : hit_views) {
FX_VLOGS(1) << "[ViewRefKoid=" << view_ref_koid << "]";
}
if (!hit_views.empty()) {
// Request that focus be transferred to the top view.
request_focus_(hit_views.front());
} else {
// The mouse event stream has no designated receiver.
// Request that focus be transferred to the root view, so that (1) the currently focused
// view becomes unfocused, and (2) the focus chain remains under control of the root view.
request_focus_(ZX_KOID_INVALID);
}
// Save target for consistent delivery of mouse events.
mouse_targets_[device_id] = hit_views;
}
if (mouse_targets_.count(device_id) > 0 && // Tracking this device, and
mouse_targets_[device_id].size() > 0) { // target view exists.
const zx_koid_t top_view_koid = mouse_targets_[device_id].front();
ReportPointerEventToGfxLegacyView(event, top_view_koid,
fuchsia::ui::input::PointerEventType::MOUSE);
}
if (pointer_phase == Phase::kUp || pointer_phase == Phase::kCancel) {
mouse_targets_.erase(device_id);
}
// Deal with unlatched MOVE events.
if (pointer_phase == Phase::kChange && mouse_targets_.count(device_id) == 0) {
// Find top-hit target and send it this move event.
const std::vector<zx_koid_t> hits = HitTest(event, /*semantic_hit_test*/ false);
if (!hits.empty()) {
const zx_koid_t top_view_koid = hits.front();
ReportPointerEventToGfxLegacyView(event, top_view_koid,
fuchsia::ui::input::PointerEventType::MOUSE);
}
}
}
// TODO(fxbug.dev/48150): Delete when we delete the PointerCapture functionality.
void InputSystem::ReportPointerEventToPointerCaptureListener(
const InternalPointerEvent& event) const {
if (!pointer_capture_listener_)
return;
const PointerCaptureListener& listener = pointer_capture_listener_.value();
const zx_koid_t view_ref_koid = utils::ExtractKoid(listener.view_ref);
std::optional<glm::mat4> view_from_context_transform = GetDestinationViewFromSourceViewTransform(
/*source*/ event.context, /*destination*/ view_ref_koid);
if (!view_from_context_transform)
return;
fuchsia::ui::input::PointerEvent gfx_event = InternalPointerEventToGfxPointerEvent(
event, view_from_context_transform.value(), fuchsia::ui::input::PointerEventType::TOUCH,
/*trace_id*/ 0);
ChattyCaptureLog(gfx_event);
// TODO(fxbug.dev/42145): Implement flow control.
listener.listener_ptr->OnPointerEvent(gfx_event, [] {});
}
void InputSystem::ReportPointerEventToGfxLegacyView(
const InternalPointerEvent& event, zx_koid_t view_ref_koid,
fuchsia::ui::input::PointerEventType type) const {
TRACE_DURATION("input", "dispatch_event_to_client", "event_type", "pointer");
if (!scene_graph_)
return;
EventReporterWeakPtr event_reporter = scene_graph_->view_tree().EventReporterOf(view_ref_koid);
if (!event_reporter)
return;
std::optional<glm::mat4> view_from_context_transform = GetDestinationViewFromSourceViewTransform(
/*source*/ event.context, /*destination*/ view_ref_koid);
if (!view_from_context_transform)
return;
const uint64_t trace_id = TRACE_NONCE();
TRACE_FLOW_BEGIN("input", "dispatch_event_to_client", trace_id);
InputEvent input_event;
input_event.set_pointer(InternalPointerEventToGfxPointerEvent(
event, view_from_context_transform.value(), type, trace_id));
FX_VLOGS(1) << "Event dispatch to view=" << view_ref_koid << ": " << input_event;
ChattyGfxLog(input_event);
event_reporter->EnqueueEvent(std::move(input_event));
}
std::optional<glm::mat4> InputSystem::GetViewFromWorldTransform(zx_koid_t view_ref_koid) const {
if (view_tree_snapshot_->view_tree.count(view_ref_koid) == 0) {
return std::nullopt;
}
return view_tree_snapshot_->view_tree.at(view_ref_koid).local_from_world_transform;
}
std::optional<glm::mat4> InputSystem::GetWorldFromViewTransform(zx_koid_t view_ref_koid) const {
const std::optional<glm::mat4> view_from_world_transform =
GetViewFromWorldTransform(view_ref_koid);
if (!view_from_world_transform.has_value()) {
return std::nullopt;
}
return glm::inverse(view_from_world_transform.value());
}
std::optional<glm::mat4> InputSystem::GetDestinationViewFromSourceViewTransform(
zx_koid_t source, zx_koid_t destination) const {
std::optional<glm::mat4> world_from_source_transform = GetWorldFromViewTransform(source);
std::optional<glm::mat4> destination_from_world_transform =
GetViewFromWorldTransform(destination);
if (!world_from_source_transform.has_value() || !destination_from_world_transform.has_value()) {
return std::nullopt;
}
return destination_from_world_transform.value() * world_from_source_transform.value();
}
} // namespace input
} // namespace scenic_impl