Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ash / shelf / shelf_button.cc
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 "ash/shelf/shelf_button.h"
6
7 #include <algorithm>
8
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"
27
28 namespace {
29
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;
37
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;
45
46    protected:
47     virtual ~Observer() {}
48   };
49
50   static ShelfButtonAnimation* GetInstance() {
51     static ShelfButtonAnimation* s_instance = new ShelfButtonAnimation();
52     return s_instance;
53   }
54
55   void AddObserver(Observer* observer) {
56     observers_.AddObserver(observer);
57   }
58
59   void RemoveObserver(Observer* observer) {
60     observers_.RemoveObserver(observer);
61     if (!observers_.might_have_observers())
62       animation_.Stop();
63   }
64
65   int GetAlpha() {
66     return GetThrobAnimation().CurrentValueBetween(0, 255);
67   }
68
69   double GetAnimation() {
70     return GetThrobAnimation().GetCurrentValue();
71   }
72
73  private:
74   ShelfButtonAnimation()
75       : animation_(this) {
76     animation_.SetThrobDuration(kAttentionThrobDurationMS);
77     animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT);
78   }
79
80   virtual ~ShelfButtonAnimation() {
81   }
82
83   gfx::ThrobAnimation& GetThrobAnimation() {
84     if (!animation_.is_animating()) {
85       animation_.Reset();
86       animation_.StartThrobbing(-1 /*throb indefinitely*/);
87     }
88     return animation_;
89   }
90
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());
98   }
99
100   gfx::ThrobAnimation animation_;
101   ObserverList<Observer> observers_;
102
103   DISALLOW_COPY_AND_ASSIGN(ShelfButtonAnimation);
104 };
105
106 }  // namespace
107
108 namespace ash {
109
110 ////////////////////////////////////////////////////////////////////////////////
111 // ShelfButton::BarView
112
113 class ShelfButton::BarView : public views::ImageView,
114                              public ShelfButtonAnimation::Observer {
115  public:
116   BarView(ShelfButton* host)
117       : host_(host),
118         show_attention_(false) {
119     // Make sure the events reach the parent view for handling.
120     set_interactive(false);
121   }
122
123   virtual ~BarView() {
124     if (show_attention_)
125       ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
126   }
127
128   // views::View:
129   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
130     if (show_attention_) {
131       int alpha = ShelfButtonAnimation::GetInstance()->GetAlpha();
132       canvas->SaveLayerAlpha(alpha);
133       views::ImageView::OnPaint(canvas);
134       canvas->Restore();
135     } else {
136       views::ImageView::OnPaint(canvas);
137     }
138   }
139
140   // ShelfButtonAnimation::Observer
141   virtual void AnimationProgressed() OVERRIDE {
142     UpdateBounds();
143     SchedulePaint();
144   }
145
146   void SetBarBoundsRect(const gfx::Rect& bounds) {
147     base_bounds_ = bounds;
148     UpdateBounds();
149   }
150
151   void ShowAttention(bool show) {
152     if (show_attention_ != show) {
153       show_attention_ = show;
154       if (show_attention_)
155         ShelfButtonAnimation::GetInstance()->AddObserver(this);
156       else
157         ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
158     }
159     UpdateBounds();
160   }
161
162  private:
163   void UpdateBounds() {
164     gfx::Rect bounds = base_bounds_;
165     if (show_attention_) {
166       // Scale from .35 to 1.0 of the total width (which is wider than the
167       // visible width of the image, so the animation "rests" briefly at full
168       // visible width.
169       double animation = ShelfButtonAnimation::GetInstance()->GetAnimation();
170       double scale = (.35 + .65 * animation);
171       if (host_->shelf_layout_manager()->GetAlignment() ==
172           SHELF_ALIGNMENT_BOTTOM) {
173         bounds.set_width(base_bounds_.width() * scale);
174         int x_offset = (base_bounds_.width() - bounds.width()) / 2;
175         bounds.set_x(base_bounds_.x() + x_offset);
176       } else {
177         bounds.set_height(base_bounds_.height() * scale);
178         int y_offset = (base_bounds_.height() - bounds.height()) / 2;
179         bounds.set_y(base_bounds_.y() + y_offset);
180       }
181     }
182     SetBoundsRect(bounds);
183   }
184
185   ShelfButton* host_;
186   bool show_attention_;
187   gfx::Rect base_bounds_;
188
189   DISALLOW_COPY_AND_ASSIGN(BarView);
190 };
191
192 ////////////////////////////////////////////////////////////////////////////////
193 // ShelfButton::IconView
194
195 ShelfButton::IconView::IconView() : icon_size_(kIconSize) {
196   // Do not make this interactive, so that events are sent to ShelfView for
197   // handling.
198   set_interactive(false);
199 }
200
201 ShelfButton::IconView::~IconView() {
202 }
203
204 ////////////////////////////////////////////////////////////////////////////////
205 // ShelfButton
206
207 ShelfButton* ShelfButton::Create(views::ButtonListener* listener,
208                                  ShelfButtonHost* host,
209                                  ShelfLayoutManager* shelf_layout_manager) {
210   ShelfButton* button = new ShelfButton(listener, host, shelf_layout_manager);
211   button->Init();
212   return button;
213 }
214
215 ShelfButton::ShelfButton(views::ButtonListener* listener,
216                          ShelfButtonHost* host,
217                          ShelfLayoutManager* shelf_layout_manager)
218     : CustomButton(listener),
219       host_(host),
220       icon_view_(NULL),
221       bar_(new BarView(this)),
222       state_(STATE_NORMAL),
223       shelf_layout_manager_(shelf_layout_manager),
224       destroyed_flag_(NULL) {
225   SetAccessibilityFocusable(true);
226
227   const gfx::ShadowValue kShadows[] = {
228     gfx::ShadowValue(gfx::Point(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
229     gfx::ShadowValue(gfx::Point(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
230     gfx::ShadowValue(gfx::Point(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)),
231   };
232   icon_shadows_.assign(kShadows, kShadows + arraysize(kShadows));
233
234   AddChildView(bar_);
235 }
236
237 ShelfButton::~ShelfButton() {
238   if (destroyed_flag_)
239     *destroyed_flag_ = true;
240 }
241
242 void ShelfButton::SetShadowedImage(const gfx::ImageSkia& image) {
243   icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
244       image, icon_shadows_));
245 }
246
247 void ShelfButton::SetImage(const gfx::ImageSkia& image) {
248   if (image.isNull()) {
249     // TODO: need an empty image.
250     icon_view_->SetImage(image);
251     return;
252   }
253
254   if (icon_view_->icon_size() == 0) {
255     SetShadowedImage(image);
256     return;
257   }
258
259   // Resize the image maintaining our aspect ratio.
260   int pref = icon_view_->icon_size();
261   float aspect_ratio =
262       static_cast<float>(image.width()) / static_cast<float>(image.height());
263   int height = pref;
264   int width = static_cast<int>(aspect_ratio * height);
265   if (width > pref) {
266     width = pref;
267     height = static_cast<int>(width / aspect_ratio);
268   }
269
270   if (width == image.width() && height == image.height()) {
271     SetShadowedImage(image);
272     return;
273   }
274
275   SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(image,
276       skia::ImageOperations::RESIZE_BEST, gfx::Size(width, height)));
277 }
278
279 const gfx::ImageSkia& ShelfButton::GetImage() const {
280   return icon_view_->GetImage();
281 }
282
283 void ShelfButton::AddState(State state) {
284   if (!(state_ & state)) {
285     state_ |= state;
286     Layout();
287     if (state & STATE_ATTENTION)
288       bar_->ShowAttention(true);
289   }
290 }
291
292 void ShelfButton::ClearState(State state) {
293   if (state_ & state) {
294     state_ &= ~state;
295     Layout();
296     if (state & STATE_ATTENTION)
297       bar_->ShowAttention(false);
298   }
299 }
300
301 gfx::Rect ShelfButton::GetIconBounds() const {
302   return icon_view_->bounds();
303 }
304
305 void ShelfButton::ShowContextMenu(const gfx::Point& p,
306                                   ui::MenuSourceType source_type) {
307   if (!context_menu_controller())
308     return;
309
310   bool destroyed = false;
311   destroyed_flag_ = &destroyed;
312
313   CustomButton::ShowContextMenu(p, source_type);
314
315   if (!destroyed) {
316     destroyed_flag_ = NULL;
317     // The menu will not propagate mouse events while its shown. To address,
318     // the hover state gets cleared once the menu was shown (and this was not
319     // destroyed).
320     ClearState(STATE_HOVERED);
321   }
322 }
323
324 bool ShelfButton::OnMousePressed(const ui::MouseEvent& event) {
325   CustomButton::OnMousePressed(event);
326   host_->PointerPressedOnButton(this, ShelfButtonHost::MOUSE, event);
327   return true;
328 }
329
330 void ShelfButton::OnMouseReleased(const ui::MouseEvent& event) {
331   CustomButton::OnMouseReleased(event);
332   host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, false);
333 }
334
335 void ShelfButton::OnMouseCaptureLost() {
336   ClearState(STATE_HOVERED);
337   host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, true);
338   CustomButton::OnMouseCaptureLost();
339 }
340
341 bool ShelfButton::OnMouseDragged(const ui::MouseEvent& event) {
342   CustomButton::OnMouseDragged(event);
343   host_->PointerDraggedOnButton(this, ShelfButtonHost::MOUSE, event);
344   return true;
345 }
346
347 void ShelfButton::OnMouseMoved(const ui::MouseEvent& event) {
348   CustomButton::OnMouseMoved(event);
349   host_->MouseMovedOverButton(this);
350 }
351
352 void ShelfButton::OnMouseEntered(const ui::MouseEvent& event) {
353   AddState(STATE_HOVERED);
354   CustomButton::OnMouseEntered(event);
355   host_->MouseEnteredButton(this);
356 }
357
358 void ShelfButton::OnMouseExited(const ui::MouseEvent& event) {
359   ClearState(STATE_HOVERED);
360   CustomButton::OnMouseExited(event);
361   host_->MouseExitedButton(this);
362 }
363
364 void ShelfButton::GetAccessibleState(ui::AXViewState* state) {
365   state->role = ui::AX_ROLE_BUTTON;
366   state->name = host_->GetAccessibleName(this);
367 }
368
369 void ShelfButton::Layout() {
370   const gfx::Rect button_bounds(GetContentsBounds());
371   int icon_pad =
372       shelf_layout_manager_->GetAlignment() != SHELF_ALIGNMENT_BOTTOM ?
373       kIconPadVertical : kIconPad;
374   int x_offset = shelf_layout_manager_->PrimaryAxisValue(0, icon_pad);
375   int y_offset = shelf_layout_manager_->PrimaryAxisValue(icon_pad, 0);
376
377   int icon_width = std::min(kIconSize,
378       button_bounds.width() - x_offset);
379   int icon_height = std::min(kIconSize,
380       button_bounds.height() - y_offset);
381
382   // If on the left or top 'invert' the inset so the constant gap is on
383   // the interior (towards the center of display) edge of the shelf.
384   if (SHELF_ALIGNMENT_LEFT == shelf_layout_manager_->GetAlignment())
385     x_offset = button_bounds.width() - (kIconSize + icon_pad);
386
387   if (SHELF_ALIGNMENT_TOP == shelf_layout_manager_->GetAlignment())
388     y_offset = button_bounds.height() - (kIconSize + icon_pad);
389
390   // Center icon with respect to the secondary axis, and ensure
391   // that the icon doesn't occlude the bar highlight.
392   if (shelf_layout_manager_->IsHorizontalAlignment()) {
393     x_offset = std::max(0, button_bounds.width() - icon_width) / 2;
394     if (y_offset + icon_height + kBarSize > button_bounds.height())
395       icon_height = button_bounds.height() - (y_offset + kBarSize);
396   } else {
397     y_offset = std::max(0, button_bounds.height() - icon_height) / 2;
398     if (x_offset + icon_width + kBarSize > button_bounds.width())
399       icon_width = button_bounds.width() - (x_offset + kBarSize);
400   }
401
402   icon_view_->SetBoundsRect(gfx::Rect(
403       button_bounds.x() + x_offset,
404       button_bounds.y() + y_offset,
405       icon_width,
406       icon_height));
407
408   // Icon size has been incorrect when running
409   // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see
410   // http://crbug.com/234854.
411   DCHECK_LE(icon_width, kIconSize);
412   DCHECK_LE(icon_height, kIconSize);
413
414   bar_->SetBarBoundsRect(button_bounds);
415
416   UpdateState();
417 }
418
419 void ShelfButton::ChildPreferredSizeChanged(views::View* child) {
420   Layout();
421 }
422
423 void ShelfButton::OnFocus() {
424   AddState(STATE_FOCUSED);
425   CustomButton::OnFocus();
426 }
427
428 void ShelfButton::OnBlur() {
429   ClearState(STATE_FOCUSED);
430   CustomButton::OnBlur();
431 }
432
433 void ShelfButton::OnPaint(gfx::Canvas* canvas) {
434   CustomButton::OnPaint(canvas);
435   if (HasFocus()) {
436     gfx::Rect paint_bounds(GetLocalBounds());
437     paint_bounds.Inset(1, 1, 1, 1);
438     canvas->DrawSolidFocusRect(paint_bounds, kFocusBorderColor);
439   }
440 }
441
442 void ShelfButton::OnGestureEvent(ui::GestureEvent* event) {
443   switch (event->type()) {
444     case ui::ET_GESTURE_TAP_DOWN:
445       AddState(STATE_HOVERED);
446       return CustomButton::OnGestureEvent(event);
447     case ui::ET_GESTURE_END:
448       ClearState(STATE_HOVERED);
449       return CustomButton::OnGestureEvent(event);
450     case ui::ET_GESTURE_SCROLL_BEGIN:
451       host_->PointerPressedOnButton(this, ShelfButtonHost::TOUCH, *event);
452       event->SetHandled();
453       return;
454     case ui::ET_GESTURE_SCROLL_UPDATE:
455       host_->PointerDraggedOnButton(this, ShelfButtonHost::TOUCH, *event);
456       event->SetHandled();
457       return;
458     case ui::ET_GESTURE_SCROLL_END:
459     case ui::ET_SCROLL_FLING_START:
460       host_->PointerReleasedOnButton(this, ShelfButtonHost::TOUCH, false);
461       event->SetHandled();
462       return;
463     default:
464       return CustomButton::OnGestureEvent(event);
465   }
466 }
467
468 void ShelfButton::Init() {
469   icon_view_ = CreateIconView();
470
471   // TODO: refactor the layers so each button doesn't require 2.
472   icon_view_->SetPaintToLayer(true);
473   icon_view_->SetFillsBoundsOpaquely(false);
474   icon_view_->SetHorizontalAlignment(views::ImageView::CENTER);
475   icon_view_->SetVerticalAlignment(views::ImageView::LEADING);
476
477   AddChildView(icon_view_);
478 }
479
480 ShelfButton::IconView* ShelfButton::CreateIconView() {
481   return new IconView;
482 }
483
484 bool ShelfButton::IsShelfHorizontal() const {
485   return shelf_layout_manager_->IsHorizontalAlignment();
486 }
487
488 void ShelfButton::UpdateState() {
489   UpdateBar();
490
491   icon_view_->SetHorizontalAlignment(
492       shelf_layout_manager_->PrimaryAxisValue(views::ImageView::CENTER,
493                                               views::ImageView::LEADING));
494   icon_view_->SetVerticalAlignment(
495       shelf_layout_manager_->PrimaryAxisValue(views::ImageView::LEADING,
496                                               views::ImageView::CENTER));
497   SchedulePaint();
498 }
499
500 void ShelfButton::UpdateBar() {
501   if (state_ & STATE_HIDDEN) {
502     bar_->SetVisible(false);
503     return;
504   }
505
506   int bar_id = 0;
507   if (state_ & STATE_ACTIVE)
508     bar_id = IDR_ASH_SHELF_UNDERLINE_ACTIVE;
509   else if (state_ & STATE_RUNNING)
510     bar_id = IDR_ASH_SHELF_UNDERLINE_RUNNING;
511
512   if (bar_id != 0) {
513     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
514     const gfx::ImageSkia* image = rb.GetImageNamed(bar_id).ToImageSkia();
515     if (shelf_layout_manager_->GetAlignment() == SHELF_ALIGNMENT_BOTTOM) {
516       bar_->SetImage(*image);
517     } else {
518       bar_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(*image,
519           shelf_layout_manager_->SelectValueForShelfAlignment(
520               SkBitmapOperations::ROTATION_90_CW,
521               SkBitmapOperations::ROTATION_90_CW,
522               SkBitmapOperations::ROTATION_270_CW,
523               SkBitmapOperations::ROTATION_180_CW)));
524     }
525     bar_->SetHorizontalAlignment(
526         shelf_layout_manager_->SelectValueForShelfAlignment(
527             views::ImageView::CENTER,
528             views::ImageView::LEADING,
529             views::ImageView::TRAILING,
530             views::ImageView::CENTER));
531     bar_->SetVerticalAlignment(
532         shelf_layout_manager_->SelectValueForShelfAlignment(
533             views::ImageView::TRAILING,
534             views::ImageView::CENTER,
535             views::ImageView::CENTER,
536             views::ImageView::LEADING));
537     bar_->SchedulePaint();
538   }
539
540   bar_->SetVisible(bar_id != 0 && state_ != STATE_NORMAL);
541 }
542
543 }  // namespace ash