| // Copyright 2015 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/bin/root_presenter/presentation.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/trace/event.h> |
| #include <lib/ui/scenic/cpp/view_ref_pair.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| #include <zircon/status.h> |
| |
| #include <cmath> |
| |
| #include <src/lib/fostr/fidl/fuchsia/ui/input/formatting.h> |
| |
| #include "src/ui/bin/root_presenter/displays/display_configuration.h" |
| #include "src/ui/bin/root_presenter/inspect.h" |
| #include "src/ui/bin/root_presenter/safe_presenter.h" |
| |
| #include <glm/glm.hpp> |
| |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wextra-semi" |
| #include <glm/ext.hpp> |
| #pragma GCC diagnostic pop |
| |
| namespace root_presenter { |
| namespace { |
| |
| // TODO(fxbug.dev/24474): Don't hardcode Z bounds in multiple locations. |
| constexpr float kDefaultRootViewDepth = 1000; |
| |
| void ChattyReportLog(const fuchsia::ui::input::InputReport& report) { |
| static uint32_t chatty = 0; |
| if (chatty++ < ChattyMax()) { |
| FX_LOGS(INFO) << "RP-PtrReport[" << chatty << "/" << ChattyMax() << "]: " << report; |
| } |
| } |
| |
| void ChattyEventLog(const fuchsia::ui::input::InputEvent& event) { |
| static uint32_t chatty = 0; |
| if (chatty++ < ChattyMax()) { |
| FX_LOGS(INFO) << "RP-PtrEvent[" << chatty << "/" << ChattyMax() << "]: " << event; |
| } |
| } |
| |
| } // namespace |
| |
| Presentation::Presentation(inspect::Node inspect_node, sys::ComponentContext* component_context, |
| fuchsia::ui::scenic::Scenic* scenic, |
| std::unique_ptr<scenic::Session> session, |
| fuchsia::ui::views::FocuserPtr focuser, |
| int32_t display_startup_rotation_adjustment) |
| : inspect_node_(std::move(inspect_node)), |
| input_report_inspector_(inspect_node_.CreateChild("input_reports")), |
| input_event_inspector_(inspect_node_.CreateChild("input_events")), |
| root_session_(std::move(session)), |
| compositor_(root_session_.get()), |
| layer_stack_(root_session_.get()), |
| layer_(root_session_.get()), |
| renderer_(root_session_.get()), |
| scene_(root_session_.get()), |
| camera_(scene_), |
| injector_session_(scenic), |
| proxy_session_(scenic), |
| display_startup_rotation_adjustment_(display_startup_rotation_adjustment), |
| presentation_binding_(this), |
| a11y_binding_(this), |
| a11y_view_registry_binding_(this), |
| safe_presenter_root_(root_session_.get()), |
| safe_presenter_injector_(&injector_session_), |
| safe_presenter_proxy_(&proxy_session_), |
| view_focuser_(std::move(focuser)), |
| color_transform_handler_(component_context, compositor_.id(), root_session_.get(), |
| &safe_presenter_root_) { |
| FX_DCHECK(component_context); |
| component_context->outgoing()->AddPublicService(presenter_bindings_.GetHandler(this)); |
| component_context->outgoing()->AddPublicService<fuchsia::ui::accessibility::view::Registry>( |
| [this](fidl::InterfaceRequest<fuchsia::ui::accessibility::view::Registry> request) { |
| if (a11y_view_registry_binding_.is_bound()) { |
| FX_LOGS(ERROR) << "Replacing a11y binding"; |
| a11y_view_registry_binding_.Unbind(); |
| } |
| a11y_view_registry_binding_.Bind(std::move(request)); |
| }); |
| |
| compositor_.SetLayerStack(layer_stack_); |
| layer_stack_.AddLayer(layer_); |
| renderer_.SetCamera(camera_); |
| layer_.SetRenderer(renderer_); |
| |
| // Create the root view's scene. |
| // TODO(fxbug.dev/24456): we add a directional light and a point light, expecting |
| // only one of them to be active at a time. This logic is implicit in |
| // EngineRenderer, since no shadow-mode supports both directional and point |
| // lights (either one or the other). When directional light support is added |
| // to PaperRenderer, the code here will result in over-brightening, and will |
| // need to be adjusted at that time. |
| scenic::AmbientLight ambient_light(root_session_.get()); |
| scenic::DirectionalLight directional_light(root_session_.get()); |
| scenic::PointLight point_light(root_session_.get()); |
| scene_.AddLight(ambient_light); |
| scene_.AddLight(directional_light); |
| scene_.AddLight(point_light); |
| directional_light.SetDirection(1.f, 1.f, 2.f); |
| point_light.SetPosition(300.f, 300.f, -2000.f); |
| point_light.SetFalloff(0.f); |
| |
| // Explicitly set "UNSHADOWED" as the default shadow type. In addition to |
| // setting the param, this sets appropriate light intensities. |
| { |
| // When no shadows, ambient light needs to be full brightness. Otherwise, |
| // ambient needs to be dimmed so that other lights don't "overbrighten". |
| ambient_light.SetColor(1.f, 1.f, 1.f); |
| directional_light.SetColor(0.f, 0.f, 0.f); |
| point_light.SetColor(0.f, 0.f, 0.f); |
| fuchsia::ui::gfx::RendererParam param; |
| param.set_shadow_technique(fuchsia::ui::gfx::ShadowTechnique::UNSHADOWED); |
| renderer_.SetParam(std::move(param)); |
| } |
| |
| SetScenicDisplayRotation(); |
| { |
| fuchsia::ui::views::ViewRef root_view_ref, injector_view_ref; |
| { // Set up views and view holders. |
| { // Set up the root view. |
| auto [internal_view_token, internal_view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [control_ref, view_ref] = scenic::ViewRefPair::New(); |
| fidl::Clone(view_ref, &root_view_ref); |
| root_view_holder_.emplace(root_session_.get(), std::move(internal_view_holder_token), |
| "Root View Holder"); |
| root_view_.emplace(root_session_.get(), std::move(internal_view_token), |
| std::move(control_ref), std::move(view_ref), "Root View"); |
| } |
| { // Set up the injector view. |
| auto [internal_view_token, internal_view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [control_ref, view_ref] = scenic::ViewRefPair::New(); |
| fidl::Clone(view_ref, &injector_view_ref); |
| injector_view_holder_.emplace(root_session_.get(), std::move(internal_view_holder_token), |
| "Injector View Holder"); |
| injector_view_.emplace(&injector_session_, std::move(internal_view_token), |
| std::move(control_ref), std::move(view_ref), "Injector View"); |
| } |
| { // Set up the "proxy view" |
| auto [internal_view_token, internal_view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [control_ref, view_ref] = scenic::ViewRefPair::New(); |
| proxy_view_holder_.emplace(&injector_session_, std::move(internal_view_holder_token), |
| "Proxy View Holder"); |
| proxy_view_.emplace(&proxy_session_, std::move(internal_view_token), std::move(control_ref), |
| std::move(view_ref), "Proxy View"); |
| } |
| |
| // Connect it all up. |
| scene_.AddChild(root_view_holder_.value()); |
| root_view_->AddChild(injector_view_holder_.value()); |
| injector_view_->AddChild(proxy_view_holder_.value()); |
| |
| safe_presenter_root_.QueuePresent([this] { UpdateGraphState({.root_view_attached = true}); }); |
| safe_presenter_injector_.QueuePresent( |
| [this] { UpdateGraphState({.injector_view_attached = true}); }); |
| safe_presenter_proxy_.QueuePresent([] {}); |
| } |
| |
| injector_.emplace(component_context, |
| /*context=*/fidl::Clone(root_view_ref), |
| /*target=*/fidl::Clone(injector_view_ref), |
| fuchsia::ui::pointerinjector::DispatchPolicy::TOP_HIT_AND_ANCESTORS_IN_TARGET, |
| inspect_node_.CreateChild("Injector")); |
| |
| // Sets up InjectorConfigSetup for input pipeline to receive view refs and viewport updates. |
| injector_config_setup_.emplace(component_context, /*context*/ std::move(root_view_ref), |
| /*target*/ std::move(injector_view_ref)); |
| } |
| |
| scenic->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo display_info) mutable { |
| InitializeDisplayModel(std::move(display_info)); |
| safe_presenter_root_.QueuePresent([] {}); |
| safe_presenter_injector_.QueuePresent([] {}); |
| safe_presenter_proxy_.QueuePresent([] {}); |
| }); |
| |
| proxy_session_.set_error_handler([](zx_status_t status) { |
| FX_LOGS(ERROR) << "Proxy session closed unexpectedly with status: " |
| << zx_status_get_string(status); |
| }); |
| injector_session_.set_error_handler([](zx_status_t status) { |
| FX_LOGS(ERROR) << "Injector session closed unexpectedly with status: " |
| << zx_status_get_string(status); |
| }); |
| |
| proxy_session_.set_event_handler([this](std::vector<fuchsia::ui::scenic::Event> events) { |
| for (const auto& event : events) { |
| if (event.Which() != fuchsia::ui::scenic::Event::Tag::kGfx) |
| continue; |
| |
| const auto& gfx_event = event.gfx(); |
| if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewConnected) { |
| UpdateGraphState({.client_view_attached = true}); |
| } else if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewDisconnected) { |
| FX_LOGS(WARNING) << "Client View disconnected. Closing channel."; |
| proxy_view_->DetachChild(client_view_holder_.value()); |
| client_view_holder_.reset(); |
| safe_presenter_proxy_.QueuePresent([] {}); |
| UpdateGraphState({.client_view_attached = false}); |
| presentation_binding_.Unbind(); |
| } else if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene) { |
| UpdateGraphState({.proxy_view_attached = true}); |
| } else if (gfx_event.Which() == fuchsia::ui::gfx::Event::Tag::kViewDetachedFromScene) { |
| UpdateGraphState({.proxy_view_attached = false}); |
| } |
| } |
| }); |
| |
| { |
| // TODO(fxbug.dev/68206) Remove this and enable client-side FIDL errors. |
| fidl::internal::TransitoryProxyControllerClientSideErrorDisabler client_side_error_disabler_; |
| |
| component_context->svc()->Connect(magnifier_.NewRequest()); |
| magnifier_->RegisterHandler(a11y_binding_.NewBinding()); |
| a11y_binding_.set_error_handler([this](auto) { ResetClipSpaceTransform(); }); |
| } |
| |
| FX_DCHECK(root_view_holder_); |
| FX_DCHECK(root_view_); |
| FX_DCHECK(injector_view_holder_); |
| FX_DCHECK(injector_view_); |
| FX_DCHECK(proxy_view_holder_); |
| FX_DCHECK(proxy_view_); |
| FX_DCHECK(injector_); |
| } |
| |
| void Presentation::UpdateGraphState(GraphState updated_state) { |
| // Replace anything that isn't std::nullopt. |
| // No (easy) way to iterate over a struct or a tuple, so we're left with brute force updating. |
| graph_state_.root_view_attached = |
| updated_state.root_view_attached.value_or(graph_state_.root_view_attached.value()); |
| graph_state_.injector_view_attached = |
| updated_state.injector_view_attached.value_or(graph_state_.injector_view_attached.value()); |
| graph_state_.a11y_view_attached = |
| updated_state.a11y_view_attached.value_or(graph_state_.a11y_view_attached.value()); |
| graph_state_.proxy_view_attached = |
| updated_state.proxy_view_attached.value_or(graph_state_.proxy_view_attached.value()); |
| graph_state_.client_view_attached = |
| updated_state.client_view_attached.value_or(graph_state_.client_view_attached.value()); |
| |
| if (graph_state_.client_view_attached.value() && create_a11y_view_holder_callback_) { |
| create_a11y_view_holder_callback_(); |
| } else if (IsValidSceneGraph()) { |
| injector_->MarkSceneReady(); |
| |
| if (client_view_ref_.has_value()) { |
| FX_LOGS(INFO) << "Transferring focus to client"; |
| view_focuser_->RequestFocus(fidl::Clone(client_view_ref_.value()), [](auto) {}); |
| } else { |
| FX_LOGS(WARNING) << "Cannot transfer focus to client: no view ref."; |
| } |
| } |
| } |
| |
| void Presentation::InitializeDisplayModel(fuchsia::ui::gfx::DisplayInfo display_info) { |
| FX_DCHECK(!display_model_initialized_); |
| display_model_initialized_ = true; |
| |
| // Initialize display model. |
| display_configuration::InitializeModelForDisplay(display_info.width_in_px, |
| display_info.height_in_px, &display_model_); |
| |
| display_metrics_ = display_model_.GetMetrics(); |
| display_configuration::LogDisplayMetrics(display_metrics_); |
| |
| // Today, a layer needs the display's physical dimensions to render correctly. |
| layer_.SetSize(static_cast<float>(display_metrics_.width_in_px()), |
| static_cast<float>(display_metrics_.height_in_px())); |
| |
| SetViewHolderProperties(display_metrics_); |
| UpdateViewport(display_metrics_); |
| } |
| |
| void Presentation::SetViewHolderProperties(const DisplayMetrics& display_metrics) { |
| const bool is_90_degree_rotation = abs(display_startup_rotation_adjustment_ % 180) == 90; |
| |
| // Layout size |
| { |
| // Set the root view to native resolution and orientation (i.e. no rotation) of the display. |
| // This lets us delegate touch coordinate transformations to Scenic. |
| const float raw_metrics_width = static_cast<float>(display_metrics.width_in_px()); |
| const float raw_metrics_height = static_cast<float>(display_metrics.height_in_px()); |
| FX_DCHECK(root_view_holder_); |
| root_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, raw_metrics_width, |
| raw_metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); |
| } |
| |
| { // Set all other views' resolutions to pips. |
| float metrics_width = static_cast<float>(display_metrics.width_in_pp()); |
| float metrics_height = static_cast<float>(display_metrics.height_in_pp()); |
| |
| // Swap metrics on left/right tilt. |
| if (is_90_degree_rotation) { |
| std::swap(metrics_width, metrics_height); |
| } |
| |
| // Injector, a11y, proxy, and client views should all have the same dimensions. |
| FX_DCHECK(injector_view_holder_); |
| injector_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, metrics_width, |
| metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); |
| |
| FX_DCHECK(proxy_view_holder_); |
| proxy_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, metrics_width, |
| metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); |
| |
| if (client_view_holder_) { |
| client_view_holder_->SetViewProperties(0.f, 0.f, -kDefaultRootViewDepth, metrics_width, |
| metrics_height, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); |
| } |
| |
| FX_VLOGS(2) << "DisplayModel layout: " << metrics_width << ", " << metrics_height; |
| } |
| |
| // Remaining transformations are only applied to the root view's child and automatically |
| // propagated down to the client view through the scene graph. The injector view holder is always |
| // the root's child. |
| { // Scale a11y view to full device size. |
| float metrics_scale_x = display_metrics.x_scale_in_px_per_pp(); |
| float metrics_scale_y = display_metrics.y_scale_in_px_per_pp(); |
| // Swap metrics on left/right tilt. |
| if (is_90_degree_rotation) { |
| std::swap(metrics_scale_x, metrics_scale_y); |
| } |
| |
| injector_view_holder_->SetScale(metrics_scale_x, metrics_scale_y, 1.f); |
| FX_VLOGS(2) << "DisplayModel pixel scale: " << metrics_scale_x << ", " << metrics_scale_y; |
| } |
| |
| { // Rotate root's child view to match desired display orientation. |
| const glm::quat display_rotation = |
| glm::quat(glm::vec3(0, 0, glm::radians<float>(display_startup_rotation_adjustment_))); |
| injector_view_holder_->SetRotation(display_rotation.x, display_rotation.y, display_rotation.z, |
| display_rotation.w); |
| } |
| |
| { // Adjust a11y view position for rotation. |
| const float metrics_w = display_metrics.width_in_px(); |
| const float metrics_h = display_metrics.height_in_px(); |
| |
| float left_offset = 0; |
| float top_offset = 0; |
| uint32_t degrees_rotated = abs(display_startup_rotation_adjustment_ % 360); |
| switch (degrees_rotated) { |
| case 0: |
| left_offset = 0; |
| top_offset = 0; |
| break; |
| case 90: |
| left_offset = metrics_w; |
| top_offset = 0; |
| break; |
| case 180: |
| left_offset = metrics_w; |
| top_offset = metrics_h; |
| break; |
| case 270: |
| left_offset = 0; |
| top_offset = metrics_h; |
| break; |
| default: |
| FX_LOGS(ERROR) << "Unsupported rotation"; |
| break; |
| } |
| injector_view_holder_->SetTranslation(left_offset, top_offset, 0.f); |
| FX_VLOGS(2) << "DisplayModel translation: " << left_offset << ", " << top_offset; |
| } |
| } |
| |
| void Presentation::PresentView( |
| fuchsia::ui::views::ViewHolderToken view_holder_token, |
| fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) { |
| if (presentation_binding_.is_bound()) { |
| FX_LOGS(ERROR) << "Support for multiple simultaneous presentations has been removed. To " |
| "replace a view, use PresentOrReplaceView"; |
| // Reject the request. |
| presentation_request.Close(ZX_ERR_ALREADY_BOUND); |
| return; |
| } |
| |
| AttachClient(std::move(view_holder_token), std::nullopt, std::move(presentation_request)); |
| } |
| |
| void Presentation::PresentOrReplaceView( |
| fuchsia::ui::views::ViewHolderToken view_holder_token, |
| fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) { |
| AttachClient(std::move(view_holder_token), std::nullopt, std::move(presentation_request)); |
| } |
| |
| void Presentation::PresentOrReplaceView2( |
| fuchsia::ui::views::ViewHolderToken view_holder_token, fuchsia::ui::views::ViewRef view_ref, |
| fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) { |
| AttachClient(std::move(view_holder_token), std::move(view_ref), std::move(presentation_request)); |
| } |
| |
| void Presentation::AttachClient( |
| fuchsia::ui::views::ViewHolderToken view_holder_token, |
| std::optional<fuchsia::ui::views::ViewRef> view_ref, |
| fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> presentation_request) { |
| if (client_view_holder_) { |
| proxy_view_->DetachChild(client_view_holder_.value()); |
| UpdateGraphState({.client_view_attached = false}); |
| } |
| |
| client_view_holder_.emplace(&proxy_session_, std::move(view_holder_token), "Client View Holder"); |
| proxy_view_->AddChild(client_view_holder_.value()); |
| |
| if (display_model_initialized_) { |
| SetViewHolderProperties(display_metrics_); |
| } |
| |
| client_view_ref_ = std::move(view_ref); // unconditional write, replacement semantics |
| |
| presentation_binding_.Bind(std::move(presentation_request)); |
| safe_presenter_proxy_.QueuePresent([] {}); |
| } |
| |
| void Presentation::UpdateViewport(const DisplayMetrics& display_metrics) { |
| // Viewport should match the visible part of the display 1:1. To do this we need to match the |
| // ClipSpaceTransform. |
| // |
| // Since the ClipSpaceTransform is defined in Vulkan NDC with scaling, and the Viewport is defined |
| // in pixel coordinates, we need to be able to transform offsets to pixel coordinates. This is |
| // done by multiplying by half the display length and inverting the scale. |
| // |
| // Because the ClipSpaceTransform is defined with its origin in the center, and the |
| // Viewport with its origin in the top left corner, we need to add a center offset to compensate. |
| // This turns out to be as simple as half the scaled display length minus half the ClipSpace |
| // length, which equals scale - 1 in NDC. |
| // |
| // Finally, because the ClipSpaceTransform and the Viewport transform are defined in opposite |
| // directions (camera to scene vs context to viewport), all the transforms should be inverted |
| // for the Viewport transform. This means an inverted scale and negative clip offsets. |
| // |
| const float display_width = static_cast<float>(display_metrics.width_in_px()); |
| const float display_height = static_cast<float>(display_metrics.height_in_px()); |
| const float inverted_scale = 1.f / clip_scale_; |
| const float ndc_to_pixel_x = inverted_scale * display_width * 0.5f; |
| const float ndc_to_pixel_y = inverted_scale * display_height * 0.5f; |
| const float center_offset_ndc = clip_scale_ - 1.f; |
| |
| injector_->SetViewport({ |
| .width = display_width, |
| .height = display_height, |
| .scale = inverted_scale, |
| .x_offset = ndc_to_pixel_x * (center_offset_ndc - clip_offset_x_), |
| .y_offset = ndc_to_pixel_y * (center_offset_ndc - clip_offset_y_), |
| }); |
| injector_config_setup_->UpdateViewport(injector_->GetCurrentViewport()); |
| } |
| |
| void Presentation::OnDeviceAdded(ui_input::InputDeviceImpl* input_device) { |
| const uint32_t device_id = input_device->id(); |
| |
| FX_VLOGS(1) << "OnDeviceAdded: device_id=" << device_id; |
| |
| FX_DCHECK(device_states_by_id_.count(device_id) == 0); |
| std::unique_ptr<ui_input::DeviceState> state; |
| |
| if (input_device->descriptor()->sensor) { |
| ui_input::OnSensorEventCallback callback = [this](uint32_t device_id, |
| fuchsia::ui::input::InputReport event) { |
| OnSensorEvent(device_id, std::move(event)); |
| }; |
| state = std::make_unique<ui_input::DeviceState>(device_id, input_device->descriptor(), |
| std::move(callback)); |
| } else { |
| ui_input::OnEventCallback callback = [this](fuchsia::ui::input::InputEvent event) { |
| OnEvent(std::move(event)); |
| }; |
| state = std::make_unique<ui_input::DeviceState>(device_id, input_device->descriptor(), |
| std::move(callback)); |
| } |
| |
| ui_input::DeviceState* state_ptr = state.get(); |
| auto device_pair = std::make_pair(input_device, std::move(state)); |
| state_ptr->OnRegistered(); |
| device_states_by_id_.emplace(device_id, std::move(device_pair)); |
| |
| injector_->OnDeviceAdded(device_id); |
| } |
| |
| void Presentation::OnDeviceRemoved(uint32_t device_id) { |
| FX_VLOGS(1) << "OnDeviceRemoved: device_id=" << device_id; |
| |
| if (device_states_by_id_.count(device_id) != 0) { |
| device_states_by_id_[device_id].second->OnUnregistered(); |
| device_states_by_id_.erase(device_id); |
| } |
| |
| injector_->OnDeviceRemoved(device_id); |
| } |
| |
| void Presentation::OnReport(uint32_t device_id, fuchsia::ui::input::InputReport input_report) { |
| // Media buttons should be processed by MediaButtonsHandler. |
| FX_DCHECK(!input_report.media_buttons); |
| TRACE_DURATION("input", "presentation_on_report", "id", input_report.trace_id); |
| TRACE_FLOW_END("input", "report_to_presentation", input_report.trace_id); |
| |
| FX_VLOGS(2) << "OnReport device=" << device_id |
| << ", count=" << device_states_by_id_.count(device_id) << ", report=" << input_report; |
| ChattyReportLog(input_report); |
| input_report_inspector_.OnInputReport(input_report); |
| |
| if (device_states_by_id_.count(device_id) == 0) { |
| FX_VLOGS(1) << "OnReport: Unknown device " << device_id; |
| return; |
| } |
| |
| if (!display_model_initialized_) |
| return; |
| |
| ui_input::DeviceState* state = device_states_by_id_[device_id].second.get(); |
| fuchsia::math::Size size; |
| size.width = display_model_.display_info().width_in_px; |
| size.height = display_model_.display_info().height_in_px; |
| |
| TRACE_FLOW_BEGIN("input", "report_to_device_state", input_report.trace_id); |
| state->Update(std::move(input_report), size); |
| } |
| |
| void Presentation::SetClipSpaceTransform(float x, float y, float scale, |
| SetClipSpaceTransformCallback callback) { |
| clip_offset_x_ = x; |
| clip_offset_y_ = y; |
| clip_scale_ = scale; |
| camera_.SetClipSpaceTransform(clip_offset_x_, clip_offset_y_, clip_scale_); |
| // The callback is used to throttle magnification transition animations and is expected to |
| // approximate the framerate. |
| safe_presenter_root_.QueuePresent([this, callback = std::move(callback)] { |
| UpdateViewport(display_metrics_); |
| callback(); |
| }); |
| } |
| |
| void Presentation::ResetClipSpaceTransform() { |
| SetClipSpaceTransform(0, 0, 1, [] {}); |
| } |
| |
| void Presentation::OnEvent(fuchsia::ui::input::InputEvent event) { |
| TRACE_DURATION("input", "presentation_on_event"); |
| FX_VLOGS(1) << "OnEvent " << event; |
| ChattyEventLog(event); |
| input_event_inspector_.OnInputEvent(event); |
| injector_->OnEvent(event); |
| } |
| |
| void Presentation::OnSensorEvent(uint32_t device_id, fuchsia::ui::input::InputReport event) { |
| FX_VLOGS(2) << "OnSensorEvent(device_id=" << device_id << "): " << event; |
| |
| FX_DCHECK(device_states_by_id_.count(device_id) > 0); |
| FX_DCHECK(device_states_by_id_[device_id].first); |
| FX_DCHECK(device_states_by_id_[device_id].first->descriptor()); |
| FX_DCHECK(device_states_by_id_[device_id].first->descriptor()->sensor.get()); |
| |
| // No clients of sensor events at the moment. |
| } |
| |
| void Presentation::SetScenicDisplayRotation() { |
| fuchsia::ui::gfx::Command command; |
| fuchsia::ui::gfx::SetDisplayRotationCmdHACK display_rotation_cmd; |
| display_rotation_cmd.compositor_id = compositor_.id(); |
| display_rotation_cmd.rotation_degrees = display_startup_rotation_adjustment_; |
| command.set_set_display_rotation(std::move(display_rotation_cmd)); |
| root_session_->Enqueue(std::move(command)); |
| } |
| |
| void Presentation::CreateAccessibilityViewHolder( |
| fuchsia::ui::views::ViewRef a11y_view_ref, |
| fuchsia::ui::views::ViewHolderToken a11y_view_holder_token, |
| CreateAccessibilityViewHolderCallback callback) { |
| if (!graph_state_.client_view_attached.value()) { |
| // Store a callback so that CreateAccessibilityViewHolder() is always called AFTER the |
| // client view is attached. Deferring this work prevents racy ordering issues, and a11y doesn't |
| // have anything to do when there's no client view anyway. |
| create_a11y_view_holder_callback_ = |
| [this, callback = std::move(callback), a11y_view_ref = std::move(a11y_view_ref), |
| a11y_view_holder_token = std::move(a11y_view_holder_token)]() mutable { |
| FX_DCHECK(graph_state_.client_view_attached.value()); |
| CreateAccessibilityViewHolder(std::move(a11y_view_ref), std::move(a11y_view_holder_token), |
| std::move(callback)); |
| }; |
| return; |
| } |
| |
| FX_CHECK(injector_view_); |
| FX_LOGS(INFO) << "Inserting A11y View"; |
| // Detach proxy view holder from injector view. |
| injector_view_->DetachChild(proxy_view_holder_.value()); |
| |
| // Detach client view from proxy view, and delete proxy view and view holder objects (which |
| // frees the scenic resources). |
| if (client_view_holder_.has_value()) { |
| proxy_view_->DetachChild(client_view_holder_.value()); |
| } |
| proxy_view_.reset(); |
| proxy_view_holder_.reset(); |
| |
| UpdateGraphState( |
| {.a11y_view_attached = false, .proxy_view_attached = false, .client_view_attached = false}); |
| |
| // Generate new proxy view/view holder tokens, create a new proxy view. |
| // Note that we do not create a new proxy view holder here, because the a11y |
| // manager must own the new proxy view holder. |
| auto [proxy_view_token, proxy_view_holder_token] = scenic::ViewTokenPair::New(); |
| auto [control_ref, view_ref] = scenic::ViewRefPair::New(); |
| proxy_view_.emplace(&proxy_session_, std::move(proxy_view_token), std::move(control_ref), |
| std::move(view_ref), "Proxy View"); |
| |
| // Add the client view holder as a child of the new proxy view. |
| if (client_view_holder_.has_value()) { |
| proxy_view_->AddChild(client_view_holder_.value()); |
| } |
| |
| // Construct the a11y view holder. |
| proxy_view_holder_.emplace(&injector_session_, std::move(a11y_view_holder_token), |
| "A11y View Holder"); |
| |
| // Add the a11y view holder as a child of the injector view. |
| injector_view_->AddChild(proxy_view_holder_.value()); |
| |
| // Update view holder properties. Changes are presented below. |
| if (display_model_initialized_) { |
| SetViewHolderProperties(display_metrics_); |
| safe_presenter_root_.QueuePresent([] {}); |
| } |
| |
| safe_presenter_injector_.QueuePresent([this] { UpdateGraphState({.a11y_view_attached = true}); }); |
| safe_presenter_proxy_.QueuePresent([this] { UpdateGraphState({.client_view_attached = true}); }); |
| |
| create_a11y_view_holder_callback_ = nullptr; |
| callback(std::move(proxy_view_holder_token)); |
| } |
| |
| } // namespace root_presenter |