1 // Copyright (c) 2012 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/launcher/launcher_button.h"
9 #include "ash/ash_switches.h"
10 #include "ash/shelf/shelf_button_host.h"
11 #include "ash/shelf/shelf_layout_manager.h"
12 #include "grit/ash_resources.h"
13 #include "skia/ext/image_operations.h"
14 #include "ui/base/accessibility/accessible_view_state.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/compositor/layer.h"
17 #include "ui/compositor/scoped_layer_animation_settings.h"
18 #include "ui/events/event_constants.h"
19 #include "ui/gfx/animation/animation_delegate.h"
20 #include "ui/gfx/animation/throb_animation.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/image/image_skia_operations.h"
24 #include "ui/gfx/skbitmap_operations.h"
25 #include "ui/views/controls/image_view.h"
29 // Size of the bar. This is along the opposite axis of the shelf. For example,
30 // if the shelf is aligned horizontally then this is the height of the bar.
31 const int kBarSize
= 3;
32 const int kIconSize
= 32;
33 const int kHopSpacing
= 2;
34 const int kIconPad
= 8;
35 const int kAlternateIconPad
= 5;
36 const int kAlternateIconPadVertical
= 6;
37 const int kHopUpMS
= 0;
38 const int kHopDownMS
= 200;
39 const int kAttentionThrobDurationMS
= 800;
41 bool ShouldHop(int state
) {
42 return state
& ash::internal::LauncherButton::STATE_HOVERED
||
43 state
& ash::internal::LauncherButton::STATE_ACTIVE
||
44 state
& ash::internal::LauncherButton::STATE_FOCUSED
;
47 // Simple AnimationDelegate that owns a single ThrobAnimation instance to
48 // keep all Draw Attention animations in sync.
49 class LauncherButtonAnimation
: public gfx::AnimationDelegate
{
53 virtual void AnimationProgressed() = 0;
56 virtual ~Observer() {}
59 static LauncherButtonAnimation
* GetInstance() {
60 static LauncherButtonAnimation
* s_instance
= new LauncherButtonAnimation();
64 void AddObserver(Observer
* observer
) {
65 observers_
.AddObserver(observer
);
68 void RemoveObserver(Observer
* observer
) {
69 observers_
.RemoveObserver(observer
);
70 if (!observers_
.might_have_observers())
75 return GetThrobAnimation().CurrentValueBetween(0, 255);
78 double GetAnimation() {
79 return GetThrobAnimation().GetCurrentValue();
83 LauncherButtonAnimation()
85 animation_
.SetThrobDuration(kAttentionThrobDurationMS
);
86 animation_
.SetTweenType(gfx::Tween::SMOOTH_IN_OUT
);
89 virtual ~LauncherButtonAnimation() {
92 gfx::ThrobAnimation
& GetThrobAnimation() {
93 if (!animation_
.is_animating()) {
95 animation_
.StartThrobbing(-1 /*throb indefinitely*/);
100 // gfx::AnimationDelegate
101 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
{
102 if (animation
!= &animation_
)
104 if (!animation_
.is_animating())
106 FOR_EACH_OBSERVER(Observer
, observers_
, AnimationProgressed());
109 gfx::ThrobAnimation animation_
;
110 ObserverList
<Observer
> observers_
;
112 DISALLOW_COPY_AND_ASSIGN(LauncherButtonAnimation
);
120 ////////////////////////////////////////////////////////////////////////////////
121 // LauncherButton::BarView
123 class LauncherButton::BarView
: public views::ImageView
,
124 public LauncherButtonAnimation::Observer
{
126 BarView(LauncherButton
* host
)
128 show_attention_(false) {
133 LauncherButtonAnimation::GetInstance()->RemoveObserver(this);
137 virtual bool HitTestRect(const gfx::Rect
& rect
) const OVERRIDE
{
138 // Allow Mouse...() messages to go to the parent view.
142 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
143 if (show_attention_
) {
144 int alpha
= LauncherButtonAnimation::GetInstance()->GetAlpha();
145 canvas
->SaveLayerAlpha(alpha
);
146 views::ImageView::OnPaint(canvas
);
149 views::ImageView::OnPaint(canvas
);
153 // LauncherButtonAnimation::Observer
154 virtual void AnimationProgressed() OVERRIDE
{
159 void SetBarBoundsRect(const gfx::Rect
& bounds
) {
160 base_bounds_
= bounds
;
164 void ShowAttention(bool show
) {
165 if (show_attention_
!= show
) {
166 show_attention_
= show
;
168 LauncherButtonAnimation::GetInstance()->AddObserver(this);
170 LauncherButtonAnimation::GetInstance()->RemoveObserver(this);
176 void UpdateBounds() {
177 gfx::Rect bounds
= base_bounds_
;
178 if (show_attention_
) {
179 // Scale from .35 to 1.0 of the total width (which is wider than the
180 // visible width of the image, so the animation "rests" briefly at full
182 double animation
= LauncherButtonAnimation::GetInstance()->GetAnimation();
183 double scale
= (.35 + .65 * animation
);
184 if (host_
->shelf_layout_manager()->GetAlignment() ==
185 SHELF_ALIGNMENT_BOTTOM
) {
186 bounds
.set_width(base_bounds_
.width() * scale
);
187 int x_offset
= (base_bounds_
.width() - bounds
.width()) / 2;
188 bounds
.set_x(base_bounds_
.x() + x_offset
);
190 bounds
.set_height(base_bounds_
.height() * scale
);
191 int y_offset
= (base_bounds_
.height() - bounds
.height()) / 2;
192 bounds
.set_y(base_bounds_
.y() + y_offset
);
195 SetBoundsRect(bounds
);
198 LauncherButton
* host_
;
199 bool show_attention_
;
200 gfx::Rect base_bounds_
;
202 DISALLOW_COPY_AND_ASSIGN(BarView
);
205 ////////////////////////////////////////////////////////////////////////////////
206 // LauncherButton::IconView
208 LauncherButton::IconView::IconView() : icon_size_(kIconSize
) {
211 LauncherButton::IconView::~IconView() {
214 bool LauncherButton::IconView::HitTestRect(const gfx::Rect
& rect
) const {
215 // Return false so that LauncherButton gets all the mouse events.
219 ////////////////////////////////////////////////////////////////////////////////
222 LauncherButton
* LauncherButton::Create(
223 views::ButtonListener
* listener
,
224 ShelfButtonHost
* host
,
225 ShelfLayoutManager
* shelf_layout_manager
) {
226 LauncherButton
* button
=
227 new LauncherButton(listener
, host
, shelf_layout_manager
);
232 LauncherButton::LauncherButton(views::ButtonListener
* listener
,
233 ShelfButtonHost
* host
,
234 ShelfLayoutManager
* shelf_layout_manager
)
235 : CustomButton(listener
),
238 bar_(new BarView(this)),
239 state_(STATE_NORMAL
),
240 shelf_layout_manager_(shelf_layout_manager
),
241 destroyed_flag_(NULL
) {
242 set_accessibility_focusable(true);
244 const gfx::ShadowValue kShadows
[] = {
245 gfx::ShadowValue(gfx::Point(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
246 gfx::ShadowValue(gfx::Point(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
247 gfx::ShadowValue(gfx::Point(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)),
249 icon_shadows_
.assign(kShadows
, kShadows
+ arraysize(kShadows
));
254 LauncherButton::~LauncherButton() {
256 *destroyed_flag_
= true;
259 void LauncherButton::SetShadowedImage(const gfx::ImageSkia
& image
) {
260 icon_view_
->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
261 image
, icon_shadows_
));
264 void LauncherButton::SetImage(const gfx::ImageSkia
& image
) {
265 if (image
.isNull()) {
266 // TODO: need an empty image.
267 icon_view_
->SetImage(image
);
271 if (icon_view_
->icon_size() == 0) {
272 SetShadowedImage(image
);
276 // Resize the image maintaining our aspect ratio.
277 int pref
= icon_view_
->icon_size();
279 static_cast<float>(image
.width()) / static_cast<float>(image
.height());
281 int width
= static_cast<int>(aspect_ratio
* height
);
284 height
= static_cast<int>(width
/ aspect_ratio
);
287 if (width
== image
.width() && height
== image
.height()) {
288 SetShadowedImage(image
);
292 SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(image
,
293 skia::ImageOperations::RESIZE_BEST
, gfx::Size(width
, height
)));
296 const gfx::ImageSkia
& LauncherButton::GetImage() const {
297 return icon_view_
->GetImage();
300 void LauncherButton::AddState(State state
) {
301 if (!(state_
& state
)) {
302 if (!ash::switches::UseAlternateShelfLayout() &&
303 (ShouldHop(state
) || !ShouldHop(state_
))) {
304 ui::ScopedLayerAnimationSettings
scoped_setter(
305 icon_view_
->layer()->GetAnimator());
306 scoped_setter
.SetTransitionDuration(
307 base::TimeDelta::FromMilliseconds(kHopUpMS
));
311 if (state
& STATE_ATTENTION
)
312 bar_
->ShowAttention(true);
316 void LauncherButton::ClearState(State state
) {
317 if (state_
& state
) {
318 if (!ash::switches::UseAlternateShelfLayout() &&
319 (!ShouldHop(state
) || ShouldHop(state_
))) {
320 ui::ScopedLayerAnimationSettings
scoped_setter(
321 icon_view_
->layer()->GetAnimator());
322 scoped_setter
.SetTweenType(gfx::Tween::LINEAR
);
323 scoped_setter
.SetTransitionDuration(
324 base::TimeDelta::FromMilliseconds(kHopDownMS
));
328 if (state
& STATE_ATTENTION
)
329 bar_
->ShowAttention(false);
333 gfx::Rect
LauncherButton::GetIconBounds() const {
334 return icon_view_
->bounds();
337 void LauncherButton::ShowContextMenu(const gfx::Point
& p
,
338 ui::MenuSourceType source_type
) {
339 if (!context_menu_controller())
342 bool destroyed
= false;
343 destroyed_flag_
= &destroyed
;
345 CustomButton::ShowContextMenu(p
, source_type
);
348 destroyed_flag_
= NULL
;
349 // The menu will not propagate mouse events while its shown. To address,
350 // the hover state gets cleared once the menu was shown (and this was not
352 ClearState(STATE_HOVERED
);
356 bool LauncherButton::OnMousePressed(const ui::MouseEvent
& event
) {
357 CustomButton::OnMousePressed(event
);
358 host_
->PointerPressedOnButton(this, ShelfButtonHost::MOUSE
, event
);
362 void LauncherButton::OnMouseReleased(const ui::MouseEvent
& event
) {
363 CustomButton::OnMouseReleased(event
);
364 host_
->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE
, false);
367 void LauncherButton::OnMouseCaptureLost() {
368 ClearState(STATE_HOVERED
);
369 host_
->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE
, true);
370 CustomButton::OnMouseCaptureLost();
373 bool LauncherButton::OnMouseDragged(const ui::MouseEvent
& event
) {
374 CustomButton::OnMouseDragged(event
);
375 host_
->PointerDraggedOnButton(this, ShelfButtonHost::MOUSE
, event
);
379 void LauncherButton::OnMouseMoved(const ui::MouseEvent
& event
) {
380 CustomButton::OnMouseMoved(event
);
381 host_
->MouseMovedOverButton(this);
384 void LauncherButton::OnMouseEntered(const ui::MouseEvent
& event
) {
385 AddState(STATE_HOVERED
);
386 CustomButton::OnMouseEntered(event
);
387 host_
->MouseEnteredButton(this);
390 void LauncherButton::OnMouseExited(const ui::MouseEvent
& event
) {
391 ClearState(STATE_HOVERED
);
392 CustomButton::OnMouseExited(event
);
393 host_
->MouseExitedButton(this);
396 void LauncherButton::GetAccessibleState(ui::AccessibleViewState
* state
) {
397 state
->role
= ui::AccessibilityTypes::ROLE_PUSHBUTTON
;
398 state
->name
= host_
->GetAccessibleName(this);
401 void LauncherButton::Layout() {
402 const gfx::Rect
button_bounds(GetContentsBounds());
403 int icon_pad
= kIconPad
;
404 if (ash::switches::UseAlternateShelfLayout()) {
406 shelf_layout_manager_
->GetAlignment() != SHELF_ALIGNMENT_BOTTOM
?
407 kAlternateIconPadVertical
: kAlternateIconPad
;
409 int x_offset
= shelf_layout_manager_
->PrimaryAxisValue(0, icon_pad
);
410 int y_offset
= shelf_layout_manager_
->PrimaryAxisValue(icon_pad
, 0);
412 int icon_width
= std::min(kIconSize
,
413 button_bounds
.width() - x_offset
);
414 int icon_height
= std::min(kIconSize
,
415 button_bounds
.height() - y_offset
);
417 // If on the left or top 'invert' the inset so the constant gap is on
418 // the interior (towards the center of display) edge of the shelf.
419 if (SHELF_ALIGNMENT_LEFT
== shelf_layout_manager_
->GetAlignment())
420 x_offset
= button_bounds
.width() - (kIconSize
+ icon_pad
);
422 if (SHELF_ALIGNMENT_TOP
== shelf_layout_manager_
->GetAlignment())
423 y_offset
= button_bounds
.height() - (kIconSize
+ icon_pad
);
425 if (ShouldHop(state_
) && !ash::switches::UseAlternateShelfLayout()) {
426 x_offset
+= shelf_layout_manager_
->SelectValueForShelfAlignment(
427 0, kHopSpacing
, -kHopSpacing
, 0);
428 y_offset
+= shelf_layout_manager_
->SelectValueForShelfAlignment(
429 -kHopSpacing
, 0, 0, kHopSpacing
);
432 // Center icon with respect to the secondary axis, and ensure
433 // that the icon doesn't occlude the bar highlight.
434 if (shelf_layout_manager_
->IsHorizontalAlignment()) {
435 x_offset
= std::max(0, button_bounds
.width() - icon_width
) / 2;
436 if (y_offset
+ icon_height
+ kBarSize
> button_bounds
.height())
437 icon_height
= button_bounds
.height() - (y_offset
+ kBarSize
);
439 y_offset
= std::max(0, button_bounds
.height() - icon_height
) / 2;
440 if (x_offset
+ icon_width
+ kBarSize
> button_bounds
.width())
441 icon_width
= button_bounds
.width() - (x_offset
+ kBarSize
);
444 icon_view_
->SetBoundsRect(gfx::Rect(
445 button_bounds
.x() + x_offset
,
446 button_bounds
.y() + y_offset
,
450 // Icon size has been incorrect when running
451 // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see
452 // http://crbug.com/234854.
453 DCHECK_LE(icon_width
, kIconSize
);
454 DCHECK_LE(icon_height
, kIconSize
);
456 bar_
->SetBarBoundsRect(button_bounds
);
461 void LauncherButton::ChildPreferredSizeChanged(views::View
* child
) {
465 void LauncherButton::OnFocus() {
466 AddState(STATE_FOCUSED
);
467 CustomButton::OnFocus();
470 void LauncherButton::OnBlur() {
471 ClearState(STATE_FOCUSED
);
472 CustomButton::OnBlur();
475 void LauncherButton::OnGestureEvent(ui::GestureEvent
* event
) {
476 switch (event
->type()) {
477 case ui::ET_GESTURE_TAP_DOWN
:
478 AddState(STATE_HOVERED
);
479 return CustomButton::OnGestureEvent(event
);
480 case ui::ET_GESTURE_END
:
481 ClearState(STATE_HOVERED
);
482 return CustomButton::OnGestureEvent(event
);
483 case ui::ET_GESTURE_SCROLL_BEGIN
:
484 host_
->PointerPressedOnButton(this, ShelfButtonHost::TOUCH
, *event
);
487 case ui::ET_GESTURE_SCROLL_UPDATE
:
488 host_
->PointerDraggedOnButton(this, ShelfButtonHost::TOUCH
, *event
);
491 case ui::ET_GESTURE_SCROLL_END
:
492 case ui::ET_SCROLL_FLING_START
:
493 host_
->PointerReleasedOnButton(this, ShelfButtonHost::TOUCH
, false);
497 return CustomButton::OnGestureEvent(event
);
501 void LauncherButton::Init() {
502 icon_view_
= CreateIconView();
504 // TODO: refactor the layers so each button doesn't require 2.
505 icon_view_
->SetPaintToLayer(true);
506 icon_view_
->SetFillsBoundsOpaquely(false);
507 icon_view_
->SetHorizontalAlignment(views::ImageView::CENTER
);
508 icon_view_
->SetVerticalAlignment(views::ImageView::LEADING
);
510 AddChildView(icon_view_
);
513 LauncherButton::IconView
* LauncherButton::CreateIconView() {
517 bool LauncherButton::IsShelfHorizontal() const {
518 return shelf_layout_manager_
->IsHorizontalAlignment();
521 void LauncherButton::UpdateState() {
524 icon_view_
->SetHorizontalAlignment(
525 shelf_layout_manager_
->PrimaryAxisValue(views::ImageView::CENTER
,
526 views::ImageView::LEADING
));
527 icon_view_
->SetVerticalAlignment(
528 shelf_layout_manager_
->PrimaryAxisValue(views::ImageView::LEADING
,
529 views::ImageView::CENTER
));
533 void LauncherButton::UpdateBar() {
534 if (state_
& STATE_HIDDEN
) {
535 bar_
->SetVisible(false);
540 if (ash::switches::UseAlternateShelfLayout()) {
541 if (state_
& STATE_ACTIVE
)
542 bar_id
= IDR_AURA_LAUNCHER_UNDERLINE_ACTIVE_ALTERNATE
;
543 else if (state_
& STATE_RUNNING
)
544 bar_id
= IDR_AURA_LAUNCHER_UNDERLINE_RUNNING_ALTERNATE
;
546 if (state_
& (STATE_ACTIVE
| STATE_ATTENTION
))
547 bar_id
= IDR_AURA_LAUNCHER_UNDERLINE_ACTIVE
;
548 else if (state_
& (STATE_HOVERED
| STATE_FOCUSED
))
549 bar_id
= IDR_AURA_LAUNCHER_UNDERLINE_HOVER
;
551 bar_id
= IDR_AURA_LAUNCHER_UNDERLINE_RUNNING
;
555 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
556 const gfx::ImageSkia
* image
= rb
.GetImageNamed(bar_id
).ToImageSkia();
557 if (shelf_layout_manager_
->GetAlignment() == SHELF_ALIGNMENT_BOTTOM
) {
558 bar_
->SetImage(*image
);
560 bar_
->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(*image
,
561 shelf_layout_manager_
->SelectValueForShelfAlignment(
562 SkBitmapOperations::ROTATION_90_CW
,
563 SkBitmapOperations::ROTATION_90_CW
,
564 SkBitmapOperations::ROTATION_270_CW
,
565 SkBitmapOperations::ROTATION_180_CW
)));
567 bar_
->SetHorizontalAlignment(
568 shelf_layout_manager_
->SelectValueForShelfAlignment(
569 views::ImageView::CENTER
,
570 views::ImageView::LEADING
,
571 views::ImageView::TRAILING
,
572 views::ImageView::CENTER
));
573 bar_
->SetVerticalAlignment(
574 shelf_layout_manager_
->SelectValueForShelfAlignment(
575 views::ImageView::TRAILING
,
576 views::ImageView::CENTER
,
577 views::ImageView::CENTER
,
578 views::ImageView::LEADING
));
579 bar_
->SchedulePaint();
582 bar_
->SetVisible(bar_id
!= 0 && state_
!= STATE_NORMAL
);
585 } // namespace internal