- add sources.
[platform/framework/web/crosswalk.git] / src / ash / launcher / launcher_button.cc
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.
4
5 #include "ash/launcher/launcher_button.h"
6
7 #include <algorithm>
8
9 #include "ash/ash_switches.h"
10 #include "ash/launcher/launcher_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"
26
27 namespace {
28
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;
40
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;
45 }
46
47 // Simple AnimationDelegate that owns a single ThrobAnimation instance to
48 // keep all Draw Attention animations in sync.
49 class LauncherButtonAnimation : public gfx::AnimationDelegate {
50  public:
51   class Observer {
52    public:
53     virtual void AnimationProgressed() = 0;
54
55    protected:
56     virtual ~Observer() {}
57   };
58
59   static LauncherButtonAnimation* GetInstance() {
60     static LauncherButtonAnimation* s_instance = new LauncherButtonAnimation();
61     return s_instance;
62   }
63
64   void AddObserver(Observer* observer) {
65     observers_.AddObserver(observer);
66   }
67
68   void RemoveObserver(Observer* observer) {
69     observers_.RemoveObserver(observer);
70     if (!observers_.might_have_observers())
71       animation_.Stop();
72   }
73
74   int GetAlpha() {
75     return GetThrobAnimation().CurrentValueBetween(0, 255);
76   }
77
78   double GetAnimation() {
79     return GetThrobAnimation().GetCurrentValue();
80   }
81
82  private:
83   LauncherButtonAnimation()
84       : animation_(this) {
85     animation_.SetThrobDuration(kAttentionThrobDurationMS);
86     animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT);
87   }
88
89   virtual ~LauncherButtonAnimation() {
90   }
91
92   gfx::ThrobAnimation& GetThrobAnimation() {
93     if (!animation_.is_animating()) {
94       animation_.Reset();
95       animation_.StartThrobbing(-1 /*throb indefinitely*/);
96     }
97     return animation_;
98   }
99
100   // gfx::AnimationDelegate
101   virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
102     if (animation != &animation_)
103       return;
104     if (!animation_.is_animating())
105       return;
106     FOR_EACH_OBSERVER(Observer, observers_, AnimationProgressed());
107   }
108
109   gfx::ThrobAnimation animation_;
110   ObserverList<Observer> observers_;
111
112   DISALLOW_COPY_AND_ASSIGN(LauncherButtonAnimation);
113 };
114
115 }  // namespace
116
117 namespace ash {
118 namespace internal {
119
120 ////////////////////////////////////////////////////////////////////////////////
121 // LauncherButton::BarView
122
123 class LauncherButton::BarView : public views::ImageView,
124                                 public LauncherButtonAnimation::Observer {
125  public:
126   BarView(LauncherButton* host)
127       : host_(host),
128         show_attention_(false) {
129   }
130
131   virtual ~BarView() {
132     if (show_attention_)
133       LauncherButtonAnimation::GetInstance()->RemoveObserver(this);
134   }
135
136   // View
137   virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE {
138     // Allow Mouse...() messages to go to the parent view.
139     return false;
140   }
141
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);
147       canvas->Restore();
148     } else {
149       views::ImageView::OnPaint(canvas);
150     }
151   }
152
153   // LauncherButtonAnimation::Observer
154   virtual void AnimationProgressed() OVERRIDE {
155     UpdateBounds();
156     SchedulePaint();
157   }
158
159   void SetBarBoundsRect(const gfx::Rect& bounds) {
160     base_bounds_ = bounds;
161     UpdateBounds();
162   }
163
164   void ShowAttention(bool show) {
165     if (show_attention_ != show) {
166       show_attention_ = show;
167       if (show_attention_)
168         LauncherButtonAnimation::GetInstance()->AddObserver(this);
169       else
170         LauncherButtonAnimation::GetInstance()->RemoveObserver(this);
171     }
172     UpdateBounds();
173   }
174
175  private:
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
181       // visible width.
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);
189       } else {
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);
193       }
194     }
195     SetBoundsRect(bounds);
196   }
197
198   LauncherButton* host_;
199   bool show_attention_;
200   gfx::Rect base_bounds_;
201
202   DISALLOW_COPY_AND_ASSIGN(BarView);
203 };
204
205 ////////////////////////////////////////////////////////////////////////////////
206 // LauncherButton::IconView
207
208 LauncherButton::IconView::IconView() : icon_size_(kIconSize) {
209 }
210
211 LauncherButton::IconView::~IconView() {
212 }
213
214 bool LauncherButton::IconView::HitTestRect(const gfx::Rect& rect) const {
215   // Return false so that LauncherButton gets all the mouse events.
216   return false;
217 }
218
219 ////////////////////////////////////////////////////////////////////////////////
220 // LauncherButton
221
222 LauncherButton* LauncherButton::Create(
223     views::ButtonListener* listener,
224     LauncherButtonHost* host,
225     ShelfLayoutManager* shelf_layout_manager) {
226   LauncherButton* button =
227       new LauncherButton(listener, host, shelf_layout_manager);
228   button->Init();
229   return button;
230 }
231
232 LauncherButton::LauncherButton(views::ButtonListener* listener,
233                                LauncherButtonHost* host,
234                                ShelfLayoutManager* shelf_layout_manager)
235     : CustomButton(listener),
236       host_(host),
237       icon_view_(NULL),
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);
243
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)),
248   };
249   icon_shadows_.assign(kShadows, kShadows + arraysize(kShadows));
250
251   AddChildView(bar_);
252 }
253
254 LauncherButton::~LauncherButton() {
255   if (destroyed_flag_)
256     *destroyed_flag_ = true;
257 }
258
259 void LauncherButton::SetShadowedImage(const gfx::ImageSkia& image) {
260   icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
261       image, icon_shadows_));
262 }
263
264 void LauncherButton::SetImage(const gfx::ImageSkia& image) {
265   if (image.isNull()) {
266     // TODO: need an empty image.
267     icon_view_->SetImage(image);
268     return;
269   }
270
271   if (icon_view_->icon_size() == 0) {
272     SetShadowedImage(image);
273     return;
274   }
275
276   // Resize the image maintaining our aspect ratio.
277   int pref = icon_view_->icon_size();
278   float aspect_ratio =
279       static_cast<float>(image.width()) / static_cast<float>(image.height());
280   int height = pref;
281   int width = static_cast<int>(aspect_ratio * height);
282   if (width > pref) {
283     width = pref;
284     height = static_cast<int>(width / aspect_ratio);
285   }
286
287   if (width == image.width() && height == image.height()) {
288     SetShadowedImage(image);
289     return;
290   }
291
292   SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(image,
293       skia::ImageOperations::RESIZE_BEST, gfx::Size(width, height)));
294 }
295
296 const gfx::ImageSkia& LauncherButton::GetImage() const {
297   return icon_view_->GetImage();
298 }
299
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));
308     }
309     state_ |= state;
310     Layout();
311     if (state & STATE_ATTENTION)
312       bar_->ShowAttention(true);
313   }
314 }
315
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));
325     }
326     state_ &= ~state;
327     Layout();
328     if (state & STATE_ATTENTION)
329       bar_->ShowAttention(false);
330   }
331 }
332
333 gfx::Rect LauncherButton::GetIconBounds() const {
334   return icon_view_->bounds();
335 }
336
337 void LauncherButton::ShowContextMenu(const gfx::Point& p,
338                                      ui::MenuSourceType source_type) {
339   if (!context_menu_controller())
340     return;
341
342   bool destroyed = false;
343   destroyed_flag_ = &destroyed;
344
345   CustomButton::ShowContextMenu(p, source_type);
346
347   if (!destroyed) {
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
351     // destroyed).
352     ClearState(STATE_HOVERED);
353   }
354 }
355
356 bool LauncherButton::OnMousePressed(const ui::MouseEvent& event) {
357   CustomButton::OnMousePressed(event);
358   host_->PointerPressedOnButton(this, LauncherButtonHost::MOUSE, event);
359   return true;
360 }
361
362 void LauncherButton::OnMouseReleased(const ui::MouseEvent& event) {
363   CustomButton::OnMouseReleased(event);
364   host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, false);
365 }
366
367 void LauncherButton::OnMouseCaptureLost() {
368   ClearState(STATE_HOVERED);
369   host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, true);
370   CustomButton::OnMouseCaptureLost();
371 }
372
373 bool LauncherButton::OnMouseDragged(const ui::MouseEvent& event) {
374   CustomButton::OnMouseDragged(event);
375   host_->PointerDraggedOnButton(this, LauncherButtonHost::MOUSE, event);
376   return true;
377 }
378
379 void LauncherButton::OnMouseMoved(const ui::MouseEvent& event) {
380   CustomButton::OnMouseMoved(event);
381   host_->MouseMovedOverButton(this);
382 }
383
384 void LauncherButton::OnMouseEntered(const ui::MouseEvent& event) {
385   AddState(STATE_HOVERED);
386   CustomButton::OnMouseEntered(event);
387   host_->MouseEnteredButton(this);
388 }
389
390 void LauncherButton::OnMouseExited(const ui::MouseEvent& event) {
391   ClearState(STATE_HOVERED);
392   CustomButton::OnMouseExited(event);
393   host_->MouseExitedButton(this);
394 }
395
396 void LauncherButton::GetAccessibleState(ui::AccessibleViewState* state) {
397   state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
398   state->name = host_->GetAccessibleName(this);
399 }
400
401 void LauncherButton::Layout() {
402   const gfx::Rect button_bounds(GetContentsBounds());
403   int icon_pad = kIconPad;
404   if (ash::switches::UseAlternateShelfLayout()) {
405       icon_pad =
406           shelf_layout_manager_->GetAlignment() != SHELF_ALIGNMENT_BOTTOM ?
407           kAlternateIconPadVertical : kAlternateIconPad;
408   }
409   int x_offset = shelf_layout_manager_->PrimaryAxisValue(0, icon_pad);
410   int y_offset = shelf_layout_manager_->PrimaryAxisValue(icon_pad, 0);
411
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);
416
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);
421
422   if (SHELF_ALIGNMENT_TOP == shelf_layout_manager_->GetAlignment())
423     y_offset = button_bounds.height() - (kIconSize + icon_pad);
424
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);
430   }
431
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);
438   } else {
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);
442   }
443
444   icon_view_->SetBoundsRect(gfx::Rect(
445       button_bounds.x() + x_offset,
446       button_bounds.y() + y_offset,
447       icon_width,
448       icon_height));
449
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);
455
456   bar_->SetBarBoundsRect(button_bounds);
457
458   UpdateState();
459 }
460
461 void LauncherButton::ChildPreferredSizeChanged(views::View* child) {
462   Layout();
463 }
464
465 void LauncherButton::OnFocus() {
466   AddState(STATE_FOCUSED);
467   CustomButton::OnFocus();
468 }
469
470 void LauncherButton::OnBlur() {
471   ClearState(STATE_FOCUSED);
472   CustomButton::OnBlur();
473 }
474
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, LauncherButtonHost::TOUCH, *event);
485       event->SetHandled();
486       return;
487     case ui::ET_GESTURE_SCROLL_UPDATE:
488       host_->PointerDraggedOnButton(this, LauncherButtonHost::TOUCH, *event);
489       event->SetHandled();
490       return;
491     case ui::ET_GESTURE_SCROLL_END:
492     case ui::ET_SCROLL_FLING_START:
493       host_->PointerReleasedOnButton(this, LauncherButtonHost::TOUCH, false);
494       event->SetHandled();
495       return;
496     default:
497       return CustomButton::OnGestureEvent(event);
498   }
499 }
500
501 void LauncherButton::Init() {
502   icon_view_ = CreateIconView();
503
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);
509
510   AddChildView(icon_view_);
511 }
512
513 LauncherButton::IconView* LauncherButton::CreateIconView() {
514   return new IconView;
515 }
516
517 bool LauncherButton::IsShelfHorizontal() const {
518   return shelf_layout_manager_->IsHorizontalAlignment();
519 }
520
521 void LauncherButton::UpdateState() {
522   UpdateBar();
523
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));
530   SchedulePaint();
531 }
532
533 void LauncherButton::UpdateBar() {
534   if (state_ & STATE_HIDDEN) {
535     bar_->SetVisible(false);
536     return;
537   }
538
539   int bar_id = 0;
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;
545   } else {
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;
550     else
551       bar_id = IDR_AURA_LAUNCHER_UNDERLINE_RUNNING;
552   }
553
554   if (bar_id != 0) {
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);
559     } else {
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)));
566     }
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();
580   }
581
582   bar_->SetVisible(bar_id != 0 && state_ != STATE_NORMAL);
583 }
584
585 }  // namespace internal
586 }  // namespace ash