Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / ash / shelf / shelf_button.cc
blob6329694252efa0a26292d48e38f2e76e7c9c03c4
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 "base/time/time.h"
14 #include "grit/ash_resources.h"
15 #include "skia/ext/image_operations.h"
16 #include "ui/accessibility/ax_view_state.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/compositor/layer.h"
19 #include "ui/compositor/scoped_layer_animation_settings.h"
20 #include "ui/events/event_constants.h"
21 #include "ui/gfx/animation/animation_delegate.h"
22 #include "ui/gfx/animation/throb_animation.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/geometry/vector2d.h"
25 #include "ui/gfx/image/image.h"
26 #include "ui/gfx/image/image_skia_operations.h"
27 #include "ui/gfx/skbitmap_operations.h"
28 #include "ui/views/controls/image_view.h"
30 namespace {
32 // Size of the bar. This is along the opposite axis of the shelf. For example,
33 // if the shelf is aligned horizontally then this is the height of the bar.
34 const int kBarSize = 3;
35 const int kIconSize = 32;
36 const int kIconPad = 5;
37 const int kIconPadVertical = 6;
38 const int kAttentionThrobDurationMS = 800;
39 const int kMaxAnimationSeconds = 10;
41 // Simple AnimationDelegate that owns a single ThrobAnimation instance to
42 // keep all Draw Attention animations in sync.
43 class ShelfButtonAnimation : public gfx::AnimationDelegate {
44 public:
45 class Observer {
46 public:
47 virtual void AnimationProgressed() = 0;
49 protected:
50 virtual ~Observer() {}
53 static ShelfButtonAnimation* GetInstance() {
54 static ShelfButtonAnimation* s_instance = new ShelfButtonAnimation();
55 return s_instance;
58 void AddObserver(Observer* observer) {
59 observers_.AddObserver(observer);
62 void RemoveObserver(Observer* observer) {
63 observers_.RemoveObserver(observer);
64 if (!observers_.might_have_observers())
65 animation_.Stop();
68 int GetAlpha() {
69 return GetThrobAnimation().CurrentValueBetween(0, 255);
72 double GetAnimation() {
73 return GetThrobAnimation().GetCurrentValue();
76 private:
77 ShelfButtonAnimation()
78 : animation_(this) {
79 animation_.SetThrobDuration(kAttentionThrobDurationMS);
80 animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT);
83 ~ShelfButtonAnimation() override {}
85 gfx::ThrobAnimation& GetThrobAnimation() {
86 if (!animation_.is_animating()) {
87 animation_.Reset();
88 animation_.StartThrobbing(-1 /*throb indefinitely*/);
90 return animation_;
93 // gfx::AnimationDelegate
94 void AnimationProgressed(const gfx::Animation* animation) override {
95 if (animation != &animation_)
96 return;
97 if (!animation_.is_animating())
98 return;
99 FOR_EACH_OBSERVER(Observer, observers_, AnimationProgressed());
102 gfx::ThrobAnimation animation_;
103 base::ObserverList<Observer> observers_;
105 DISALLOW_COPY_AND_ASSIGN(ShelfButtonAnimation);
108 } // namespace
110 namespace ash {
112 ////////////////////////////////////////////////////////////////////////////////
113 // ShelfButton::BarView
115 class ShelfButton::BarView : public views::ImageView,
116 public ShelfButtonAnimation::Observer {
117 public:
118 BarView(ShelfButton* host)
119 : host_(host),
120 show_attention_(false),
121 animation_end_time_(base::TimeTicks()),
122 animating_(false) {
123 // Make sure the events reach the parent view for handling.
124 set_interactive(false);
127 ~BarView() override {
128 if (show_attention_)
129 ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
132 // views::View:
133 void OnPaint(gfx::Canvas* canvas) override {
134 if (show_attention_) {
135 int alpha =
136 animating_ ? ShelfButtonAnimation::GetInstance()->GetAlpha() : 255;
137 canvas->SaveLayerAlpha(alpha);
138 views::ImageView::OnPaint(canvas);
139 canvas->Restore();
140 } else {
141 views::ImageView::OnPaint(canvas);
145 // ShelfButtonAnimation::Observer
146 void AnimationProgressed() override {
147 UpdateBounds();
148 SchedulePaint();
151 void SetBarBoundsRect(const gfx::Rect& bounds) {
152 base_bounds_ = bounds;
153 UpdateBounds();
156 void ShowAttention(bool show) {
157 if (show_attention_ != show) {
158 show_attention_ = show;
159 if (show_attention_) {
160 animating_ = true;
161 animation_end_time_ = base::TimeTicks::Now() +
162 base::TimeDelta::FromSeconds(kMaxAnimationSeconds);
163 ShelfButtonAnimation::GetInstance()->AddObserver(this);
164 } else {
165 animating_ = false;
166 ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
169 UpdateBounds();
172 private:
173 void UpdateBounds() {
174 gfx::Rect bounds = base_bounds_;
175 if (show_attention_) {
176 // Scale from .35 to 1.0 of the total width (which is wider than the
177 // visible width of the image), so the animation "rests" briefly at full
178 // visible width. Cap bounds length at kIconSize to prevent visual
179 // flutter while centering bar within further expanding bounds.
180 double animation = animating_ ?
181 ShelfButtonAnimation::GetInstance()->GetAnimation() : 1.0;
182 double scale = .35 + .65 * animation;
183 if (host_->shelf_layout_manager()->GetAlignment() ==
184 SHELF_ALIGNMENT_BOTTOM) {
185 int width = base_bounds_.width() * scale;
186 bounds.set_width(std::min(width, kIconSize));
187 int x_offset = (base_bounds_.width() - bounds.width()) / 2;
188 bounds.set_x(base_bounds_.x() + x_offset);
189 UpdateAnimating(bounds.width() == kIconSize);
190 } else {
191 int height = base_bounds_.height() * scale;
192 bounds.set_height(std::min(height, kIconSize));
193 int y_offset = (base_bounds_.height() - bounds.height()) / 2;
194 bounds.set_y(base_bounds_.y() + y_offset);
195 UpdateAnimating(bounds.height() == kIconSize);
198 SetBoundsRect(bounds);
201 void UpdateAnimating(bool max_length) {
202 if (!max_length)
203 return;
204 if (base::TimeTicks::Now() > animation_end_time_) {
205 animating_ = false;
206 ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
210 ShelfButton* host_;
211 bool show_attention_;
212 base::TimeTicks animation_end_time_; // For attention throbbing underline.
213 bool animating_; // Is time-limited attention animation running?
214 gfx::Rect base_bounds_;
216 DISALLOW_COPY_AND_ASSIGN(BarView);
219 ////////////////////////////////////////////////////////////////////////////////
220 // ShelfButton::IconView
222 ShelfButton::IconView::IconView() : icon_size_(kIconSize) {
223 // Do not make this interactive, so that events are sent to ShelfView for
224 // handling.
225 set_interactive(false);
228 ShelfButton::IconView::~IconView() {
231 ////////////////////////////////////////////////////////////////////////////////
232 // ShelfButton
234 // static
235 const char ShelfButton::kViewClassName[] = "ash/ShelfButton";
237 ShelfButton* ShelfButton::Create(views::ButtonListener* listener,
238 ShelfButtonHost* host,
239 ShelfLayoutManager* shelf_layout_manager) {
240 ShelfButton* button = new ShelfButton(listener, host, shelf_layout_manager);
241 button->Init();
242 return button;
245 ShelfButton::ShelfButton(views::ButtonListener* listener,
246 ShelfButtonHost* host,
247 ShelfLayoutManager* shelf_layout_manager)
248 : CustomButton(listener),
249 host_(host),
250 icon_view_(NULL),
251 bar_(new BarView(this)),
252 state_(STATE_NORMAL),
253 shelf_layout_manager_(shelf_layout_manager),
254 destroyed_flag_(NULL) {
255 SetAccessibilityFocusable(true);
257 const gfx::ShadowValue kShadows[] = {
258 gfx::ShadowValue(gfx::Vector2d(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
259 gfx::ShadowValue(gfx::Vector2d(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
260 gfx::ShadowValue(gfx::Vector2d(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)),
262 icon_shadows_.assign(kShadows, kShadows + arraysize(kShadows));
264 AddChildView(bar_);
267 ShelfButton::~ShelfButton() {
268 if (destroyed_flag_)
269 *destroyed_flag_ = true;
272 void ShelfButton::SetShadowedImage(const gfx::ImageSkia& image) {
273 icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
274 image, icon_shadows_));
277 void ShelfButton::SetImage(const gfx::ImageSkia& image) {
278 if (image.isNull()) {
279 // TODO: need an empty image.
280 icon_view_->SetImage(image);
281 return;
284 if (icon_view_->icon_size() == 0) {
285 SetShadowedImage(image);
286 return;
289 // Resize the image maintaining our aspect ratio.
290 int pref = icon_view_->icon_size();
291 float aspect_ratio =
292 static_cast<float>(image.width()) / static_cast<float>(image.height());
293 int height = pref;
294 int width = static_cast<int>(aspect_ratio * height);
295 if (width > pref) {
296 width = pref;
297 height = static_cast<int>(width / aspect_ratio);
300 if (width == image.width() && height == image.height()) {
301 SetShadowedImage(image);
302 return;
305 SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(image,
306 skia::ImageOperations::RESIZE_BEST, gfx::Size(width, height)));
309 const gfx::ImageSkia& ShelfButton::GetImage() const {
310 return icon_view_->GetImage();
313 void ShelfButton::AddState(State state) {
314 if (!(state_ & state)) {
315 state_ |= state;
316 Layout();
317 if (state & STATE_ATTENTION)
318 bar_->ShowAttention(true);
322 void ShelfButton::ClearState(State state) {
323 if (state_ & state) {
324 state_ &= ~state;
325 Layout();
326 if (state & STATE_ATTENTION)
327 bar_->ShowAttention(false);
331 gfx::Rect ShelfButton::GetIconBounds() const {
332 return icon_view_->bounds();
335 void ShelfButton::ShowContextMenu(const gfx::Point& p,
336 ui::MenuSourceType source_type) {
337 if (!context_menu_controller())
338 return;
340 bool destroyed = false;
341 destroyed_flag_ = &destroyed;
343 CustomButton::ShowContextMenu(p, source_type);
345 if (!destroyed) {
346 destroyed_flag_ = NULL;
347 // The menu will not propagate mouse events while its shown. To address,
348 // the hover state gets cleared once the menu was shown (and this was not
349 // destroyed).
350 ClearState(STATE_HOVERED);
354 const char* ShelfButton::GetClassName() const {
355 return kViewClassName;
358 bool ShelfButton::OnMousePressed(const ui::MouseEvent& event) {
359 CustomButton::OnMousePressed(event);
360 host_->PointerPressedOnButton(this, ShelfButtonHost::MOUSE, event);
361 return true;
364 void ShelfButton::OnMouseReleased(const ui::MouseEvent& event) {
365 CustomButton::OnMouseReleased(event);
366 host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, false);
369 void ShelfButton::OnMouseCaptureLost() {
370 ClearState(STATE_HOVERED);
371 host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, true);
372 CustomButton::OnMouseCaptureLost();
375 bool ShelfButton::OnMouseDragged(const ui::MouseEvent& event) {
376 CustomButton::OnMouseDragged(event);
377 host_->PointerDraggedOnButton(this, ShelfButtonHost::MOUSE, event);
378 return true;
381 void ShelfButton::OnMouseMoved(const ui::MouseEvent& event) {
382 CustomButton::OnMouseMoved(event);
383 host_->MouseMovedOverButton(this);
386 void ShelfButton::OnMouseEntered(const ui::MouseEvent& event) {
387 AddState(STATE_HOVERED);
388 CustomButton::OnMouseEntered(event);
389 host_->MouseEnteredButton(this);
392 void ShelfButton::OnMouseExited(const ui::MouseEvent& event) {
393 ClearState(STATE_HOVERED);
394 CustomButton::OnMouseExited(event);
395 host_->MouseExitedButton(this);
398 void ShelfButton::GetAccessibleState(ui::AXViewState* state) {
399 state->role = ui::AX_ROLE_BUTTON;
400 state->name = host_->GetAccessibleName(this);
403 void ShelfButton::Layout() {
404 const gfx::Rect button_bounds(GetContentsBounds());
405 int icon_pad =
406 shelf_layout_manager_->GetAlignment() != SHELF_ALIGNMENT_BOTTOM ?
407 kIconPadVertical : kIconPad;
408 int x_offset = shelf_layout_manager_->PrimaryAxisValue(0, icon_pad);
409 int y_offset = shelf_layout_manager_->PrimaryAxisValue(icon_pad, 0);
411 int icon_width = std::min(kIconSize,
412 button_bounds.width() - x_offset);
413 int icon_height = std::min(kIconSize,
414 button_bounds.height() - y_offset);
416 // If on the left or top 'invert' the inset so the constant gap is on
417 // the interior (towards the center of display) edge of the shelf.
418 if (SHELF_ALIGNMENT_LEFT == shelf_layout_manager_->GetAlignment())
419 x_offset = button_bounds.width() - (kIconSize + icon_pad);
421 if (SHELF_ALIGNMENT_TOP == shelf_layout_manager_->GetAlignment())
422 y_offset = button_bounds.height() - (kIconSize + icon_pad);
424 // Center icon with respect to the secondary axis, and ensure
425 // that the icon doesn't occlude the bar highlight.
426 if (shelf_layout_manager_->IsHorizontalAlignment()) {
427 x_offset = std::max(0, button_bounds.width() - icon_width) / 2;
428 if (y_offset + icon_height + kBarSize > button_bounds.height())
429 icon_height = button_bounds.height() - (y_offset + kBarSize);
430 } else {
431 y_offset = std::max(0, button_bounds.height() - icon_height) / 2;
432 if (x_offset + icon_width + kBarSize > button_bounds.width())
433 icon_width = button_bounds.width() - (x_offset + kBarSize);
436 // Expand bounds to include shadows.
437 gfx::Insets insets_shadows = gfx::ShadowValue::GetMargin(icon_shadows_);
438 // Adjust offsets to center icon, not icon + shadow.
439 x_offset += (insets_shadows.left() - insets_shadows.right()) / 2;
440 y_offset += (insets_shadows.top() - insets_shadows.bottom()) / 2;
441 gfx::Rect icon_view_bounds =
442 gfx::Rect(button_bounds.x() + x_offset, button_bounds.y() + y_offset,
443 icon_width, icon_height);
444 icon_view_bounds.Inset(insets_shadows);
445 icon_view_->SetBoundsRect(icon_view_bounds);
447 // Icon size has been incorrect when running
448 // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see
449 // http://crbug.com/234854.
450 DCHECK_LE(icon_width, kIconSize);
451 DCHECK_LE(icon_height, kIconSize);
453 bar_->SetBarBoundsRect(button_bounds);
455 UpdateState();
458 void ShelfButton::ChildPreferredSizeChanged(views::View* child) {
459 Layout();
462 void ShelfButton::OnFocus() {
463 AddState(STATE_FOCUSED);
464 CustomButton::OnFocus();
467 void ShelfButton::OnBlur() {
468 ClearState(STATE_FOCUSED);
469 CustomButton::OnBlur();
472 void ShelfButton::OnPaint(gfx::Canvas* canvas) {
473 CustomButton::OnPaint(canvas);
474 if (HasFocus()) {
475 gfx::Rect paint_bounds(GetLocalBounds());
476 paint_bounds.Inset(1, 1, 1, 1);
477 canvas->DrawSolidFocusRect(paint_bounds, kFocusBorderColor);
481 void ShelfButton::OnGestureEvent(ui::GestureEvent* event) {
482 switch (event->type()) {
483 case ui::ET_GESTURE_TAP_DOWN:
484 AddState(STATE_HOVERED);
485 return CustomButton::OnGestureEvent(event);
486 case ui::ET_GESTURE_END:
487 ClearState(STATE_HOVERED);
488 return CustomButton::OnGestureEvent(event);
489 case ui::ET_GESTURE_SCROLL_BEGIN:
490 host_->PointerPressedOnButton(this, ShelfButtonHost::TOUCH, *event);
491 event->SetHandled();
492 return;
493 case ui::ET_GESTURE_SCROLL_UPDATE:
494 host_->PointerDraggedOnButton(this, ShelfButtonHost::TOUCH, *event);
495 event->SetHandled();
496 return;
497 case ui::ET_GESTURE_SCROLL_END:
498 case ui::ET_SCROLL_FLING_START:
499 host_->PointerReleasedOnButton(this, ShelfButtonHost::TOUCH, false);
500 event->SetHandled();
501 return;
502 default:
503 return CustomButton::OnGestureEvent(event);
507 void ShelfButton::Init() {
508 icon_view_ = CreateIconView();
510 // TODO: refactor the layers so each button doesn't require 2.
511 icon_view_->SetPaintToLayer(true);
512 icon_view_->SetFillsBoundsOpaquely(false);
513 icon_view_->SetHorizontalAlignment(views::ImageView::CENTER);
514 icon_view_->SetVerticalAlignment(views::ImageView::LEADING);
516 AddChildView(icon_view_);
519 ShelfButton::IconView* ShelfButton::CreateIconView() {
520 return new IconView;
523 bool ShelfButton::IsShelfHorizontal() const {
524 return shelf_layout_manager_->IsHorizontalAlignment();
527 void ShelfButton::UpdateState() {
528 UpdateBar();
530 icon_view_->SetHorizontalAlignment(
531 shelf_layout_manager_->PrimaryAxisValue(views::ImageView::CENTER,
532 views::ImageView::LEADING));
533 icon_view_->SetVerticalAlignment(
534 shelf_layout_manager_->PrimaryAxisValue(views::ImageView::LEADING,
535 views::ImageView::CENTER));
536 SchedulePaint();
539 void ShelfButton::UpdateBar() {
540 if (state_ & STATE_HIDDEN) {
541 bar_->SetVisible(false);
542 return;
545 int bar_id = 0;
546 if (state_ & (STATE_ACTIVE))
547 bar_id = IDR_ASH_SHELF_UNDERLINE_ACTIVE;
548 else if (state_ & STATE_ATTENTION)
549 bar_id = IDR_ASH_SHELF_UNDERLINE_ATTENTION;
550 else if (state_ & STATE_RUNNING)
551 bar_id = IDR_ASH_SHELF_UNDERLINE_RUNNING;
553 if (bar_id != 0) {
554 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
555 const gfx::ImageSkia* image = rb.GetImageNamed(bar_id).ToImageSkia();
556 if (shelf_layout_manager_->GetAlignment() == SHELF_ALIGNMENT_BOTTOM) {
557 bar_->SetImage(*image);
558 } else {
559 bar_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(*image,
560 shelf_layout_manager_->SelectValueForShelfAlignment(
561 SkBitmapOperations::ROTATION_90_CW,
562 SkBitmapOperations::ROTATION_90_CW,
563 SkBitmapOperations::ROTATION_270_CW,
564 SkBitmapOperations::ROTATION_180_CW)));
566 bar_->SetHorizontalAlignment(
567 shelf_layout_manager_->SelectValueForShelfAlignment(
568 views::ImageView::CENTER,
569 views::ImageView::LEADING,
570 views::ImageView::TRAILING,
571 views::ImageView::CENTER));
572 bar_->SetVerticalAlignment(
573 shelf_layout_manager_->SelectValueForShelfAlignment(
574 views::ImageView::TRAILING,
575 views::ImageView::CENTER,
576 views::ImageView::CENTER,
577 views::ImageView::LEADING));
578 bar_->SchedulePaint();
581 bar_->SetVisible(bar_id != 0 && state_ != STATE_NORMAL);
584 } // namespace ash