blob: ebdb9b03c1f9d83065b6b6c54105054e313206db [file] [log] [blame]
// Copyright 2015 The Chromium 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 <math.h>
#include "services/keyboard_native/view_observer_delegate.h"
#include "base/bind.h"
#include "base/sys_info.h"
#include "mojo/gpu/gl_texture.h"
#include "mojo/public/cpp/application/application_impl.h"
#include "mojo/public/cpp/application/connect.h"
#include "mojo/services/prediction/interfaces/prediction.mojom.h"
#include "mojo/skia/ganesh_surface.h"
#include "services/keyboard_native/clip_animation.h"
#include "services/keyboard_native/keyboard_service_impl.h"
#include "services/keyboard_native/predictor.h"
#include "skia/ext/refptr.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/shadow_value.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/vector2d.h"
namespace keyboard {
static const base::TimeDelta kAnimationDurationMs =
base::TimeDelta::FromMilliseconds(1000);
mojo::Size ToSize(const mojo::Rect& rect) {
mojo::Size size;
size.width = rect.width;
size.height = rect.height;
return size;
}
ViewObserverDelegate::ViewObserverDelegate()
: keyboard_service_impl_(nullptr),
predictor_(nullptr),
view_(nullptr),
id_namespace_(0u),
surface_id_(1u),
key_layout_(),
needs_draw_(false),
frame_pending_(false),
floating_key_height_(0.0f),
floating_key_width_(0.0f),
weak_factory_(this) {
key_layout_.SetTextCallback(
base::Bind(&ViewObserverDelegate::OnText, weak_factory_.GetWeakPtr()));
key_layout_.SetDeleteCallback(
base::Bind(&ViewObserverDelegate::OnDelete, weak_factory_.GetWeakPtr()));
key_layout_.SetSuggestTextCallback(base::Bind(
&ViewObserverDelegate::OnSuggestText, weak_factory_.GetWeakPtr()));
submit_frame_callback_ = base::Bind(&ViewObserverDelegate::OnFrameComplete,
weak_factory_.GetWeakPtr());
}
ViewObserverDelegate::~ViewObserverDelegate() {
}
void ViewObserverDelegate::SetKeyboardServiceImpl(
KeyboardServiceImpl* keyboard_service_impl) {
keyboard_service_impl_ = keyboard_service_impl;
}
void ViewObserverDelegate::SetIdNamespace(uint32_t id_namespace) {
id_namespace_ = id_namespace;
UpdateSurfaceIds();
}
void ViewObserverDelegate::UpdateSurfaceIds() {
auto surface_id = mojo::SurfaceId::New();
surface_id->id_namespace = id_namespace_;
surface_id->local = surface_id_;
view_->SetSurfaceId(surface_id.Pass());
}
void ViewObserverDelegate::OnViewCreated(mojo::View* view, mojo::Shell* shell) {
if (view_ != nullptr) {
view_->RemoveObserver(this);
}
view_ = view;
view_->AddObserver(this);
gl_context_ = mojo::GLContext::Create(shell);
mojo::ResourceReturnerPtr resource_returner;
texture_cache_.reset(new mojo::TextureCache(gl_context_, &resource_returner));
gr_context_.reset(new mojo::GaneshContext(gl_context_));
mojo::ServiceProviderPtr surfaces_service_provider;
shell->ConnectToApplication("mojo:surfaces_service",
mojo::GetProxy(&surfaces_service_provider),
nullptr);
mojo::ConnectToService(surfaces_service_provider.get(), &surface_);
surface_->SetResourceReturner(resource_returner.Pass());
surface_->CreateSurface(surface_id_);
surface_->GetIdNamespace(base::Bind(&ViewObserverDelegate::SetIdNamespace,
base::Unretained(this)));
predictor_ = new Predictor(shell);
predictor_->SetUpdateCallback(base::Bind(
&ViewObserverDelegate::OnUpdateSuggestion, weak_factory_.GetWeakPtr()));
key_layout_.SetPredictor(predictor_);
IssueDraw();
}
void ViewObserverDelegate::OnText(const std::string& text) {
keyboard_service_impl_->OnKey(text.c_str());
}
void ViewObserverDelegate::OnDelete() {
keyboard_service_impl_->OnDelete();
}
void ViewObserverDelegate::OnSuggestText(const std::string& text) {
std::string text_with_space = text + " ";
keyboard_service_impl_->OnKey(text_with_space.c_str());
}
void ViewObserverDelegate::OnUpdateSuggestion() {
IssueDraw();
}
void ViewObserverDelegate::UpdateState(int32 pointer_id,
mojo::EventType action,
const gfx::PointF& touch_point) {
// Ignore touches outside of key area.
if (!key_area_.Contains(touch_point)) {
return;
}
base::TimeTicks current_ticks = base::TimeTicks::Now();
auto it2 = active_pointer_state_.find(pointer_id);
if (it2 != active_pointer_state_.end()) {
if (it2->second->last_point_valid) {
animations_.push_back(make_scoped_ptr(
new MotionDecayAnimation(current_ticks, kAnimationDurationMs,
it2->second->last_point, touch_point)));
it2->second->last_point = touch_point;
} else {
it2->second->last_point = touch_point;
}
it2->second->last_point_valid = action != mojo::EventType::POINTER_UP;
}
if (action == mojo::EventType::POINTER_UP ||
action == mojo::EventType::POINTER_DOWN) {
animations_.push_back(make_scoped_ptr(new MaterialSplashAnimation(
current_ticks, kAnimationDurationMs, touch_point)));
}
}
void ViewObserverDelegate::DrawKeysToCanvas(const gfx::RectF& key_area,
SkCanvas* canvas) {
key_layout_.SetKeyArea(key_area);
key_layout_.Draw(canvas);
}
void ViewObserverDelegate::DrawState() {
base::TimeTicks current_ticks = base::TimeTicks::Now();
mojo::Size size = ToSize(view_->bounds());
mojo::GaneshContext::Scope scope(gr_context_.get());
scoped_ptr<mojo::TextureCache::TextureInfo> texture_info(
texture_cache_->GetTexture(size).Pass());
mojo::GaneshSurface surface(gr_context_.get(),
texture_info->Texture().Pass());
gr_context_.get()->gr()->resetContext(kTextureBinding_GrGLBackendState);
SkCanvas* canvas = surface.canvas();
canvas->clear(SK_ColorTRANSPARENT);
// If we have a clip animation it must go first as only things drawn after the
// clip is set will be clipped.
if (clip_animation_) {
if (!clip_animation_->IsDone(current_ticks)) {
clip_animation_->Draw(canvas, current_ticks);
} else {
clip_animation_.reset();
}
}
DrawKeysToCanvas(key_area_, canvas);
// clip animations to the key area.
canvas->clipRect(SkRect::MakeXYWH(key_area_.x(), key_area_.y(),
key_area_.width(), key_area_.height()));
DrawAnimations(canvas, current_ticks);
// reset clip to whatever the clip animation sets.
canvas->clipRect(SkRect::MakeXYWH(0, 0, size.width, size.height),
SkRegion::kReplace_Op);
if (clip_animation_) {
if (!clip_animation_->IsDone(current_ticks)) {
clip_animation_->Draw(canvas, current_ticks);
} else {
clip_animation_.reset();
}
}
DrawFloatingKey(canvas, floating_key_height_, floating_key_width_);
canvas->flush();
scoped_ptr<mojo::GLTexture> surface_texture(surface.TakeTexture().Pass());
mojo::FramePtr frame = mojo::TextureUploader::GetUploadFrame(
gl_context_, texture_info->ResourceId(), surface_texture);
texture_cache_->NotifyPendingResourceReturn(texture_info->ResourceId(),
surface_texture.Pass());
surface_->SubmitFrame(surface_id_, frame.Pass(), submit_frame_callback_);
}
void ViewObserverDelegate::DrawAnimations(
SkCanvas* canvas,
const base::TimeTicks& current_ticks) {
auto first_valid_animation = animations_.end();
for (auto it = animations_.begin(); it != animations_.end(); ++it) {
bool done = (*it)->IsDone(current_ticks);
if (!done) {
(*it)->Draw(canvas, current_ticks);
if (first_valid_animation == animations_.end()) {
first_valid_animation = it;
}
}
}
// Clean up 'done' animations.
animations_.erase(animations_.begin(), first_valid_animation);
}
void ViewObserverDelegate::DrawFloatingKey(SkCanvas* canvas,
float floating_key_height,
float floating_key_width) {
if (active_pointer_ids_.empty()) {
return;
}
for (auto& pointer_state : active_pointer_state_) {
if (pointer_state.second->last_key != nullptr &&
pointer_state.second->last_point_valid) {
SkPaint paint;
paint.setColor(SK_ColorYELLOW);
float left =
pointer_state.second->last_point.x() - (floating_key_width / 2);
float top =
pointer_state.second->last_point.y() - (0.45f * key_area_.height());
SkRect rect = SkRect::MakeLTRB(left, top, left + floating_key_width,
top + floating_key_height);
// Add shadow beneath the floating key.
paint.setStrokeJoin(SkPaint::kRound_Join);
int blur = 20;
float ratio_x_from_center =
(left + (floating_key_width / 2) - (key_area_.width() / 2)) /
(key_area_.width() / 2);
float ratio_y_from_center =
(top + (floating_key_height / 2) - (key_area_.height() / 2)) /
(key_area_.height() / 2);
SkColor color = SkColorSetARGB(0x80, 0, 0, 0);
std::vector<gfx::ShadowValue> shadows;
shadows.push_back(gfx::ShadowValue(
gfx::Vector2d(ratio_x_from_center * 10, ratio_y_from_center * 10),
blur, color));
skia::RefPtr<SkDrawLooper> looper = gfx::CreateShadowDrawLooper(shadows);
paint.setLooper(looper.get());
canvas->drawRect(rect, paint);
skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
SkTypeface::CreateFromName("Arial", SkTypeface::kNormal));
SkPaint text_paint;
text_paint.setTypeface(typeface.get());
text_paint.setColor(SK_ColorBLACK);
text_paint.setTextSize(floating_key_height / 1.7f);
text_paint.setAntiAlias(true);
text_paint.setTextAlign(SkPaint::kCenter_Align);
pointer_state.second->last_key->Draw(
canvas, text_paint,
gfx::RectF(left, top, floating_key_width, floating_key_height));
}
}
}
void ViewObserverDelegate::IssueDraw() {
if (!frame_pending_) {
DrawState();
frame_pending_ = true;
needs_draw_ = !animations_.empty() || clip_animation_;
} else {
needs_draw_ = true;
}
}
void ViewObserverDelegate::OnFrameComplete() {
frame_pending_ = false;
if (needs_draw_) {
IssueDraw();
}
}
// mojo::ViewObserver implementation.
void ViewObserverDelegate::OnViewBoundsChanged(mojo::View* view,
const mojo::Rect& old_bounds,
const mojo::Rect& new_bounds) {
surface_->DestroySurface(surface_id_);
surface_id_++;
surface_->CreateSurface(surface_id_);
UpdateSurfaceIds();
floating_key_height_ = static_cast<float>(new_bounds.height) / 5.0f;
floating_key_width_ = static_cast<float>(new_bounds.width) / 8.0f;
// One third of our height will be used for overlapping views behind us. Only
// the floating key will be drawn into that area. The remaining two thirds
// will be the key area.
float overlap = new_bounds.height / 3.0f;
key_area_ =
gfx::RectF(0, overlap, new_bounds.width, new_bounds.height - overlap);
// Upon resize, kick off a clip animation centered on our key area with a
// radius equal to the length from a corner of the new bounds to the center of
// the key area.
base::TimeTicks current_ticks = base::TimeTicks::Now();
gfx::PointF center(key_area_.width() / 2.0f,
overlap + (key_area_.height() / 2.0f));
float radius = sqrt(new_bounds.width / 2.0f * new_bounds.width / 2.0f +
new_bounds.height / 2.0f * new_bounds.height / 2.0f);
clip_animation_.reset(
new ClipAnimation(current_ticks, kAnimationDurationMs, center, radius));
IssueDraw();
}
void ViewObserverDelegate::OnViewInputEvent(mojo::View* view,
const mojo::EventPtr& event) {
if (event->pointer_data) {
gfx::PointF point(event->pointer_data->x, event->pointer_data->y);
if (event->action == mojo::EventType::POINTER_UP) {
key_layout_.OnTouchUp(point);
}
if (event->action == mojo::EventType::POINTER_DOWN ||
event->action == mojo::EventType::POINTER_UP) {
auto it =
std::find(active_pointer_ids_.begin(), active_pointer_ids_.end(),
event->pointer_data->pointer_id);
if (it != active_pointer_ids_.end()) {
active_pointer_ids_.erase(it);
}
auto it2 = active_pointer_state_.find(event->pointer_data->pointer_id);
if (it2 != active_pointer_state_.end()) {
active_pointer_state_.erase(it2);
}
if (event->action == mojo::EventType::POINTER_DOWN) {
active_pointer_ids_.push_back(event->pointer_data->pointer_id);
PointerState* pointer_state = new PointerState();
pointer_state->last_key = nullptr;
pointer_state->last_point = gfx::PointF();
pointer_state->last_point_valid = false;
active_pointer_state_[event->pointer_data->pointer_id].reset(
pointer_state);
}
}
auto it2 = active_pointer_state_.find(event->pointer_data->pointer_id);
if (it2 != active_pointer_state_.end()) {
active_pointer_state_[event->pointer_data->pointer_id]->last_key =
key_layout_.GetKeyAtPoint(point);
}
UpdateState(event->pointer_data->pointer_id, event->action, point);
IssueDraw();
}
}
void ViewObserverDelegate::OnViewDestroyed(mojo::View* view) {
if (view_ == view) {
view_->RemoveObserver(this);
view_ = nullptr;
gl_context_->Destroy();
}
}
} // namespace keyboard