| // Copyright 2013 The Flutter 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 "gfx_platform_view.h" |
| |
| #include "flutter/fml/make_copyable.h" |
| |
| namespace flutter_runner { |
| |
| GfxPlatformView::GfxPlatformView( |
| flutter::PlatformView::Delegate& delegate, |
| flutter::TaskRunners task_runners, |
| fuchsia::ui::views::ViewRef view_ref, |
| std::shared_ptr<flutter::ExternalViewEmbedder> external_view_embedder, |
| fuchsia::ui::input::ImeServiceHandle ime_service, |
| fuchsia::ui::input3::KeyboardHandle keyboard, |
| fuchsia::ui::pointer::TouchSourceHandle touch_source, |
| fuchsia::ui::pointer::MouseSourceHandle mouse_source, |
| fuchsia::ui::views::FocuserHandle focuser, |
| fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused, |
| fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> |
| session_listener_request, |
| fit::closure on_session_listener_error_callback, |
| OnEnableWireframe wireframe_enabled_callback, |
| OnCreateGfxView on_create_view_callback, |
| OnUpdateView on_update_view_callback, |
| OnDestroyGfxView on_destroy_view_callback, |
| OnCreateSurface on_create_surface_callback, |
| OnSemanticsNodeUpdate on_semantics_node_update_callback, |
| OnRequestAnnounce on_request_announce_callback, |
| OnShaderWarmup on_shader_warmup, |
| AwaitVsyncCallback await_vsync_callback, |
| AwaitVsyncForSecondaryCallbackCallback |
| await_vsync_for_secondary_callback_callback) |
| : PlatformView(delegate, |
| std::move(task_runners), |
| std::move(view_ref), |
| std::move(external_view_embedder), |
| std::move(ime_service), |
| std::move(keyboard), |
| std::move(touch_source), |
| std::move(mouse_source), |
| std::move(focuser), |
| std::move(view_ref_focused), |
| std::move(wireframe_enabled_callback), |
| std::move(on_update_view_callback), |
| std::move(on_create_surface_callback), |
| std::move(on_semantics_node_update_callback), |
| std::move(on_request_announce_callback), |
| std::move(on_shader_warmup), |
| std::move(await_vsync_callback), |
| std::move(await_vsync_for_secondary_callback_callback)), |
| session_listener_binding_(this, std::move(session_listener_request)), |
| session_listener_error_callback_( |
| std::move(on_session_listener_error_callback)), |
| on_create_view_callback_(std::move(on_create_view_callback)), |
| on_destroy_view_callback_(std::move(on_destroy_view_callback)), |
| weak_factory_(this) { |
| session_listener_binding_.set_error_handler([](zx_status_t status) { |
| FML_LOG(ERROR) << "Interface error on: SessionListener, status: " << status; |
| }); |
| } |
| |
| GfxPlatformView::~GfxPlatformView() = default; |
| |
| void GfxPlatformView::OnScenicError(std::string error) { |
| FML_LOG(ERROR) << "Session error: " << error; |
| session_listener_error_callback_(); |
| } |
| |
| void GfxPlatformView::OnScenicEvent( |
| std::vector<fuchsia::ui::scenic::Event> events) { |
| TRACE_EVENT0("flutter", "PlatformView::OnScenicEvent"); |
| |
| std::vector<fuchsia::ui::gfx::Event> deferred_view_events; |
| bool metrics_changed = false; |
| for (auto& event : events) { |
| switch (event.Which()) { |
| case fuchsia::ui::scenic::Event::Tag::kGfx: |
| switch (event.gfx().Which()) { |
| case fuchsia::ui::gfx::Event::Tag::kMetrics: { |
| const fuchsia::ui::gfx::Metrics& metrics = |
| event.gfx().metrics().metrics; |
| const float new_view_pixel_ratio = metrics.scale_x; |
| if (new_view_pixel_ratio <= 0.f) { |
| FML_DLOG(ERROR) |
| << "Got an invalid pixel ratio from Scenic; ignoring: " |
| << new_view_pixel_ratio; |
| break; |
| } |
| |
| // Avoid metrics update when possible -- it is computationally |
| // expensive. |
| if (view_pixel_ratio_.has_value() && |
| *view_pixel_ratio_ == new_view_pixel_ratio) { |
| FML_DLOG(ERROR) |
| << "Got an identical pixel ratio from Scenic; ignoring: " |
| << new_view_pixel_ratio; |
| break; |
| } |
| |
| view_pixel_ratio_ = new_view_pixel_ratio; |
| metrics_changed = true; |
| break; |
| } |
| case fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: { |
| const fuchsia::ui::gfx::BoundingBox& bounding_box = |
| event.gfx().view_properties_changed().properties.bounding_box; |
| const std::array<float, 2> new_view_size = { |
| std::max(bounding_box.max.x - bounding_box.min.x, 0.0f), |
| std::max(bounding_box.max.y - bounding_box.min.y, 0.0f)}; |
| if (new_view_size[0] <= 0.f || new_view_size[1] <= 0.f) { |
| FML_DLOG(ERROR) |
| << "Got an invalid view size from Scenic; ignoring: " |
| << new_view_size[0] << " " << new_view_size[1]; |
| break; |
| } |
| |
| // Avoid metrics update when possible -- it is computationally |
| // expensive. |
| if (view_logical_size_.has_value() && |
| *view_logical_size_ == new_view_size) { |
| FML_DLOG(ERROR) |
| << "Got an identical view size from Scenic; ignoring: " |
| << new_view_size[0] << " " << new_view_size[1]; |
| break; |
| } |
| |
| view_logical_size_ = new_view_size; |
| view_logical_origin_ = {bounding_box.min.x, bounding_box.min.y}; |
| metrics_changed = true; |
| break; |
| } |
| case fuchsia::ui::gfx::Event::Tag::kViewConnected: |
| if (!OnChildViewConnected( |
| event.gfx().view_connected().view_holder_id)) { |
| deferred_view_events.push_back(std::move(event.gfx())); |
| } |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewDisconnected: |
| if (!OnChildViewDisconnected( |
| event.gfx().view_disconnected().view_holder_id)) { |
| deferred_view_events.push_back(std::move(event.gfx())); |
| } |
| break; |
| case fuchsia::ui::gfx::Event::Tag::kViewStateChanged: |
| if (!OnChildViewStateChanged( |
| event.gfx().view_state_changed().view_holder_id, |
| event.gfx().view_state_changed().state.is_rendering)) { |
| deferred_view_events.push_back(std::move(event.gfx())); |
| } |
| break; |
| case fuchsia::ui::gfx::Event::Tag::Invalid: |
| FML_DCHECK(false) << "Flutter PlatformView::OnScenicEvent: Got " |
| "an invalid GFX event."; |
| break; |
| default: |
| // We don't care about some event types, so not handling them is OK. |
| break; |
| } |
| break; |
| case fuchsia::ui::scenic::Event::Tag::kInput: |
| switch (event.input().Which()) { |
| case fuchsia::ui::input::InputEvent::Tag::kFocus: |
| break; // Focus handled elsewhere. |
| case fuchsia::ui::input::InputEvent::Tag::kPointer: { |
| // Only received when TouchSource not plugged in. |
| OnHandlePointerEvent(event.input().pointer()); |
| break; |
| } |
| case fuchsia::ui::input::InputEvent::Tag::kKeyboard: { |
| // All devices should receive key events via input3.KeyboardListener |
| // instead. |
| FML_LOG(WARNING) << "Keyboard event from Scenic: ignored"; |
| break; |
| } |
| case fuchsia::ui::input::InputEvent::Tag::Invalid: { |
| FML_DCHECK(false) |
| << "Flutter PlatformView::OnScenicEvent: Got an invalid INPUT " |
| "event."; |
| } |
| } |
| break; |
| default: { |
| break; |
| } |
| } |
| } |
| |
| // If some View events went unmatched, try processing them again one more time |
| // in case they arrived out-of-order with the View creation callback. |
| if (!deferred_view_events.empty()) { |
| task_runners_.GetPlatformTaskRunner()->PostTask(fml::MakeCopyable( |
| [weak = weak_factory_.GetWeakPtr(), |
| deferred_view_events = std::move(deferred_view_events)]() { |
| if (!weak) { |
| FML_LOG(WARNING) |
| << "PlatformView already destroyed when " |
| "processing deferred view events; dropping events."; |
| return; |
| } |
| |
| for (const auto& event : deferred_view_events) { |
| switch (event.Which()) { |
| case fuchsia::ui::gfx::Event::Tag::kViewConnected: { |
| bool view_found = weak->OnChildViewConnected( |
| event.view_connected().view_holder_id); |
| FML_DCHECK(view_found); |
| break; |
| } |
| case fuchsia::ui::gfx::Event::Tag::kViewDisconnected: { |
| bool view_found = weak->OnChildViewDisconnected( |
| event.view_disconnected().view_holder_id); |
| FML_DCHECK(view_found); |
| break; |
| } |
| case fuchsia::ui::gfx::Event::Tag::kViewStateChanged: { |
| bool view_found = weak->OnChildViewStateChanged( |
| event.view_state_changed().view_holder_id, |
| event.view_state_changed().state.is_rendering); |
| FML_DCHECK(view_found); |
| break; |
| } |
| default: |
| FML_DCHECK(false) << "Flutter PlatformView::OnScenicEvent: Got " |
| "an invalid deferred GFX event."; |
| break; |
| } |
| } |
| })); |
| } |
| |
| // If any of the viewport metrics changed, inform the engine now. |
| if (view_pixel_ratio_.has_value() && view_logical_size_.has_value() && |
| metrics_changed) { |
| const float pixel_ratio = *view_pixel_ratio_; |
| const std::array<float, 2> logical_size = *view_logical_size_; |
| SetViewportMetrics({ |
| pixel_ratio, // device_pixel_ratio |
| logical_size[0] * pixel_ratio, // physical_width |
| logical_size[1] * pixel_ratio, // physical_height |
| 0.0f, // physical_padding_top |
| 0.0f, // physical_padding_right |
| 0.0f, // physical_padding_bottom |
| 0.0f, // physical_padding_left |
| 0.0f, // physical_view_inset_top |
| 0.0f, // physical_view_inset_right |
| 0.0f, // physical_view_inset_bottom |
| 0.0f, // physical_view_inset_left |
| 0.0f, // p_physical_system_gesture_inset_top |
| 0.0f, // p_physical_system_gesture_inset_right |
| 0.0f, // p_physical_system_gesture_inset_bottom |
| 0.0f, // p_physical_system_gesture_inset_left, |
| -1.0, // p_physical_touch_slop, |
| {}, // p_physical_display_features_bounds |
| {}, // p_physical_display_features_type |
| {}, // p_physical_display_features_state |
| }); |
| } |
| } |
| |
| bool GfxPlatformView::OnChildViewConnected(scenic::ResourceId view_holder_id) { |
| auto view_id_mapping = child_view_ids_.find(view_holder_id); |
| if (view_id_mapping == child_view_ids_.end()) { |
| return false; |
| } |
| |
| std::ostringstream out; |
| out << "{" |
| << "\"method\":\"View.viewConnected\"," |
| << "\"args\":{" |
| << " \"viewId\":" << view_id_mapping->second // ViewHolderToken handle |
| << " }" |
| << "}"; |
| auto call = out.str(); |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(call.c_str(), call.size()), nullptr); |
| DispatchPlatformMessage(std::move(message)); |
| |
| return true; |
| } |
| |
| bool GfxPlatformView::OnChildViewDisconnected( |
| scenic::ResourceId view_holder_id) { |
| auto view_id_mapping = child_view_ids_.find(view_holder_id); |
| if (view_id_mapping == child_view_ids_.end()) { |
| return false; |
| } |
| |
| std::ostringstream out; |
| out << "{" |
| << "\"method\":\"View.viewDisconnected\"," |
| << "\"args\":{" |
| << " \"viewId\":" << view_id_mapping->second // ViewHolderToken handle |
| << " }" |
| << "}"; |
| auto call = out.str(); |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(call.c_str(), call.size()), nullptr); |
| DispatchPlatformMessage(std::move(message)); |
| |
| return true; |
| } |
| |
| bool GfxPlatformView::OnChildViewStateChanged(scenic::ResourceId view_holder_id, |
| bool is_rendering) { |
| auto view_id_mapping = child_view_ids_.find(view_holder_id); |
| if (view_id_mapping == child_view_ids_.end()) { |
| return false; |
| } |
| |
| const std::string is_rendering_str = is_rendering ? "true" : "false"; |
| std::ostringstream out; |
| out << "{" |
| << "\"method\":\"View.viewStateChanged\"," |
| << "\"args\":{" |
| << " \"viewId\":" << view_id_mapping->second << "," // ViewHolderToken |
| << " \"is_rendering\":" << is_rendering_str << "," // IsViewRendering |
| << " \"state\":" << is_rendering_str // IsViewRendering |
| << " }" |
| << "}"; |
| auto call = out.str(); |
| |
| std::unique_ptr<flutter::PlatformMessage> message = |
| std::make_unique<flutter::PlatformMessage>( |
| "flutter/platform_views", |
| fml::MallocMapping::Copy(call.c_str(), call.size()), nullptr); |
| DispatchPlatformMessage(std::move(message)); |
| |
| return true; |
| } |
| |
| void GfxPlatformView::OnCreateView(ViewCallback on_view_created, |
| int64_t view_id_raw, |
| bool hit_testable, |
| bool focusable) { |
| auto on_view_bound = |
| [weak = weak_factory_.GetWeakPtr(), |
| platform_task_runner = task_runners_.GetPlatformTaskRunner(), |
| view_id = view_id_raw](scenic::ResourceId resource_id) { |
| platform_task_runner->PostTask([weak, view_id, resource_id]() { |
| if (!weak) { |
| FML_LOG(WARNING) |
| << "ViewHolder bound to PlatformView after PlatformView was " |
| "destroyed; ignoring."; |
| return; |
| } |
| |
| FML_DCHECK(weak->child_view_ids_.count(resource_id) == 0); |
| weak->child_view_ids_[resource_id] = view_id; |
| }); |
| }; |
| on_create_view_callback_(view_id_raw, std::move(on_view_created), |
| std::move(on_view_bound), hit_testable, focusable); |
| } |
| |
| void GfxPlatformView::OnDisposeView(int64_t view_id_raw) { |
| auto on_view_unbound = |
| [weak = weak_factory_.GetWeakPtr(), |
| platform_task_runner = task_runners_.GetPlatformTaskRunner()]( |
| scenic::ResourceId resource_id) { |
| platform_task_runner->PostTask([weak, resource_id]() { |
| if (!weak) { |
| FML_LOG(WARNING) |
| << "ViewHolder unbound from PlatformView after PlatformView" |
| "was destroyed; ignoring."; |
| return; |
| } |
| |
| FML_DCHECK(weak->child_view_ids_.count(resource_id) == 1); |
| weak->child_view_ids_.erase(resource_id); |
| }); |
| }; |
| on_destroy_view_callback_(view_id_raw, std::move(on_view_unbound)); |
| } |
| |
| } // namespace flutter_runner |