Performance histograms for extension content verification
[chromium-blink-merge.git] / ash / shelf / shelf_button.cc
blobe1db5d0355fd0f47cd50ceb5535bc3d7fa34f481
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.
5 #include "ash/shelf/shelf_button.h"
7 #include <algorithm>
9 #include "ash/ash_constants.h"
10 #include "ash/ash_switches.h"
11 #include "ash/shelf/shelf_button_host.h"
12 #include "ash/shelf/shelf_layout_manager.h"
13 #include "grit/ash_resources.h"
14 #include "skia/ext/image_operations.h"
15 #include "ui/accessibility/ax_view_state.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/compositor/layer.h"
18 #include "ui/compositor/scoped_layer_animation_settings.h"
19 #include "ui/events/event_constants.h"
20 #include "ui/gfx/animation/animation_delegate.h"
21 #include "ui/gfx/animation/throb_animation.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/image/image.h"
24 #include "ui/gfx/image/image_skia_operations.h"
25 #include "ui/gfx/skbitmap_operations.h"
26 #include "ui/views/controls/image_view.h"
28 namespace {
30 // Size of the bar. This is along the opposite axis of the shelf. For example,
31 // if the shelf is aligned horizontally then this is the height of the bar.
32 const int kBarSize = 3;
33 const int kIconSize = 32;
34 const int kIconPad = 5;
35 const int kIconPadVertical = 6;
36 const int kAttentionThrobDurationMS = 800;
38 // Simple AnimationDelegate that owns a single ThrobAnimation instance to
39 // keep all Draw Attention animations in sync.
40 class ShelfButtonAnimation : public gfx::AnimationDelegate {
41 public:
42 class Observer {
43 public:
44 virtual void AnimationProgressed() = 0;
46 protected:
47 virtual ~Observer() {}
50 static ShelfButtonAnimation* GetInstance() {
51 static ShelfButtonAnimation* s_instance = new ShelfButtonAnimation();
52 return s_instance;
55 void AddObserver(Observer* observer) {
56 observers_.AddObserver(observer);
59 void RemoveObserver(Observer* observer) {
60 observers_.RemoveObserver(observer);
61 if (!observers_.might_have_observers())
62 animation_.Stop();
65 int GetAlpha() {
66 return GetThrobAnimation().CurrentValueBetween(0, 255);
69 double GetAnimation() {
70 return GetThrobAnimation().GetCurrentValue();
73 private:
74 ShelfButtonAnimation()
75 : animation_(this) {
76 animation_.SetThrobDuration(kAttentionThrobDurationMS);
77 animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT);
80 virtual ~ShelfButtonAnimation() {
83 gfx::ThrobAnimation& GetThrobAnimation() {
84 if (!animation_.is_animating()) {
85 animation_.Reset();
86 animation_.StartThrobbing(-1 /*throb indefinitely*/);
88 return animation_;
91 // gfx::AnimationDelegate
92 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
93 if (animation != &animation_)
94 return;
95 if (!animation_.is_animating())
96 return;
97 FOR_EACH_OBSERVER(Observer, observers_, AnimationProgressed());
100 gfx::ThrobAnimation animation_;
101 ObserverList<Observer> observers_;
103 DISALLOW_COPY_AND_ASSIGN(ShelfButtonAnimation);
106 } // namespace
108 namespace ash {
110 ////////////////////////////////////////////////////////////////////////////////
111 // ShelfButton::BarView
113 class ShelfButton::BarView : public views::ImageView,
114 public ShelfButtonAnimation::Observer {
115 public:
116 BarView(ShelfButton* host)
117 : host_(host),
118 show_attention_(false) {
121 virtual ~BarView() {
122 if (show_attention_)
123 ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
126 // views::View:
127 virtual bool CanProcessEventsWithinSubtree() const OVERRIDE {
128 // Send events to the parent view for handling.
129 return false;
132 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
133 if (show_attention_) {
134 int alpha = ShelfButtonAnimation::GetInstance()->GetAlpha();
135 canvas->SaveLayerAlpha(alpha);
136 views::ImageView::OnPaint(canvas);
137 canvas->Restore();
138 } else {
139 views::ImageView::OnPaint(canvas);
143 // ShelfButtonAnimation::Observer
144 virtual void AnimationProgressed() OVERRIDE {
145 UpdateBounds();
146 SchedulePaint();
149 void SetBarBoundsRect(const gfx::Rect& bounds) {
150 base_bounds_ = bounds;
151 UpdateBounds();
154 void ShowAttention(bool show) {
155 if (show_attention_ != show) {
156 show_attention_ = show;
157 if (show_attention_)
158 ShelfButtonAnimation::GetInstance()->AddObserver(this);
159 else
160 ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
162 UpdateBounds();
165 private:
166 void UpdateBounds() {
167 gfx::Rect bounds = base_bounds_;
168 if (show_attention_) {
169 // Scale from .35 to 1.0 of the total width (which is wider than the
170 // visible width of the image, so the animation "rests" briefly at full
171 // visible width.
172 double animation = ShelfButtonAnimation::GetInstance()->GetAnimation();
173 double scale = (.35 + .65 * animation);
174 if (host_->shelf_layout_manager()->GetAlignment() ==
175 SHELF_ALIGNMENT_BOTTOM) {
176 bounds.set_width(base_bounds_.width() * scale);
177 int x_offset = (base_bounds_.width() - bounds.width()) / 2;
178 bounds.set_x(base_bounds_.x() + x_offset);
179 } else {
180 bounds.set_height(base_bounds_.height() * scale);
181 int y_offset = (base_bounds_.height() - bounds.height()) / 2;
182 bounds.set_y(base_bounds_.y() + y_offset);
185 SetBoundsRect(bounds);
188 ShelfButton* host_;
189 bool show_attention_;
190 gfx::Rect base_bounds_;
192 DISALLOW_COPY_AND_ASSIGN(BarView);
195 ////////////////////////////////////////////////////////////////////////////////
196 // ShelfButton::IconView
198 ShelfButton::IconView::IconView() : icon_size_(kIconSize) {
201 ShelfButton::IconView::~IconView() {
204 bool ShelfButton::IconView::CanProcessEventsWithinSubtree() const {
205 // Return false so that events are sent to ShelfView for handling.
206 return false;
209 ////////////////////////////////////////////////////////////////////////////////
210 // ShelfButton
212 ShelfButton* ShelfButton::Create(views::ButtonListener* listener,
213 ShelfButtonHost* host,
214 ShelfLayoutManager* shelf_layout_manager) {
215 ShelfButton* button = new ShelfButton(listener, host, shelf_layout_manager);
216 button->Init();
217 return button;
220 ShelfButton::ShelfButton(views::ButtonListener* listener,
221 ShelfButtonHost* host,
222 ShelfLayoutManager* shelf_layout_manager)
223 : CustomButton(listener),
224 host_(host),
225 icon_view_(NULL),
226 bar_(new BarView(this)),
227 state_(STATE_NORMAL),
228 shelf_layout_manager_(shelf_layout_manager),
229 destroyed_flag_(NULL) {
230 SetAccessibilityFocusable(true);
232 const gfx::ShadowValue kShadows[] = {
233 gfx::ShadowValue(gfx::Point(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
234 gfx::ShadowValue(gfx::Point(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
235 gfx::ShadowValue(gfx::Point(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)),
237 icon_shadows_.assign(kShadows, kShadows + arraysize(kShadows));
239 AddChildView(bar_);
242 ShelfButton::~ShelfButton() {
243 if (destroyed_flag_)
244 *destroyed_flag_ = true;
247 void ShelfButton::SetShadowedImage(const gfx::ImageSkia& image) {
248 icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
249 image, icon_shadows_));
252 void ShelfButton::SetImage(const gfx::ImageSkia& image) {
253 if (image.isNull()) {
254 // TODO: need an empty image.
255 icon_view_->SetImage(image);
256 return;
259 if (icon_view_->icon_size() == 0) {
260 SetShadowedImage(image);
261 return;
264 // Resize the image maintaining our aspect ratio.
265 int pref = icon_view_->icon_size();
266 float aspect_ratio =
267 static_cast<float>(image.width()) / static_cast<float>(image.height());
268 int height = pref;
269 int width = static_cast<int>(aspect_ratio * height);
270 if (width > pref) {
271 width = pref;
272 height = static_cast<int>(width / aspect_ratio);
275 if (width == image.width() && height == image.height()) {
276 SetShadowedImage(image);
277 return;
280 SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(image,
281 skia::ImageOperations::RESIZE_BEST, gfx::Size(width, height)));
284 const gfx::ImageSkia& ShelfButton::GetImage() const {
285 return icon_view_->GetImage();
288 void ShelfButton::AddState(State state) {
289 if (!(state_ & state)) {
290 state_ |= state;
291 Layout();
292 if (state & STATE_ATTENTION)
293 bar_->ShowAttention(true);
297 void ShelfButton::ClearState(State state) {
298 if (state_ & state) {
299 state_ &= ~state;
300 Layout();
301 if (state & STATE_ATTENTION)
302 bar_->ShowAttention(false);
306 gfx::Rect ShelfButton::GetIconBounds() const {
307 return icon_view_->bounds();
310 void ShelfButton::ShowContextMenu(const gfx::Point& p,
311 ui::MenuSourceType source_type) {
312 if (!context_menu_controller())
313 return;
315 bool destroyed = false;
316 destroyed_flag_ = &destroyed;
318 CustomButton::ShowContextMenu(p, source_type);
320 if (!destroyed) {
321 destroyed_flag_ = NULL;
322 // The menu will not propagate mouse events while its shown. To address,
323 // the hover state gets cleared once the menu was shown (and this was not
324 // destroyed).
325 ClearState(STATE_HOVERED);
329 bool ShelfButton::OnMousePressed(const ui::MouseEvent& event) {
330 CustomButton::OnMousePressed(event);
331 host_->PointerPressedOnButton(this, ShelfButtonHost::MOUSE, event);
332 return true;
335 void ShelfButton::OnMouseReleased(const ui::MouseEvent& event) {
336 CustomButton::OnMouseReleased(event);
337 host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, false);
340 void ShelfButton::OnMouseCaptureLost() {
341 ClearState(STATE_HOVERED);
342 host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, true);
343 CustomButton::OnMouseCaptureLost();
346 bool ShelfButton::OnMouseDragged(const ui::MouseEvent& event) {
347 CustomButton::OnMouseDragged(event);
348 host_->PointerDraggedOnButton(this, ShelfButtonHost::MOUSE, event);
349 return true;
352 void ShelfButton::OnMouseMoved(const ui::MouseEvent& event) {
353 CustomButton::OnMouseMoved(event);
354 host_->MouseMovedOverButton(this);
357 void ShelfButton::OnMouseEntered(const ui::MouseEvent& event) {
358 AddState(STATE_HOVERED);
359 CustomButton::OnMouseEntered(event);
360 host_->MouseEnteredButton(this);
363 void ShelfButton::OnMouseExited(const ui::MouseEvent& event) {
364 ClearState(STATE_HOVERED);
365 CustomButton::OnMouseExited(event);
366 host_->MouseExitedButton(this);
369 void ShelfButton::GetAccessibleState(ui::AXViewState* state) {
370 state->role = ui::AX_ROLE_BUTTON;
371 state->name = host_->GetAccessibleName(this);
374 void ShelfButton::Layout() {
375 const gfx::Rect button_bounds(GetContentsBounds());
376 int icon_pad =
377 shelf_layout_manager_->GetAlignment() != SHELF_ALIGNMENT_BOTTOM ?
378 kIconPadVertical : kIconPad;
379 int x_offset = shelf_layout_manager_->PrimaryAxisValue(0, icon_pad);
380 int y_offset = shelf_layout_manager_->PrimaryAxisValue(icon_pad, 0);
382 int icon_width = std::min(kIconSize,
383 button_bounds.width() - x_offset);
384 int icon_height = std::min(kIconSize,
385 button_bounds.height() - y_offset);
387 // If on the left or top 'invert' the inset so the constant gap is on
388 // the interior (towards the center of display) edge of the shelf.
389 if (SHELF_ALIGNMENT_LEFT == shelf_layout_manager_->GetAlignment())
390 x_offset = button_bounds.width() - (kIconSize + icon_pad);
392 if (SHELF_ALIGNMENT_TOP == shelf_layout_manager_->GetAlignment())
393 y_offset = button_bounds.height() - (kIconSize + icon_pad);
395 // Center icon with respect to the secondary axis, and ensure
396 // that the icon doesn't occlude the bar highlight.
397 if (shelf_layout_manager_->IsHorizontalAlignment()) {
398 x_offset = std::max(0, button_bounds.width() - icon_width) / 2;
399 if (y_offset + icon_height + kBarSize > button_bounds.height())
400 icon_height = button_bounds.height() - (y_offset + kBarSize);
401 } else {
402 y_offset = std::max(0, button_bounds.height() - icon_height) / 2;
403 if (x_offset + icon_width + kBarSize > button_bounds.width())
404 icon_width = button_bounds.width() - (x_offset + kBarSize);
407 icon_view_->SetBoundsRect(gfx::Rect(
408 button_bounds.x() + x_offset,
409 button_bounds.y() + y_offset,
410 icon_width,
411 icon_height));
413 // Icon size has been incorrect when running
414 // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see
415 // http://crbug.com/234854.
416 DCHECK_LE(icon_width, kIconSize);
417 DCHECK_LE(icon_height, kIconSize);
419 bar_->SetBarBoundsRect(button_bounds);
421 UpdateState();
424 void ShelfButton::ChildPreferredSizeChanged(views::View* child) {
425 Layout();
428 void ShelfButton::OnFocus() {
429 AddState(STATE_FOCUSED);
430 CustomButton::OnFocus();
433 void ShelfButton::OnBlur() {
434 ClearState(STATE_FOCUSED);
435 CustomButton::OnBlur();
438 void ShelfButton::OnPaint(gfx::Canvas* canvas) {
439 CustomButton::OnPaint(canvas);
440 if (HasFocus()) {
441 gfx::Rect paint_bounds(GetLocalBounds());
442 paint_bounds.Inset(1, 1, 1, 1);
443 canvas->DrawSolidFocusRect(paint_bounds, kFocusBorderColor);
447 void ShelfButton::OnGestureEvent(ui::GestureEvent* event) {
448 switch (event->type()) {
449 case ui::ET_GESTURE_TAP_DOWN:
450 AddState(STATE_HOVERED);
451 return CustomButton::OnGestureEvent(event);
452 case ui::ET_GESTURE_END:
453 ClearState(STATE_HOVERED);
454 return CustomButton::OnGestureEvent(event);
455 case ui::ET_GESTURE_SCROLL_BEGIN:
456 host_->PointerPressedOnButton(this, ShelfButtonHost::TOUCH, *event);
457 event->SetHandled();
458 return;
459 case ui::ET_GESTURE_SCROLL_UPDATE:
460 host_->PointerDraggedOnButton(this, ShelfButtonHost::TOUCH, *event);
461 event->SetHandled();
462 return;
463 case ui::ET_GESTURE_SCROLL_END:
464 case ui::ET_SCROLL_FLING_START:
465 host_->PointerReleasedOnButton(this, ShelfButtonHost::TOUCH, false);
466 event->SetHandled();
467 return;
468 default:
469 return CustomButton::OnGestureEvent(event);
473 void ShelfButton::Init() {
474 icon_view_ = CreateIconView();
476 // TODO: refactor the layers so each button doesn't require 2.
477 icon_view_->SetPaintToLayer(true);
478 icon_view_->SetFillsBoundsOpaquely(false);
479 icon_view_->SetHorizontalAlignment(views::ImageView::CENTER);
480 icon_view_->SetVerticalAlignment(views::ImageView::LEADING);
482 AddChildView(icon_view_);
485 ShelfButton::IconView* ShelfButton::CreateIconView() {
486 return new IconView;
489 bool ShelfButton::IsShelfHorizontal() const {
490 return shelf_layout_manager_->IsHorizontalAlignment();
493 void ShelfButton::UpdateState() {
494 UpdateBar();
496 icon_view_->SetHorizontalAlignment(
497 shelf_layout_manager_->PrimaryAxisValue(views::ImageView::CENTER,
498 views::ImageView::LEADING));
499 icon_view_->SetVerticalAlignment(
500 shelf_layout_manager_->PrimaryAxisValue(views::ImageView::LEADING,
501 views::ImageView::CENTER));
502 SchedulePaint();
505 void ShelfButton::UpdateBar() {
506 if (state_ & STATE_HIDDEN) {
507 bar_->SetVisible(false);
508 return;
511 int bar_id = 0;
512 if (state_ & STATE_ACTIVE)
513 bar_id = IDR_ASH_SHELF_UNDERLINE_ACTIVE;
514 else if (state_ & STATE_RUNNING)
515 bar_id = IDR_ASH_SHELF_UNDERLINE_RUNNING;
517 if (bar_id != 0) {
518 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
519 const gfx::ImageSkia* image = rb.GetImageNamed(bar_id).ToImageSkia();
520 if (shelf_layout_manager_->GetAlignment() == SHELF_ALIGNMENT_BOTTOM) {
521 bar_->SetImage(*image);
522 } else {
523 bar_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(*image,
524 shelf_layout_manager_->SelectValueForShelfAlignment(
525 SkBitmapOperations::ROTATION_90_CW,
526 SkBitmapOperations::ROTATION_90_CW,
527 SkBitmapOperations::ROTATION_270_CW,
528 SkBitmapOperations::ROTATION_180_CW)));
530 bar_->SetHorizontalAlignment(
531 shelf_layout_manager_->SelectValueForShelfAlignment(
532 views::ImageView::CENTER,
533 views::ImageView::LEADING,
534 views::ImageView::TRAILING,
535 views::ImageView::CENTER));
536 bar_->SetVerticalAlignment(
537 shelf_layout_manager_->SelectValueForShelfAlignment(
538 views::ImageView::TRAILING,
539 views::ImageView::CENTER,
540 views::ImageView::CENTER,
541 views::ImageView::LEADING));
542 bar_->SchedulePaint();
545 bar_->SetVisible(bar_id != 0 && state_ != STATE_NORMAL);
548 } // namespace ash