blob: fe5d161b0576014567fe05844366e0e07a1ad601 [file] [log] [blame]
// Copyright (c) 2012 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 "ui/gfx/compositor/layer.h"
#include <algorithm>
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebContentLayer.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebExternalTextureLayer.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebFloatPoint.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebFloatRect.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSize.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSolidColorLayer.h"
#include "ui/base/animation/animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/compositor/compositor_switches.h"
#include "ui/gfx/compositor/layer_animator.h"
#include "ui/gfx/interpolated_transform.h"
#include "ui/gfx/point3.h"
namespace {
const float EPSILON = 1e-3f;
bool IsApproximateMultipleOf(float value, float base) {
float remainder = fmod(fabs(value), base);
return remainder < EPSILON || base - remainder < EPSILON;
}
const ui::Layer* GetRoot(const ui::Layer* layer) {
return layer->parent() ? GetRoot(layer->parent()) : layer;
}
} // namespace
namespace ui {
Layer::Layer()
: type_(LAYER_TEXTURED),
compositor_(NULL),
parent_(NULL),
visible_(true),
fills_bounds_opaquely_(true),
layer_updated_externally_(false),
opacity_(1.0f),
delegate_(NULL) {
CreateWebLayer();
}
Layer::Layer(LayerType type)
: type_(type),
compositor_(NULL),
parent_(NULL),
visible_(true),
fills_bounds_opaquely_(true),
layer_updated_externally_(false),
opacity_(1.0f),
delegate_(NULL) {
CreateWebLayer();
}
Layer::~Layer() {
// Destroying the animator may cause observers to use the layer (and
// indirectly the WebLayer). Destroy the animator first so that the WebLayer
// is still around.
animator_.reset();
if (compositor_)
compositor_->SetRootLayer(NULL);
if (parent_)
parent_->Remove(this);
for (size_t i = 0; i < children_.size(); ++i)
children_[i]->parent_ = NULL;
web_layer_.removeFromParent();
}
Compositor* Layer::GetCompositor() {
return GetRoot(this)->compositor_;
}
void Layer::SetCompositor(Compositor* compositor) {
// This function must only be called to set the compositor on the root layer,
// or to reset it.
DCHECK(!compositor || !compositor_);
DCHECK(!compositor || compositor->root_layer() == this);
DCHECK(!parent_);
compositor_ = compositor;
}
void Layer::Add(Layer* child) {
DCHECK(!child->compositor_);
if (child->parent_)
child->parent_->Remove(child);
child->parent_ = this;
children_.push_back(child);
web_layer_.addChild(child->web_layer_);
}
void Layer::Remove(Layer* child) {
std::vector<Layer*>::iterator i =
std::find(children_.begin(), children_.end(), child);
DCHECK(i != children_.end());
children_.erase(i);
child->parent_ = NULL;
child->web_layer_.removeFromParent();
}
void Layer::StackAtTop(Layer* child) {
if (children_.size() <= 1 || child == children_.back())
return; // Already in front.
StackAbove(child, children_.back());
}
void Layer::StackAbove(Layer* child, Layer* other) {
StackRelativeTo(child, other, true);
}
void Layer::StackAtBottom(Layer* child) {
if (children_.size() <= 1 || child == children_.front())
return; // Already on bottom.
StackBelow(child, children_.front());
}
void Layer::StackBelow(Layer* child, Layer* other) {
StackRelativeTo(child, other, false);
}
bool Layer::Contains(const Layer* other) const {
for (const Layer* parent = other; parent; parent = parent->parent()) {
if (parent == this)
return true;
}
return false;
}
void Layer::SetAnimator(LayerAnimator* animator) {
if (animator)
animator->SetDelegate(this);
animator_.reset(animator);
}
LayerAnimator* Layer::GetAnimator() {
if (!animator_.get())
SetAnimator(LayerAnimator::CreateDefaultAnimator());
return animator_.get();
}
void Layer::SetTransform(const ui::Transform& transform) {
GetAnimator()->SetTransform(transform);
}
Transform Layer::GetTargetTransform() const {
if (animator_.get() && animator_->IsAnimatingProperty(
LayerAnimationElement::TRANSFORM))
return animator_->GetTargetTransform();
return transform_;
}
void Layer::SetBounds(const gfx::Rect& bounds) {
GetAnimator()->SetBounds(bounds);
}
gfx::Rect Layer::GetTargetBounds() const {
if (animator_.get() && animator_->IsAnimatingProperty(
LayerAnimationElement::BOUNDS))
return animator_->GetTargetBounds();
return bounds_;
}
void Layer::SetOpacity(float opacity) {
GetAnimator()->SetOpacity(opacity);
}
float Layer::GetTargetOpacity() const {
if (animator_.get() && animator_->IsAnimatingProperty(
LayerAnimationElement::OPACITY))
return animator_->GetTargetOpacity();
return opacity_;
}
void Layer::SetVisible(bool visible) {
GetAnimator()->SetVisibility(visible);
}
bool Layer::GetTargetVisibility() const {
if (animator_.get() && animator_->IsAnimatingProperty(
LayerAnimationElement::VISIBILITY))
return animator_->GetTargetVisibility();
return visible_;
}
bool Layer::IsDrawn() const {
const Layer* layer = this;
while (layer && layer->visible_)
layer = layer->parent_;
return layer == NULL;
}
bool Layer::ShouldDraw() const {
return type_ != LAYER_NOT_DRAWN && GetCombinedOpacity() > 0.0f;
}
// static
void Layer::ConvertPointToLayer(const Layer* source,
const Layer* target,
gfx::Point* point) {
if (source == target)
return;
const Layer* root_layer = GetRoot(source);
CHECK_EQ(root_layer, GetRoot(target));
if (source != root_layer)
source->ConvertPointForAncestor(root_layer, point);
if (target != root_layer)
target->ConvertPointFromAncestor(root_layer, point);
}
void Layer::SetFillsBoundsOpaquely(bool fills_bounds_opaquely) {
if (fills_bounds_opaquely_ == fills_bounds_opaquely)
return;
fills_bounds_opaquely_ = fills_bounds_opaquely;
web_layer_.setOpaque(fills_bounds_opaquely);
RecomputeDebugBorderColor();
}
void Layer::SetExternalTexture(Texture* texture) {
DCHECK_EQ(type_, LAYER_TEXTURED);
layer_updated_externally_ = !!texture;
texture_ = texture;
if (web_layer_is_accelerated_ != layer_updated_externally_) {
// Switch to a different type of layer.
web_layer_.removeAllChildren();
WebKit::WebLayer new_layer;
if (layer_updated_externally_) {
WebKit::WebExternalTextureLayer texture_layer =
WebKit::WebExternalTextureLayer::create();
texture_layer.setFlipped(texture_->flipped());
new_layer = texture_layer;
} else {
new_layer = WebKit::WebContentLayer::create(this);
}
if (parent_) {
DCHECK(!parent_->web_layer_.isNull());
parent_->web_layer_.replaceChild(web_layer_, new_layer);
}
web_layer_ = new_layer;
web_layer_is_accelerated_ = layer_updated_externally_;
for (size_t i = 0; i < children_.size(); ++i) {
DCHECK(!children_[i]->web_layer_.isNull());
web_layer_.addChild(children_[i]->web_layer_);
}
web_layer_.setAnchorPoint(WebKit::WebFloatPoint(0.f, 0.f));
web_layer_.setOpaque(fills_bounds_opaquely_);
web_layer_.setOpacity(visible_ ? opacity_ : 0.f);
web_layer_.setDebugBorderWidth(show_debug_borders_ ? 2 : 0);
RecomputeTransform();
RecomputeDebugBorderColor();
}
RecomputeDrawsContentAndUVRect();
}
void Layer::SetColor(SkColor color) {
DCHECK_EQ(type_, LAYER_SOLID_COLOR);
// WebColor is equivalent to SkColor, per WebColor.h.
web_layer_.to<WebKit::WebSolidColorLayer>().setBackgroundColor(
static_cast<WebKit::WebColor>(color));
SetFillsBoundsOpaquely(SkColorGetA(color) == 0xFF);
}
bool Layer::SchedulePaint(const gfx::Rect& invalid_rect) {
if (type_ == LAYER_SOLID_COLOR || !delegate_)
return false;
damaged_region_.op(invalid_rect.x(),
invalid_rect.y(),
invalid_rect.right(),
invalid_rect.bottom(),
SkRegion::kUnion_Op);
ScheduleDraw();
return true;
}
void Layer::ScheduleDraw() {
Compositor* compositor = GetCompositor();
if (compositor)
compositor->ScheduleDraw();
}
void Layer::SendDamagedRects() {
if (delegate_ && !damaged_region_.isEmpty()) {
for (SkRegion::Iterator iter(damaged_region_);
!iter.done(); iter.next()) {
const SkIRect& damaged = iter.rect();
WebKit::WebFloatRect web_rect(
damaged.x(),
damaged.y(),
damaged.width(),
damaged.height());
if (!web_layer_is_accelerated_)
web_layer_.to<WebKit::WebContentLayer>().invalidateRect(web_rect);
else
web_layer_.to<WebKit::WebExternalTextureLayer>().invalidateRect(
web_rect);
}
damaged_region_.setEmpty();
}
for (size_t i = 0; i < children_.size(); ++i)
children_[i]->SendDamagedRects();
}
void Layer::SuppressPaint() {
if (!delegate_)
return;
delegate_ = NULL;
for (size_t i = 0; i < children_.size(); ++i)
children_[i]->SuppressPaint();
}
void Layer::paintContents(WebKit::WebCanvas* web_canvas,
const WebKit::WebRect& clip) {
TRACE_EVENT0("ui", "Layer::paintContents");
gfx::Canvas canvas(web_canvas);
if (delegate_)
delegate_->OnPaintLayer(&canvas);
}
float Layer::GetCombinedOpacity() const {
float opacity = opacity_;
Layer* current = this->parent_;
while (current) {
opacity *= current->opacity_;
current = current->parent_;
}
return opacity;
}
void Layer::StackRelativeTo(Layer* child, Layer* other, bool above) {
DCHECK_NE(child, other);
DCHECK_EQ(this, child->parent());
DCHECK_EQ(this, other->parent());
const size_t child_i =
std::find(children_.begin(), children_.end(), child) - children_.begin();
const size_t other_i =
std::find(children_.begin(), children_.end(), other) - children_.begin();
if ((above && child_i == other_i + 1) || (!above && child_i + 1 == other_i))
return;
const size_t dest_i =
above ?
(child_i < other_i ? other_i : other_i + 1) :
(child_i < other_i ? other_i - 1 : other_i);
children_.erase(children_.begin() + child_i);
children_.insert(children_.begin() + dest_i, child);
child->web_layer_.removeFromParent();
web_layer_.insertChild(child->web_layer_, dest_i);
}
bool Layer::ConvertPointForAncestor(const Layer* ancestor,
gfx::Point* point) const {
ui::Transform transform;
bool result = GetTransformRelativeTo(ancestor, &transform);
gfx::Point3f p(*point);
transform.TransformPoint(p);
*point = p.AsPoint();
return result;
}
bool Layer::ConvertPointFromAncestor(const Layer* ancestor,
gfx::Point* point) const {
ui::Transform transform;
bool result = GetTransformRelativeTo(ancestor, &transform);
gfx::Point3f p(*point);
transform.TransformPointReverse(p);
*point = p.AsPoint();
return result;
}
bool Layer::GetTransformRelativeTo(const Layer* ancestor,
ui::Transform* transform) const {
const Layer* p = this;
for (; p && p != ancestor; p = p->parent()) {
if (p->transform().HasChange())
transform->ConcatTransform(p->transform());
transform->ConcatTranslate(static_cast<float>(p->bounds().x()),
static_cast<float>(p->bounds().y()));
}
return p == ancestor;
}
void Layer::SetBoundsImmediately(const gfx::Rect& bounds) {
if (bounds == bounds_)
return;
bool was_move = bounds_.size() == bounds.size();
bounds_ = bounds;
if (IsDrawn()) {
if (was_move)
ScheduleDraw();
else
SchedulePaint(gfx::Rect(bounds.size()));
}
RecomputeTransform();
RecomputeDrawsContentAndUVRect();
}
void Layer::SetTransformImmediately(const ui::Transform& transform) {
transform_ = transform;
RecomputeTransform();
}
void Layer::SetOpacityImmediately(float opacity) {
bool schedule_draw = (opacity != opacity_ && IsDrawn());
opacity_ = opacity;
if (visible_)
web_layer_.setOpacity(opacity);
RecomputeDebugBorderColor();
if (schedule_draw)
ScheduleDraw();
}
void Layer::SetVisibilityImmediately(bool visible) {
if (visible_ == visible)
return;
visible_ = visible;
// TODO(piman): Expose a visibility flag on WebLayer.
web_layer_.setOpacity(visible_ ? opacity_ : 0.f);
}
void Layer::SetBoundsFromAnimation(const gfx::Rect& bounds) {
SetBoundsImmediately(bounds);
}
void Layer::SetTransformFromAnimation(const Transform& transform) {
SetTransformImmediately(transform);
}
void Layer::SetOpacityFromAnimation(float opacity) {
SetOpacityImmediately(opacity);
}
void Layer::SetVisibilityFromAnimation(bool visibility) {
SetVisibilityImmediately(visibility);
}
void Layer::ScheduleDrawForAnimation() {
ScheduleDraw();
}
const gfx::Rect& Layer::GetBoundsForAnimation() const {
return bounds();
}
const Transform& Layer::GetTransformForAnimation() const {
return transform();
}
float Layer::GetOpacityForAnimation() const {
return opacity();
}
bool Layer::GetVisibilityForAnimation() const {
return visible();
}
void Layer::CreateWebLayer() {
if (type_ == LAYER_SOLID_COLOR)
web_layer_ = WebKit::WebSolidColorLayer::create();
else
web_layer_ = WebKit::WebContentLayer::create(this);
web_layer_.setAnchorPoint(WebKit::WebFloatPoint(0.f, 0.f));
web_layer_.setOpaque(true);
web_layer_is_accelerated_ = false;
show_debug_borders_ = CommandLine::ForCurrentProcess()->HasSwitch(
switches::kUIShowLayerBorders);
web_layer_.setDebugBorderWidth(show_debug_borders_ ? 2 : 0);
RecomputeDrawsContentAndUVRect();
RecomputeDebugBorderColor();
}
void Layer::RecomputeTransform() {
ui::Transform transform = transform_;
transform.ConcatTranslate(bounds_.x(), bounds_.y());
web_layer_.setTransform(transform.matrix());
}
void Layer::RecomputeDrawsContentAndUVRect() {
DCHECK(!web_layer_.isNull());
bool should_draw = type_ != LAYER_NOT_DRAWN;
if (!web_layer_is_accelerated_) {
if (type_ != LAYER_SOLID_COLOR)
web_layer_.to<WebKit::WebContentLayer>().setDrawsContent(should_draw);
web_layer_.setBounds(bounds_.size());
} else {
DCHECK(texture_);
unsigned int texture_id = texture_->texture_id();
WebKit::WebExternalTextureLayer texture_layer =
web_layer_.to<WebKit::WebExternalTextureLayer>();
texture_layer.setTextureId(should_draw ? texture_id : 0);
gfx::Size texture_size = texture_->size();
gfx::Size size(std::min(bounds_.width(), texture_size.width()),
std::min(bounds_.height(), texture_size.height()));
WebKit::WebFloatRect rect(
0,
0,
static_cast<float>(size.width())/texture_size.width(),
static_cast<float>(size.height())/texture_size.height());
texture_layer.setUVRect(rect);
web_layer_.setBounds(size);
}
}
void Layer::RecomputeDebugBorderColor() {
if (!show_debug_borders_)
return;
unsigned int color = 0xFF000000;
color |= web_layer_is_accelerated_ ? 0x0000FF00 : 0x00FF0000;
bool opaque = fills_bounds_opaquely_ && (GetCombinedOpacity() == 1.f);
if (!opaque)
color |= 0xFF;
web_layer_.setDebugBorderColor(color);
}
} // namespace ui