| // 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 |