1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "cc/layers/painted_scrollbar_layer.h"
6
7#include "base/auto_reset.h"
8#include "base/basictypes.h"
9#include "base/debug/trace_event.h"
10#include "cc/layers/painted_scrollbar_layer_impl.h"
11#include "cc/resources/ui_resource_bitmap.h"
12#include "cc/trees/layer_tree_host.h"
13#include "cc/trees/layer_tree_impl.h"
14#include "skia/ext/platform_canvas.h"
15#include "skia/ext/refptr.h"
16#include "third_party/skia/include/core/SkBitmap.h"
17#include "third_party/skia/include/core/SkCanvas.h"
18#include "third_party/skia/include/core/SkSize.h"
19#include "ui/gfx/skia_util.h"
20
21namespace cc {
22
23scoped_ptr<LayerImpl> PaintedScrollbarLayer::CreateLayerImpl(
24    LayerTreeImpl* tree_impl) {
25  return PaintedScrollbarLayerImpl::Create(
26      tree_impl, id(), scrollbar_->Orientation()).PassAs<LayerImpl>();
27}
28
29scoped_refptr<PaintedScrollbarLayer> PaintedScrollbarLayer::Create(
30    scoped_ptr<Scrollbar> scrollbar,
31    int scroll_layer_id) {
32  return make_scoped_refptr(
33      new PaintedScrollbarLayer(scrollbar.Pass(), scroll_layer_id));
34}
35
36PaintedScrollbarLayer::PaintedScrollbarLayer(scoped_ptr<Scrollbar> scrollbar,
37                                             int scroll_layer_id)
38    : scrollbar_(scrollbar.Pass()),
39      scroll_layer_id_(scroll_layer_id),
40      clip_layer_id_(Layer::INVALID_ID),
41      thumb_thickness_(scrollbar_->ThumbThickness()),
42      thumb_length_(scrollbar_->ThumbLength()),
43      is_overlay_(scrollbar_->IsOverlay()),
44      has_thumb_(scrollbar_->HasThumb()) {
45  if (!scrollbar_->IsOverlay())
46    SetShouldScrollOnMainThread(true);
47}
48
49PaintedScrollbarLayer::~PaintedScrollbarLayer() {}
50
51int PaintedScrollbarLayer::ScrollLayerId() const {
52  return scroll_layer_id_;
53}
54
55void PaintedScrollbarLayer::SetScrollLayer(int layer_id) {
56  if (layer_id == scroll_layer_id_)
57    return;
58
59  scroll_layer_id_ = layer_id;
60  SetNeedsFullTreeSync();
61}
62
63void PaintedScrollbarLayer::SetClipLayer(int layer_id) {
64  if (layer_id == clip_layer_id_)
65    return;
66
67  clip_layer_id_ = layer_id;
68  SetNeedsFullTreeSync();
69}
70
71bool PaintedScrollbarLayer::OpacityCanAnimateOnImplThread() const {
72  return scrollbar_->IsOverlay();
73}
74
75ScrollbarOrientation PaintedScrollbarLayer::orientation() const {
76  return scrollbar_->Orientation();
77}
78
79int PaintedScrollbarLayer::MaxTextureSize() {
80  DCHECK(layer_tree_host());
81  return layer_tree_host()->GetRendererCapabilities().max_texture_size;
82}
83
84float PaintedScrollbarLayer::ClampScaleToMaxTextureSize(float scale) {
85  // If the scaled content_bounds() is bigger than the max texture size of the
86  // device, we need to clamp it by rescaling, since content_bounds() is used
87  // below to set the texture size.
88  gfx::Size scaled_bounds = ComputeContentBoundsForScale(scale, scale);
89  if (scaled_bounds.width() > MaxTextureSize() ||
90      scaled_bounds.height() > MaxTextureSize()) {
91    if (scaled_bounds.width() > scaled_bounds.height())
92      return (MaxTextureSize() - 1) / static_cast<float>(bounds().width());
93    else
94      return (MaxTextureSize() - 1) / static_cast<float>(bounds().height());
95  }
96  return scale;
97}
98
99void PaintedScrollbarLayer::CalculateContentsScale(
100    float ideal_contents_scale,
101    float device_scale_factor,
102    float page_scale_factor,
103    float maximum_animation_contents_scale,
104    bool animating_transform_to_screen,
105    float* contents_scale_x,
106    float* contents_scale_y,
107    gfx::Size* content_bounds) {
108  ContentsScalingLayer::CalculateContentsScale(
109      ClampScaleToMaxTextureSize(ideal_contents_scale),
110      device_scale_factor,
111      page_scale_factor,
112      maximum_animation_contents_scale,
113      animating_transform_to_screen,
114      contents_scale_x,
115      contents_scale_y,
116      content_bounds);
117}
118
119void PaintedScrollbarLayer::PushPropertiesTo(LayerImpl* layer) {
120  ContentsScalingLayer::PushPropertiesTo(layer);
121
122  PushScrollClipPropertiesTo(layer);
123
124  PaintedScrollbarLayerImpl* scrollbar_layer =
125      static_cast<PaintedScrollbarLayerImpl*>(layer);
126
127  scrollbar_layer->SetThumbThickness(thumb_thickness_);
128  scrollbar_layer->SetThumbLength(thumb_length_);
129  if (orientation() == HORIZONTAL) {
130    scrollbar_layer->SetTrackStart(
131        track_rect_.x() - location_.x());
132    scrollbar_layer->SetTrackLength(track_rect_.width());
133  } else {
134    scrollbar_layer->SetTrackStart(
135        track_rect_.y() - location_.y());
136    scrollbar_layer->SetTrackLength(track_rect_.height());
137  }
138
139  if (track_resource_.get())
140    scrollbar_layer->set_track_ui_resource_id(track_resource_->id());
141  if (thumb_resource_.get())
142    scrollbar_layer->set_thumb_ui_resource_id(thumb_resource_->id());
143
144  scrollbar_layer->set_is_overlay_scrollbar(is_overlay_);
145}
146
147ScrollbarLayerInterface* PaintedScrollbarLayer::ToScrollbarLayer() {
148  return this;
149}
150
151void PaintedScrollbarLayer::PushScrollClipPropertiesTo(LayerImpl* layer) {
152  PaintedScrollbarLayerImpl* scrollbar_layer =
153      static_cast<PaintedScrollbarLayerImpl*>(layer);
154
155  scrollbar_layer->SetScrollLayerById(scroll_layer_id_);
156  scrollbar_layer->SetClipLayerById(clip_layer_id_);
157}
158
159void PaintedScrollbarLayer::SetLayerTreeHost(LayerTreeHost* host) {
160  // When the LTH is set to null or has changed, then this layer should remove
161  // all of its associated resources.
162  if (!host || host != layer_tree_host()) {
163    track_resource_.reset();
164    thumb_resource_.reset();
165  }
166
167  ContentsScalingLayer::SetLayerTreeHost(host);
168}
169
170gfx::Rect PaintedScrollbarLayer::ScrollbarLayerRectToContentRect(
171    const gfx::Rect& layer_rect) const {
172  // Don't intersect with the bounds as in LayerRectToContentRect() because
173  // layer_rect here might be in coordinates of the containing layer.
174  gfx::Rect expanded_rect = gfx::ScaleToEnclosingRect(
175      layer_rect, contents_scale_x(), contents_scale_y());
176  // We should never return a rect bigger than the content_bounds().
177  gfx::Size clamped_size = expanded_rect.size();
178  clamped_size.SetToMin(content_bounds());
179  expanded_rect.set_size(clamped_size);
180  return expanded_rect;
181}
182
183gfx::Rect PaintedScrollbarLayer::OriginThumbRect() const {
184  gfx::Size thumb_size;
185  if (orientation() == HORIZONTAL) {
186    thumb_size =
187        gfx::Size(scrollbar_->ThumbLength(), scrollbar_->ThumbThickness());
188  } else {
189    thumb_size =
190        gfx::Size(scrollbar_->ThumbThickness(), scrollbar_->ThumbLength());
191  }
192  return gfx::Rect(thumb_size);
193}
194
195void PaintedScrollbarLayer::UpdateThumbAndTrackGeometry() {
196  UpdateProperty(scrollbar_->TrackRect(), &track_rect_);
197  UpdateProperty(scrollbar_->Location(), &location_);
198  UpdateProperty(scrollbar_->IsOverlay(), &is_overlay_);
199  UpdateProperty(scrollbar_->HasThumb(), &has_thumb_);
200  if (has_thumb_) {
201    UpdateProperty(scrollbar_->ThumbThickness(), &thumb_thickness_);
202    UpdateProperty(scrollbar_->ThumbLength(), &thumb_length_);
203  }
204}
205
206bool PaintedScrollbarLayer::Update(ResourceUpdateQueue* queue,
207                                   const OcclusionTracker<Layer>* occlusion) {
208  UpdateThumbAndTrackGeometry();
209
210  gfx::Rect track_layer_rect = gfx::Rect(location_, bounds());
211  gfx::Rect scaled_track_rect = ScrollbarLayerRectToContentRect(
212      track_layer_rect);
213
214  if (track_rect_.IsEmpty() || scaled_track_rect.IsEmpty())
215    return false;
216
217  {
218    base::AutoReset<bool> ignore_set_needs_commit(&ignore_set_needs_commit_,
219                                                  true);
220    ContentsScalingLayer::Update(queue, occlusion);
221  }
222
223  if (update_rect_.IsEmpty() && track_resource_)
224    return false;
225
226  track_resource_ = ScopedUIResource::Create(
227      layer_tree_host(),
228      RasterizeScrollbarPart(track_layer_rect, scaled_track_rect, TRACK));
229
230  gfx::Rect thumb_layer_rect = OriginThumbRect();
231  gfx::Rect scaled_thumb_rect =
232      ScrollbarLayerRectToContentRect(thumb_layer_rect);
233  if (has_thumb_ && !scaled_thumb_rect.IsEmpty()) {
234    thumb_resource_ = ScopedUIResource::Create(
235        layer_tree_host(),
236        RasterizeScrollbarPart(thumb_layer_rect, scaled_thumb_rect, THUMB));
237  }
238
239  // UI resources changed so push properties is needed.
240  SetNeedsPushProperties();
241  return true;
242}
243
244UIResourceBitmap PaintedScrollbarLayer::RasterizeScrollbarPart(
245    const gfx::Rect& layer_rect,
246    const gfx::Rect& content_rect,
247    ScrollbarPart part) {
248  DCHECK(!content_rect.size().IsEmpty());
249  DCHECK(!layer_rect.size().IsEmpty());
250
251  SkBitmap skbitmap;
252  skbitmap.allocN32Pixels(content_rect.width(), content_rect.height());
253  SkCanvas skcanvas(skbitmap);
254
255  float scale_x =
256      content_rect.width() / static_cast<float>(layer_rect.width());
257  float scale_y =
258      content_rect.height() / static_cast<float>(layer_rect.height());
259
260  skcanvas.scale(SkFloatToScalar(scale_x),
261                 SkFloatToScalar(scale_y));
262  skcanvas.translate(SkFloatToScalar(-layer_rect.x()),
263                     SkFloatToScalar(-layer_rect.y()));
264
265  SkRect layer_skrect = RectToSkRect(layer_rect);
266  SkPaint paint;
267  paint.setAntiAlias(false);
268  paint.setXfermodeMode(SkXfermode::kClear_Mode);
269  skcanvas.drawRect(layer_skrect, paint);
270  skcanvas.clipRect(layer_skrect);
271
272  scrollbar_->PaintPart(&skcanvas, part, layer_rect);
273  // Make sure that the pixels are no longer mutable to unavoid unnecessary
274  // allocation and copying.
275  skbitmap.setImmutable();
276
277  return UIResourceBitmap(skbitmap);
278}
279
280}  // namespace cc
281