| // 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/lib/ui/base_view/base_view.h" | |
| #include <lib/fostr/fidl/fuchsia/ui/gfx/formatting.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <lib/trace/event.h> | |
| #include <lib/ui/scenic/cpp/commands.h> | |
| #include <lib/ui/scenic/cpp/view_token_pair.h> | |
| #include <zircon/status.h> | |
| namespace scenic { | |
| BaseView::BaseView(ViewContext context, const std::string& debug_name) | |
| : component_context_(context.component_context), | |
| listener_binding_(this, std::move(context.session_and_listener_request.second)), | |
| session_(std::move(context.session_and_listener_request.first)), | |
| root_node_(&session_), | |
| ime_client_(this), | |
| enable_ime_(context.enable_ime) { | |
| if (!context.view_ref_pair) { | |
| context.view_ref_pair = scenic::ViewRefPair::New(); | |
| } | |
| view_.emplace(&session_, std::move(context.view_token), | |
| std::move(context.view_ref_pair->control_ref), | |
| std::move(context.view_ref_pair->view_ref), debug_name); | |
| FX_DCHECK(view_); | |
| session_.SetDebugName(debug_name); | |
| // Listen for metrics events on our top node. | |
| root_node_.SetEventMask(fuchsia::ui::gfx::kMetricsEventMask); | |
| view_->AddChild(root_node_); | |
| if (enable_ime_) { | |
| ime_manager_ = component_context_->svc()->Connect<fuchsia::ui::input::ImeService>(); | |
| ime_.set_error_handler([](zx_status_t status) { | |
| FX_LOGS(ERROR) << "Interface error on: Input Method Editor " << zx_status_get_string(status); | |
| }); | |
| ime_manager_.set_error_handler([](zx_status_t status) { | |
| FX_LOGS(ERROR) << "Interface error on: Text Sync Service " << zx_status_get_string(status); | |
| }); | |
| } | |
| // We must immediately invalidate the scene, otherwise we wouldn't ever hook | |
| // the View up to the ViewHolder. An alternative would be to require | |
| // subclasses to call an Init() method to set up the initial connection. | |
| InvalidateScene(); | |
| } | |
| void BaseView::SetReleaseHandler(fit::function<void(zx_status_t)> callback) { | |
| listener_binding_.set_error_handler(std::move(callback)); | |
| } | |
| void BaseView::InvalidateScene(PresentCallback present_callback) { | |
| TRACE_DURATION("view", "BaseView::InvalidateScene"); | |
| if (present_callback) { | |
| callbacks_for_next_present_.push_back(std::move(present_callback)); | |
| } | |
| if (invalidate_pending_) | |
| return; | |
| invalidate_pending_ = true; | |
| // Present the scene ASAP. Pass in the last presentation time; otherwise, if | |
| // presentation_time argument is less than the previous time passed to | |
| // PresentScene, the Session will be closed. | |
| // (We cannot use the current time because the last requested presentation | |
| // time, |last_presentation_time_|, could still be in the future. This is | |
| // because Session.Present() returns after it _begins_ preparing the given | |
| // frame, not after it is presented.) | |
| if (!present_pending_) | |
| PresentScene(last_presentation_time_); | |
| } | |
| void BaseView::PresentScene() { PresentScene(last_presentation_time_); } | |
| void BaseView::OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) { | |
| TRACE_DURATION("view", "BaseView::OnScenicEvent"); | |
| for (auto& event : events) { | |
| switch (event.Which()) { | |
| case ::fuchsia::ui::scenic::Event::Tag::kGfx: | |
| switch (event.gfx().Which()) { | |
| case ::fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: { | |
| auto& evt = event.gfx().view_properties_changed(); | |
| FX_DCHECK(view_->id() == evt.view_id); | |
| auto old_props = view_properties_; | |
| view_properties_ = evt.properties; | |
| ::fuchsia::ui::gfx::BoundingBox layout_box = ViewPropertiesLayoutBox(view_properties_); | |
| logical_size_ = scenic::Max(layout_box.max - layout_box.min, 0.f); | |
| physical_size_.x = logical_size_.x * metrics_.scale_x; | |
| physical_size_.y = logical_size_.y * metrics_.scale_y; | |
| physical_size_.z = logical_size_.z * metrics_.scale_z; | |
| OnPropertiesChanged(std::move(old_props)); | |
| InvalidateScene(); | |
| break; | |
| } | |
| case fuchsia::ui::gfx::Event::Tag::kMetrics: { | |
| auto& evt = event.gfx().metrics(); | |
| if (evt.node_id == root_node_.id()) { | |
| auto old_metrics = metrics_; | |
| metrics_ = std::move(evt.metrics); | |
| physical_size_.x = logical_size_.x * metrics_.scale_x; | |
| physical_size_.y = logical_size_.y * metrics_.scale_y; | |
| physical_size_.z = logical_size_.z * metrics_.scale_z; | |
| OnMetricsChanged(std::move(old_metrics)); | |
| InvalidateScene(); | |
| } | |
| break; | |
| } | |
| default: { | |
| OnScenicEvent(std::move(event)); | |
| } | |
| } | |
| break; | |
| case ::fuchsia::ui::scenic::Event::Tag::kInput: { | |
| if (event.input().Which() == fuchsia::ui::input::InputEvent::Tag::kFocus && enable_ime_) { | |
| OnHandleFocusEvent(event.input().focus()); | |
| } | |
| OnInputEvent(std::move(event.input())); | |
| break; | |
| } | |
| case ::fuchsia::ui::scenic::Event::Tag::kUnhandled: { | |
| OnUnhandledCommand(std::move(event.unhandled())); | |
| break; | |
| } | |
| default: { | |
| OnScenicEvent(std::move(event)); | |
| } | |
| } | |
| } | |
| } | |
| void BaseView::PresentScene(zx_time_t presentation_time) { | |
| TRACE_DURATION("view", "BaseView::PresentScene"); | |
| // TODO(fxbug.dev/24406): Remove this when BaseView::PresentScene() is deprecated, | |
| // see fxbug.dev/24573. | |
| if (present_pending_) | |
| return; | |
| present_pending_ = true; | |
| // Keep track of the most recent presentation time we've passed to | |
| // Session.Present(), for use in InvalidateScene(). | |
| last_presentation_time_ = presentation_time; | |
| TRACE_FLOW_BEGIN("gfx", "Session::Present", session_present_count_); | |
| ++session_present_count_; | |
| session()->Present( | |
| presentation_time, [this, present_callbacks = std::move(callbacks_for_next_present_)]( | |
| fuchsia::images::PresentationInfo info) mutable { | |
| TRACE_DURATION("view", "BaseView::PresentationCallback"); | |
| TRACE_FLOW_END("gfx", "present_callback", info.presentation_time); | |
| FX_DCHECK(present_pending_); | |
| zx_time_t next_presentation_time = info.presentation_time + info.presentation_interval; | |
| bool present_needed = false; | |
| if (invalidate_pending_) { | |
| invalidate_pending_ = false; | |
| OnSceneInvalidated(std::move(info)); | |
| present_needed = true; | |
| } | |
| for (auto& callback : present_callbacks) { | |
| callback(info); | |
| } | |
| present_pending_ = false; | |
| if (present_needed) | |
| PresentScene(next_presentation_time); | |
| }); | |
| callbacks_for_next_present_.clear(); | |
| } | |
| // |fuchsia::ui::input::InputMethodEditorClient| | |
| void BaseView::DidUpdateState(fuchsia::ui::input::TextInputState state, | |
| std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) { | |
| if (input_event) { | |
| const fuchsia::ui::input::InputEvent& input = *input_event; | |
| fuchsia::ui::input::InputEvent input_event_copy; | |
| fidl::Clone(input, &input_event_copy); | |
| OnInputEvent(std::move(input_event_copy)); | |
| } | |
| } | |
| // |fuchsia::ui::input::InputMethodEditorClient| | |
| void BaseView::OnAction(fuchsia::ui::input::InputMethodAction action) {} | |
| bool BaseView::OnHandleFocusEvent(const fuchsia::ui::input::FocusEvent& focus) { | |
| if (focus.focused) { | |
| ActivateIme(); | |
| return true; | |
| } else if (!focus.focused) { | |
| DeactivateIme(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| void BaseView::ActivateIme() { | |
| ime_manager_->GetInputMethodEditor( | |
| fuchsia::ui::input::KeyboardType::TEXT, // keyboard type | |
| fuchsia::ui::input::InputMethodAction::DONE, // input method action | |
| fuchsia::ui::input::TextInputState{}, // initial state | |
| ime_client_.NewBinding(), // client | |
| ime_.NewRequest() // editor | |
| ); | |
| } | |
| void BaseView::DeactivateIme() { | |
| if (ime_) { | |
| ime_manager_->HideKeyboard(); | |
| ime_ = nullptr; | |
| } | |
| if (ime_client_.is_bound()) { | |
| ime_client_.Unbind(); | |
| } | |
| } | |
| } // namespace scenic |