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/system/tray/tray_background_view.h"
7 #include "ash/root_window_controller.h"
8 #include "ash/screen_util.h"
9 #include "ash/shelf/shelf_layout_manager.h"
10 #include "ash/shelf/shelf_widget.h"
11 #include "ash/shell.h"
12 #include "ash/shell_window_ids.h"
13 #include "ash/system/status_area_widget.h"
14 #include "ash/system/status_area_widget_delegate.h"
15 #include "ash/system/tray/system_tray.h"
16 #include "ash/system/tray/tray_constants.h"
17 #include "ash/system/tray/tray_event_filter.h"
18 #include "ash/wm/window_animations.h"
19 #include "base/command_line.h"
20 #include "grit/ash_resources.h"
21 #include "ui/accessibility/ax_view_state.h"
22 #include "ui/aura/window.h"
23 #include "ui/aura/window_event_dispatcher.h"
24 #include "ui/base/nine_image_painter_factory.h"
25 #include "ui/base/ui_base_switches_util.h"
26 #include "ui/compositor/layer.h"
27 #include "ui/compositor/layer_animation_element.h"
28 #include "ui/compositor/scoped_layer_animation_settings.h"
29 #include "ui/events/event_constants.h"
30 #include "ui/gfx/animation/tween.h"
31 #include "ui/gfx/canvas.h"
32 #include "ui/gfx/geometry/rect.h"
33 #include "ui/gfx/image/image_skia.h"
34 #include "ui/gfx/image/image_skia_operations.h"
35 #include "ui/gfx/nine_image_painter.h"
36 #include "ui/gfx/screen.h"
37 #include "ui/gfx/skia_util.h"
38 #include "ui/gfx/transform.h"
39 #include "ui/views/background.h"
40 #include "ui/views/layout/box_layout.h"
44 const int kTrayBackgroundAlpha
= 100;
45 const int kTrayBackgroundHoverAlpha
= 150;
46 const SkColor kTrayBackgroundPressedColor
= SkColorSetRGB(66, 129, 244);
48 const int kAnimationDurationForPopupMs
= 200;
50 // Duration of opacity animation for visibility changes.
51 const int kAnimationDurationForVisibilityMs
= 250;
53 // When becoming visible delay the animation so that StatusAreaWidgetDelegate
54 // can animate sibling views out of the position to be occuped by the
55 // TrayBackgroundView.
56 const int kShowAnimationDelayMs
= 100;
60 using views::TrayBubbleView
;
65 const char TrayBackgroundView::kViewClassName
[] = "tray/TrayBackgroundView";
67 // Used to track when the anchor widget changes position on screen so that the
68 // bubble position can be updated.
69 class TrayBackgroundView::TrayWidgetObserver
: public views::WidgetObserver
{
71 explicit TrayWidgetObserver(TrayBackgroundView
* host
)
75 void OnWidgetBoundsChanged(views::Widget
* widget
,
76 const gfx::Rect
& new_bounds
) override
{
77 host_
->AnchorUpdated();
80 void OnWidgetVisibilityChanged(views::Widget
* widget
, bool visible
) override
{
81 host_
->AnchorUpdated();
85 TrayBackgroundView
* host_
;
87 DISALLOW_COPY_AND_ASSIGN(TrayWidgetObserver
);
90 class TrayBackground
: public views::Background
{
92 const static int kImageTypeDefault
= 0;
93 const static int kImageTypeOnBlack
= 1;
94 const static int kImageTypePressed
= 2;
95 const static int kNumStates
= 3;
97 const static int kImageHorizontal
= 0;
98 const static int kImageVertical
= 1;
99 const static int kNumOrientations
= 2;
101 explicit TrayBackground(TrayBackgroundView
* tray_background_view
) :
102 tray_background_view_(tray_background_view
) {
103 set_alpha(kTrayBackgroundAlpha
);
106 ~TrayBackground() override
{}
108 SkColor
color() { return color_
; }
109 void set_color(SkColor color
) { color_
= color
; }
110 void set_alpha(int alpha
) { color_
= SkColorSetARGB(alpha
, 0, 0, 0); }
113 ShelfWidget
* GetShelfWidget() const {
114 return RootWindowController::ForWindow(tray_background_view_
->
115 status_area_widget()->GetNativeWindow())->shelf();
118 // Overridden from views::Background.
119 void Paint(gfx::Canvas
* canvas
, views::View
* view
) const override
{
120 const int kGridSizeForPainter
= 9;
121 const int kImages
[kNumOrientations
][kNumStates
][kGridSizeForPainter
] = {
123 IMAGE_GRID_HORIZONTAL(IDR_AURA_TRAY_BG_HORIZ
),
124 IMAGE_GRID_HORIZONTAL(IDR_AURA_TRAY_BG_HORIZ_ONBLACK
),
125 IMAGE_GRID_HORIZONTAL(IDR_AURA_TRAY_BG_HORIZ_PRESSED
),
128 IMAGE_GRID_VERTICAL(IDR_AURA_TRAY_BG_VERTICAL
),
129 IMAGE_GRID_VERTICAL(IDR_AURA_TRAY_BG_VERTICAL_ONBLACK
),
130 IMAGE_GRID_VERTICAL(IDR_AURA_TRAY_BG_VERTICAL_PRESSED
),
134 int orientation
= kImageHorizontal
;
135 ShelfWidget
* shelf_widget
= GetShelfWidget();
137 !shelf_widget
->shelf_layout_manager()->IsHorizontalAlignment())
138 orientation
= kImageVertical
;
140 int state
= kImageTypeDefault
;
141 if (tray_background_view_
->draw_background_as_active())
142 state
= kImageTypePressed
;
143 else if (shelf_widget
&& shelf_widget
->GetDimsShelf())
144 state
= kImageTypeOnBlack
;
146 state
= kImageTypeDefault
;
148 ui::CreateNineImagePainter(kImages
[orientation
][state
])
149 ->Paint(canvas
, view
->GetLocalBounds());
153 // Reference to the TrayBackgroundView for which this is a background.
154 TrayBackgroundView
* tray_background_view_
;
156 DISALLOW_COPY_AND_ASSIGN(TrayBackground
);
159 TrayBackgroundView::TrayContainer::TrayContainer(ShelfAlignment alignment
)
160 : alignment_(alignment
) {
164 void TrayBackgroundView::TrayContainer::SetAlignment(ShelfAlignment alignment
) {
165 if (alignment_
== alignment
)
167 alignment_
= alignment
;
171 gfx::Size
TrayBackgroundView::TrayContainer::GetPreferredSize() const {
173 return views::View::GetPreferredSize();
177 void TrayBackgroundView::TrayContainer::ChildPreferredSizeChanged(
178 views::View
* child
) {
179 PreferredSizeChanged();
182 void TrayBackgroundView::TrayContainer::ChildVisibilityChanged(View
* child
) {
183 PreferredSizeChanged();
186 void TrayBackgroundView::TrayContainer::ViewHierarchyChanged(
187 const ViewHierarchyChangedDetails
& details
) {
188 if (details
.parent
== this)
189 PreferredSizeChanged();
192 void TrayBackgroundView::TrayContainer::UpdateLayout() {
193 // Adjust the size of status tray dark background by adding additional
195 if (alignment_
== SHELF_ALIGNMENT_BOTTOM
||
196 alignment_
== SHELF_ALIGNMENT_TOP
) {
197 SetBorder(views::Border::CreateEmptyBorder(
198 kPaddingFromEdgeOfShelf
,
199 kPaddingFromEdgeOfShelf
,
200 kPaddingFromEdgeOfShelf
,
201 kPaddingFromEdgeOfShelf
));
203 views::BoxLayout
* layout
=
204 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0);
205 layout
->SetDefaultFlex(1);
206 views::View::SetLayoutManager(layout
);
208 SetBorder(views::Border::CreateEmptyBorder(
209 kPaddingFromEdgeOfShelf
,
210 kPaddingFromEdgeOfShelf
,
211 kPaddingFromEdgeOfShelf
,
212 kPaddingFromEdgeOfShelf
));
214 views::BoxLayout
* layout
=
215 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0);
216 layout
->SetDefaultFlex(1);
217 views::View::SetLayoutManager(layout
);
219 PreferredSizeChanged();
222 ////////////////////////////////////////////////////////////////////////////////
223 // TrayBackgroundView
225 TrayBackgroundView::TrayBackgroundView(StatusAreaWidget
* status_area_widget
)
226 : status_area_widget_(status_area_widget
),
227 tray_container_(NULL
),
228 shelf_alignment_(SHELF_ALIGNMENT_BOTTOM
),
230 hide_background_animator_(this, 0, kTrayBackgroundAlpha
),
231 hover_background_animator_(
234 kTrayBackgroundHoverAlpha
- kTrayBackgroundAlpha
),
236 draw_background_as_active_(false),
237 widget_observer_(new TrayWidgetObserver(this)) {
238 set_notify_enter_exit_on_child(true);
240 // Initially we want to paint the background, but without the hover effect.
241 hide_background_animator_
.SetPaintsBackground(
242 true, BACKGROUND_CHANGE_IMMEDIATE
);
243 hover_background_animator_
.SetPaintsBackground(
244 false, BACKGROUND_CHANGE_IMMEDIATE
);
246 tray_container_
= new TrayContainer(shelf_alignment_
);
247 SetContents(tray_container_
);
248 tray_event_filter_
.reset(new TrayEventFilter
);
250 SetPaintToLayer(true);
251 SetFillsBoundsOpaquely(false);
252 // Start the tray items not visible, because visibility changes are animated.
253 views::View::SetVisible(false);
256 TrayBackgroundView::~TrayBackgroundView() {
258 GetWidget()->RemoveObserver(widget_observer_
.get());
261 void TrayBackgroundView::Initialize() {
262 GetWidget()->AddObserver(widget_observer_
.get());
266 void TrayBackgroundView::SetVisible(bool visible
) {
267 if (visible
== layer()->GetTargetVisibility())
271 // The alignment of the shelf can change while the TrayBackgroundView is
272 // hidden. Reset the offscreen transform so that the animation to becoming
273 // visible reflects the current layout.
274 HideTransformation();
275 // SetVisible(false) is defered until the animation for hiding is done.
276 // Otherwise the view is immediately hidden and the animation does not
278 views::View::SetVisible(true);
279 // If SetVisible(true) is called while animating to not visible, then
280 // views::View::SetVisible(true) is a no-op. When the previous animation
281 // ends layer->SetVisible(false) is called. To prevent this
282 // layer->SetVisible(true) immediately interrupts the animation of this
283 // property, and keeps the layer visible.
284 layer()->SetVisible(true);
287 ui::ScopedLayerAnimationSettings
animation(layer()->GetAnimator());
288 animation
.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
289 kAnimationDurationForVisibilityMs
));
290 animation
.SetPreemptionStrategy(
291 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
294 animation
.SetTweenType(gfx::Tween::EASE_OUT
);
295 // Show is delayed so as to allow time for other children of
296 // StatusAreaWidget to begin animating to their new positions.
297 layer()->GetAnimator()->SchedulePauseForProperties(
298 base::TimeDelta::FromMilliseconds(kShowAnimationDelayMs
),
299 ui::LayerAnimationElement::OPACITY
|
300 ui::LayerAnimationElement::TRANSFORM
);
301 layer()->SetOpacity(1.0f
);
302 gfx::Transform transform
;
303 transform
.Translate(0.0f
, 0.0f
);
304 layer()->SetTransform(transform
);
306 // Listen only to the hide animation. As we cannot turn off visibility
307 // until the animation is over.
308 animation
.AddObserver(this);
309 animation
.SetTweenType(gfx::Tween::EASE_IN
);
310 layer()->SetOpacity(0.0f
);
311 layer()->SetVisible(false);
312 HideTransformation();
316 const char* TrayBackgroundView::GetClassName() const {
317 return kViewClassName
;
320 void TrayBackgroundView::OnMouseEntered(const ui::MouseEvent
& event
) {
324 void TrayBackgroundView::OnMouseExited(const ui::MouseEvent
& event
) {
328 void TrayBackgroundView::ChildPreferredSizeChanged(views::View
* child
) {
329 PreferredSizeChanged();
332 void TrayBackgroundView::GetAccessibleState(ui::AXViewState
* state
) {
333 state
->role
= ui::AX_ROLE_BUTTON
;
334 state
->name
= GetAccessibleNameForTray();
337 void TrayBackgroundView::AboutToRequestFocusFromTabTraversal(bool reverse
) {
338 // Return focus to the login view. See crbug.com/120500.
339 views::View
* v
= GetNextFocusableView();
341 v
->AboutToRequestFocusFromTabTraversal(reverse
);
344 bool TrayBackgroundView::PerformAction(const ui::Event
& event
) {
348 gfx::Rect
TrayBackgroundView::GetFocusBounds() {
349 // The tray itself expands to the right and bottom edge of the screen to make
350 // sure clicking on the edges brings up the popup. However, the focus border
351 // should be only around the container.
352 return GetContentsBounds();
355 void TrayBackgroundView::OnGestureEvent(ui::GestureEvent
* event
) {
356 if (switches::IsTouchFeedbackEnabled()) {
357 if (event
->type() == ui::ET_GESTURE_TAP_DOWN
) {
358 SetDrawBackgroundAsActive(true);
359 } else if (event
->type() == ui::ET_GESTURE_SCROLL_BEGIN
||
360 event
->type() == ui::ET_GESTURE_TAP_CANCEL
) {
361 SetDrawBackgroundAsActive(false);
364 ActionableView::OnGestureEvent(event
);
367 void TrayBackgroundView::UpdateBackground(int alpha
) {
368 // The animator should never fire when the alternate shelf layout is used.
369 if (!background_
|| draw_background_as_active_
)
371 background_
->set_alpha(hide_background_animator_
.alpha() +
372 hover_background_animator_
.alpha());
376 void TrayBackgroundView::SetContents(views::View
* contents
) {
377 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
378 AddChildView(contents
);
381 void TrayBackgroundView::SetPaintsBackground(
382 bool value
, BackgroundAnimatorChangeType change_type
) {
383 hide_background_animator_
.SetPaintsBackground(value
, change_type
);
386 void TrayBackgroundView::SetContentsBackground() {
387 background_
= new TrayBackground(this);
388 tray_container_
->set_background(background_
);
391 ShelfLayoutManager
* TrayBackgroundView::GetShelfLayoutManager() {
392 return ShelfLayoutManager::ForShelf(GetWidget()->GetNativeView());
395 void TrayBackgroundView::SetShelfAlignment(ShelfAlignment alignment
) {
396 shelf_alignment_
= alignment
;
398 tray_container_
->SetAlignment(alignment
);
401 void TrayBackgroundView::SetTrayBorder() {
402 views::View
* parent
= status_area_widget_
->status_area_widget_delegate();
403 // Tray views are laid out right-to-left or bottom-to-top
404 bool on_edge
= (this == parent
->child_at(0));
405 int left_edge
, top_edge
, right_edge
, bottom_edge
;
406 if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM
) {
407 top_edge
= ShelfLayoutManager::kShelfItemInset
;
409 bottom_edge
= kShelfSize
-
410 ShelfLayoutManager::kShelfItemInset
- kShelfItemHeight
;
411 right_edge
= on_edge
? kPaddingFromEdgeOfShelf
: 0;
412 } else if (shelf_alignment() == SHELF_ALIGNMENT_LEFT
) {
414 left_edge
= kShelfSize
-
415 ShelfLayoutManager::kShelfItemInset
- kShelfItemHeight
;
416 bottom_edge
= on_edge
? kPaddingFromEdgeOfShelf
: 0;
417 right_edge
= ShelfLayoutManager::kShelfItemInset
;
418 } else { // SHELF_ALIGNMENT_RIGHT
420 left_edge
= ShelfLayoutManager::kShelfItemInset
;
421 bottom_edge
= on_edge
? kPaddingFromEdgeOfShelf
: 0;
422 right_edge
= kShelfSize
-
423 ShelfLayoutManager::kShelfItemInset
- kShelfItemHeight
;
425 SetBorder(views::Border::CreateEmptyBorder(
426 top_edge
, left_edge
, bottom_edge
, right_edge
));
429 void TrayBackgroundView::OnImplicitAnimationsCompleted() {
430 // If there is another animation in the queue, the reverse animation was
431 // triggered before the completion of animating to invisible. Do not turn off
432 // the visibility so that the next animation may render. The value of
433 // layer()->GetTargetVisibility() can be incorrect if the hide animation was
434 // aborted to schedule an animation to become visible. As the new animation
435 // is not yet added to the queue. crbug.com/374236
436 if(layer()->GetAnimator()->is_animating() ||
437 layer()->GetTargetVisibility())
439 views::View::SetVisible(false);
442 void TrayBackgroundView::HideTransformation() {
443 gfx::Transform transform
;
444 if (shelf_alignment_
== SHELF_ALIGNMENT_BOTTOM
||
445 shelf_alignment_
== SHELF_ALIGNMENT_TOP
)
446 transform
.Translate(width(), 0.0f
);
448 transform
.Translate(0.0f
, height());
449 layer()->SetTransform(transform
);
452 void TrayBackgroundView::InitializeBubbleAnimations(
453 views::Widget
* bubble_widget
) {
454 wm::SetWindowVisibilityAnimationType(
455 bubble_widget
->GetNativeWindow(),
456 wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE
);
457 wm::SetWindowVisibilityAnimationTransition(
458 bubble_widget
->GetNativeWindow(),
460 wm::SetWindowVisibilityAnimationDuration(
461 bubble_widget
->GetNativeWindow(),
462 base::TimeDelta::FromMilliseconds(kAnimationDurationForPopupMs
));
465 aura::Window
* TrayBackgroundView::GetBubbleWindowContainer() const {
466 return ash::Shell::GetContainer(
467 tray_container()->GetWidget()->GetNativeWindow()->GetRootWindow(),
468 ash::kShellWindowId_SettingBubbleContainer
);
471 gfx::Rect
TrayBackgroundView::GetBubbleAnchorRect(
472 views::Widget
* anchor_widget
,
473 TrayBubbleView::AnchorType anchor_type
,
474 TrayBubbleView::AnchorAlignment anchor_alignment
) const {
476 if (anchor_widget
&& anchor_widget
->IsVisible()) {
477 rect
= anchor_widget
->GetWindowBoundsInScreen();
478 if (anchor_type
== TrayBubbleView::ANCHOR_TYPE_TRAY
) {
479 if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM
) {
480 bool rtl
= base::i18n::IsRTL();
482 rtl
? kBubblePaddingHorizontalSide
: 0,
483 kBubblePaddingHorizontalBottom
,
484 rtl
? 0 : kBubblePaddingHorizontalSide
,
486 } else if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_LEFT
) {
487 rect
.Inset(0, 0, kBubblePaddingVerticalSide
+ 4,
488 kBubblePaddingVerticalBottom
);
489 } else if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT
) {
490 rect
.Inset(kBubblePaddingVerticalSide
, 0, 0,
491 kBubblePaddingVerticalBottom
);
493 // TODO(bruthig) May need to handle other ANCHOR_ALIGNMENT_ values.
494 // ie. ANCHOR_ALIGNMENT_TOP
495 DCHECK(false) << "Unhandled anchor alignment.";
497 } else if (anchor_type
== TrayBubbleView::ANCHOR_TYPE_BUBBLE
) {
498 // Invert the offsets to align with the bubble below.
499 // Note that with the alternate shelf layout the tips are not shown and
500 // the offsets for left and right alignment do not need to be applied.
501 int vertical_alignment
= 0;
502 int horizontal_alignment
= kBubblePaddingVerticalBottom
;
503 if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_LEFT
)
504 rect
.Inset(vertical_alignment
, 0, 0, horizontal_alignment
);
505 else if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT
)
506 rect
.Inset(0, 0, vertical_alignment
, horizontal_alignment
);
508 DCHECK(false) << "Unhandled anchor type.";
511 aura::Window
* target_root
= anchor_widget
?
512 anchor_widget
->GetNativeView()->GetRootWindow() :
513 Shell::GetPrimaryRootWindow();
514 rect
= target_root
->bounds();
515 if (anchor_type
== TrayBubbleView::ANCHOR_TYPE_TRAY
) {
516 if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM
) {
518 base::i18n::IsRTL() ?
519 kPaddingFromRightEdgeOfScreenBottomAlignment
:
520 rect
.width() - kPaddingFromRightEdgeOfScreenBottomAlignment
,
521 rect
.height() - kPaddingFromBottomOfScreenBottomAlignment
,
523 rect
= ScreenUtil::ConvertRectToScreen(target_root
, rect
);
524 } else if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_LEFT
) {
526 kPaddingFromRightEdgeOfScreenBottomAlignment
,
527 rect
.height() - kPaddingFromBottomOfScreenBottomAlignment
,
529 rect
= ScreenUtil::ConvertRectToScreen(target_root
, rect
);
530 } else if (anchor_alignment
== TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT
) {
532 rect
.width() - kPaddingFromRightEdgeOfScreenBottomAlignment
,
533 rect
.height() - kPaddingFromBottomOfScreenBottomAlignment
,
535 rect
= ScreenUtil::ConvertRectToScreen(target_root
, rect
);
537 // TODO(bruthig) May need to handle other ANCHOR_ALIGNMENT_ values.
538 // ie. ANCHOR_ALIGNMENT_TOP
539 DCHECK(false) << "Unhandled anchor alignment.";
543 base::i18n::IsRTL() ?
544 kPaddingFromRightEdgeOfScreenBottomAlignment
:
545 rect
.width() - kPaddingFromRightEdgeOfScreenBottomAlignment
,
546 rect
.height() - kPaddingFromBottomOfScreenBottomAlignment
,
553 TrayBubbleView::AnchorAlignment
TrayBackgroundView::GetAnchorAlignment() const {
554 switch (shelf_alignment_
) {
555 case SHELF_ALIGNMENT_BOTTOM
:
556 return TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM
;
557 case SHELF_ALIGNMENT_LEFT
:
558 return TrayBubbleView::ANCHOR_ALIGNMENT_LEFT
;
559 case SHELF_ALIGNMENT_RIGHT
:
560 return TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT
;
561 case SHELF_ALIGNMENT_TOP
:
562 return TrayBubbleView::ANCHOR_ALIGNMENT_TOP
;
565 return TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM
;
568 void TrayBackgroundView::SetDrawBackgroundAsActive(bool visible
) {
569 if (draw_background_as_active_
== visible
)
571 draw_background_as_active_
= visible
;
575 // Do not change gradually, changing color between grey and blue is weird.
576 if (draw_background_as_active_
)
577 background_
->set_color(kTrayBackgroundPressedColor
);
579 background_
->set_alpha(kTrayBackgroundHoverAlpha
);
581 background_
->set_alpha(kTrayBackgroundAlpha
);
585 void TrayBackgroundView::UpdateBubbleViewArrow(
586 views::TrayBubbleView
* bubble_view
) {
587 // Nothing to do here.