blob: aaec61539ce93ccd78e6aadc6be5c8ac2becf4ac [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/public/cpp/application/application_impl.h"
#include "mojo/skia/ganesh_surface.h"
#include "services/keyboard_native/clip_animation.h"
#include "services/keyboard_native/keyboard_service_impl.h"
#include "skia/ext/refptr.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkTypeface.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),
view_(nullptr),
key_layout_(),
needs_draw_(false),
frame_pending_(false),
weak_factory_(this) {
key_layout_.SetTextCallback(
base::Bind(&ViewObserverDelegate::OnText, weak_factory_.GetWeakPtr()));
key_layout_.SetDeleteCallback(
base::Bind(&ViewObserverDelegate::OnDelete, weak_factory_.GetWeakPtr()));
}
ViewObserverDelegate::~ViewObserverDelegate() {
}
void ViewObserverDelegate::SetKeyboardServiceImpl(
KeyboardServiceImpl* keyboard_service_impl) {
keyboard_service_impl_ = keyboard_service_impl;
}
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);
gr_context_.reset(new mojo::GaneshContext(gl_context_));
texture_uploader_.reset(new mojo::TextureUploader(this, shell, gl_context_));
IssueDraw();
}
void ViewObserverDelegate::OnText(const std::string& text) {
keyboard_service_impl_->OnKey(text.c_str());
}
void ViewObserverDelegate::OnDelete() {
keyboard_service_impl_->OnDelete();
}
void ViewObserverDelegate::UpdateState(int32 pointer_id,
int action,
const gfx::Point& touch_point) {
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::EVENT_TYPE_POINTER_UP;
}
if (action == mojo::EVENT_TYPE_POINTER_UP ||
action == mojo::EVENT_TYPE_POINTER_DOWN) {
animations_.push_back(make_scoped_ptr(new MaterialSplashAnimation(
current_ticks, kAnimationDurationMs, touch_point)));
}
}
// mojo::TextureUploader::Client implementation.
void ViewObserverDelegate::OnSurfaceIdAvailable(mojo::SurfaceIdPtr surface_id) {
view_->SetSurfaceId(surface_id.Pass());
}
void ViewObserverDelegate::DrawKeysToCanvas(const mojo::Size& size,
SkCanvas* canvas) {
key_layout_.SetSize(size);
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());
mojo::GaneshSurface surface(
gr_context_.get(),
make_scoped_ptr(new mojo::GLTexture(gl_context_, size)));
gr_context_.get()->gr()->resetContext(kTextureBinding_GrGLBackendState);
SkCanvas* canvas = surface.canvas();
// 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(size, canvas);
DrawAnimations(canvas, current_ticks);
DrawFloatingKey(canvas, size);
canvas->flush();
texture_uploader_->Upload(surface.TakeTexture());
}
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,
const mojo::Size& size) {
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 row_height = static_cast<float>(size.height) / 4.0f;
float floating_key_width = static_cast<float>(size.width) / 7.0f;
float left =
pointer_state.second->last_point.x() - (floating_key_width / 2);
float top = pointer_state.second->last_point.y() - (1.5f * row_height);
SkRect rect = SkRect::MakeLTRB(left, top, left + floating_key_width,
top + row_height);
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(row_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, row_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) {
// Upon resize, kick off a clip animation centered on our new bounds with a
// radius equal to the length from a corner of to the center of the new
// bounds.
base::TimeTicks current_ticks = base::TimeTicks::Now();
gfx::Point center(new_bounds.width / 2.0f, new_bounds.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::Point point(event->pointer_data->x, event->pointer_data->y);
if (event->action == mojo::EVENT_TYPE_POINTER_UP) {
key_layout_.OnTouchUp(point);
}
if (event->action == mojo::EVENT_TYPE_POINTER_DOWN ||
event->action == mojo::EVENT_TYPE_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::EVENT_TYPE_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::Point();
pointer_state->last_point_valid = false;
active_pointer_state_[event->pointer_data->pointer_id] =
make_scoped_ptr(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