• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
~Observer()47     virtual ~Observer() {}
48   };
49 
GetInstance()50   static ShelfButtonAnimation* GetInstance() {
51     static ShelfButtonAnimation* s_instance = new ShelfButtonAnimation();
52     return s_instance;
53   }
54 
AddObserver(Observer * observer)55   void AddObserver(Observer* observer) {
56     observers_.AddObserver(observer);
57   }
58 
RemoveObserver(Observer * observer)59   void RemoveObserver(Observer* observer) {
60     observers_.RemoveObserver(observer);
61     if (!observers_.might_have_observers())
62       animation_.Stop();
63   }
64 
GetAlpha()65   int GetAlpha() {
66     return GetThrobAnimation().CurrentValueBetween(0, 255);
67   }
68 
GetAnimation()69   double GetAnimation() {
70     return GetThrobAnimation().GetCurrentValue();
71   }
72 
73  private:
ShelfButtonAnimation()74   ShelfButtonAnimation()
75       : animation_(this) {
76     animation_.SetThrobDuration(kAttentionThrobDurationMS);
77     animation_.SetTweenType(gfx::Tween::SMOOTH_IN_OUT);
78   }
79 
~ShelfButtonAnimation()80   virtual ~ShelfButtonAnimation() {
81   }
82 
GetThrobAnimation()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
AnimationProgressed(const gfx::Animation * animation)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:
BarView(ShelfButton * host)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 
~BarView()123   virtual ~BarView() {
124     if (show_attention_)
125       ShelfButtonAnimation::GetInstance()->RemoveObserver(this);
126   }
127 
128   // views::View:
OnPaint(gfx::Canvas * canvas)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
AnimationProgressed()141   virtual void AnimationProgressed() OVERRIDE {
142     UpdateBounds();
143     SchedulePaint();
144   }
145 
SetBarBoundsRect(const gfx::Rect & bounds)146   void SetBarBoundsRect(const gfx::Rect& bounds) {
147     base_bounds_ = bounds;
148     UpdateBounds();
149   }
150 
ShowAttention(bool show)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:
UpdateBounds()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 
IconView()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 
~IconView()201 ShelfButton::IconView::~IconView() {
202 }
203 
204 ////////////////////////////////////////////////////////////////////////////////
205 // ShelfButton
206 
Create(views::ButtonListener * listener,ShelfButtonHost * host,ShelfLayoutManager * shelf_layout_manager)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 
ShelfButton(views::ButtonListener * listener,ShelfButtonHost * host,ShelfLayoutManager * shelf_layout_manager)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 
~ShelfButton()237 ShelfButton::~ShelfButton() {
238   if (destroyed_flag_)
239     *destroyed_flag_ = true;
240 }
241 
SetShadowedImage(const gfx::ImageSkia & image)242 void ShelfButton::SetShadowedImage(const gfx::ImageSkia& image) {
243   icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
244       image, icon_shadows_));
245 }
246 
SetImage(const gfx::ImageSkia & image)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 
GetImage() const279 const gfx::ImageSkia& ShelfButton::GetImage() const {
280   return icon_view_->GetImage();
281 }
282 
AddState(State state)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 
ClearState(State state)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 
GetIconBounds() const301 gfx::Rect ShelfButton::GetIconBounds() const {
302   return icon_view_->bounds();
303 }
304 
ShowContextMenu(const gfx::Point & p,ui::MenuSourceType source_type)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 
OnMousePressed(const ui::MouseEvent & event)324 bool ShelfButton::OnMousePressed(const ui::MouseEvent& event) {
325   CustomButton::OnMousePressed(event);
326   host_->PointerPressedOnButton(this, ShelfButtonHost::MOUSE, event);
327   return true;
328 }
329 
OnMouseReleased(const ui::MouseEvent & event)330 void ShelfButton::OnMouseReleased(const ui::MouseEvent& event) {
331   CustomButton::OnMouseReleased(event);
332   host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, false);
333 }
334 
OnMouseCaptureLost()335 void ShelfButton::OnMouseCaptureLost() {
336   ClearState(STATE_HOVERED);
337   host_->PointerReleasedOnButton(this, ShelfButtonHost::MOUSE, true);
338   CustomButton::OnMouseCaptureLost();
339 }
340 
OnMouseDragged(const ui::MouseEvent & event)341 bool ShelfButton::OnMouseDragged(const ui::MouseEvent& event) {
342   CustomButton::OnMouseDragged(event);
343   host_->PointerDraggedOnButton(this, ShelfButtonHost::MOUSE, event);
344   return true;
345 }
346 
OnMouseMoved(const ui::MouseEvent & event)347 void ShelfButton::OnMouseMoved(const ui::MouseEvent& event) {
348   CustomButton::OnMouseMoved(event);
349   host_->MouseMovedOverButton(this);
350 }
351 
OnMouseEntered(const ui::MouseEvent & event)352 void ShelfButton::OnMouseEntered(const ui::MouseEvent& event) {
353   AddState(STATE_HOVERED);
354   CustomButton::OnMouseEntered(event);
355   host_->MouseEnteredButton(this);
356 }
357 
OnMouseExited(const ui::MouseEvent & event)358 void ShelfButton::OnMouseExited(const ui::MouseEvent& event) {
359   ClearState(STATE_HOVERED);
360   CustomButton::OnMouseExited(event);
361   host_->MouseExitedButton(this);
362 }
363 
GetAccessibleState(ui::AXViewState * state)364 void ShelfButton::GetAccessibleState(ui::AXViewState* state) {
365   state->role = ui::AX_ROLE_BUTTON;
366   state->name = host_->GetAccessibleName(this);
367 }
368 
Layout()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 
ChildPreferredSizeChanged(views::View * child)419 void ShelfButton::ChildPreferredSizeChanged(views::View* child) {
420   Layout();
421 }
422 
OnFocus()423 void ShelfButton::OnFocus() {
424   AddState(STATE_FOCUSED);
425   CustomButton::OnFocus();
426 }
427 
OnBlur()428 void ShelfButton::OnBlur() {
429   ClearState(STATE_FOCUSED);
430   CustomButton::OnBlur();
431 }
432 
OnPaint(gfx::Canvas * canvas)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 
OnGestureEvent(ui::GestureEvent * event)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 
Init()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 
CreateIconView()480 ShelfButton::IconView* ShelfButton::CreateIconView() {
481   return new IconView;
482 }
483 
IsShelfHorizontal() const484 bool ShelfButton::IsShelfHorizontal() const {
485   return shelf_layout_manager_->IsHorizontalAlignment();
486 }
487 
UpdateState()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 
UpdateBar()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
544