Windows should animate when they are about to get docked at screen edges.
[chromium-blink-merge.git] / ash / system / tray / system_tray_bubble.cc
blobeff1ffd84ef48af5dd2c533495fd9cb12004c37a
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/system_tray_bubble.h"
7 #include "ash/shell.h"
8 #include "ash/system/tray/system_tray.h"
9 #include "ash/system/tray/system_tray_delegate.h"
10 #include "ash/system/tray/system_tray_item.h"
11 #include "ash/system/tray/tray_bubble_wrapper.h"
12 #include "ash/system/tray/tray_constants.h"
13 #include "base/message_loop/message_loop.h"
14 #include "ui/aura/window.h"
15 #include "ui/compositor/layer.h"
16 #include "ui/compositor/layer_animation_observer.h"
17 #include "ui/compositor/scoped_layer_animation_settings.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/views/layout/box_layout.h"
20 #include "ui/views/view.h"
21 #include "ui/views/widget/widget.h"
23 using views::TrayBubbleView;
25 namespace ash {
27 namespace {
29 // Normally a detailed view is the same size as the default view. However,
30 // when showing a detailed view directly (e.g. clicking on a notification),
31 // we may not know the height of the default view, or the default view may
32 // be too short, so we use this as a default and minimum height for any
33 // detailed view.
34 const int kDetailedBubbleMaxHeight = kTrayPopupItemHeight * 5;
36 // Duration of swipe animation used when transitioning from a default to
37 // detailed view or vice versa.
38 const int kSwipeDelayMS = 150;
40 // A view with some special behaviour for tray items in the popup:
41 // - optionally changes background color on hover.
42 class TrayPopupItemContainer : public views::View {
43 public:
44 TrayPopupItemContainer(views::View* view,
45 bool change_background,
46 bool draw_border)
47 : hover_(false),
48 change_background_(change_background) {
49 set_notify_enter_exit_on_child(true);
50 if (draw_border) {
51 set_border(
52 views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor));
54 views::BoxLayout* layout = new views::BoxLayout(
55 views::BoxLayout::kVertical, 0, 0, 0);
56 layout->set_spread_blank_space(true);
57 SetLayoutManager(layout);
58 SetPaintToLayer(view->layer() != NULL);
59 if (view->layer())
60 SetFillsBoundsOpaquely(view->layer()->fills_bounds_opaquely());
61 AddChildView(view);
62 SetVisible(view->visible());
65 virtual ~TrayPopupItemContainer() {}
67 private:
68 // Overridden from views::View.
69 virtual void ChildVisibilityChanged(View* child) OVERRIDE {
70 if (visible() == child->visible())
71 return;
72 SetVisible(child->visible());
73 PreferredSizeChanged();
76 virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
77 PreferredSizeChanged();
80 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
81 hover_ = true;
82 SchedulePaint();
85 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
86 hover_ = false;
87 SchedulePaint();
90 virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
91 if (child_count() == 0)
92 return;
94 views::View* view = child_at(0);
95 if (!view->background()) {
96 canvas->FillRect(gfx::Rect(size()), (hover_ && change_background_) ?
97 kHoverBackgroundColor : kBackgroundColor);
101 bool hover_;
102 bool change_background_;
104 DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer);
107 // Implicit animation observer that deletes itself and the layer at the end of
108 // the animation.
109 class AnimationObserverDeleteLayer : public ui::ImplicitAnimationObserver {
110 public:
111 explicit AnimationObserverDeleteLayer(ui::Layer* layer)
112 : layer_(layer) {
115 virtual ~AnimationObserverDeleteLayer() {
118 virtual void OnImplicitAnimationsCompleted() OVERRIDE {
119 base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE, this);
122 private:
123 scoped_ptr<ui::Layer> layer_;
125 DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer);
128 } // namespace
130 namespace internal {
132 // SystemTrayBubble
134 SystemTrayBubble::SystemTrayBubble(
135 ash::SystemTray* tray,
136 const std::vector<ash::SystemTrayItem*>& items,
137 BubbleType bubble_type)
138 : tray_(tray),
139 bubble_view_(NULL),
140 items_(items),
141 bubble_type_(bubble_type),
142 autoclose_delay_(0) {
145 SystemTrayBubble::~SystemTrayBubble() {
146 DestroyItemViews();
147 // Reset the host pointer in bubble_view_ in case its destruction is deferred.
148 if (bubble_view_)
149 bubble_view_->reset_delegate();
152 void SystemTrayBubble::UpdateView(
153 const std::vector<ash::SystemTrayItem*>& items,
154 BubbleType bubble_type) {
155 DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION);
157 scoped_ptr<ui::Layer> scoped_layer;
158 if (bubble_type != bubble_type_) {
159 base::TimeDelta swipe_duration =
160 base::TimeDelta::FromMilliseconds(kSwipeDelayMS);
161 scoped_layer.reset(bubble_view_->RecreateLayer());
162 // Keep the reference to layer as we need it after releasing it.
163 ui::Layer* layer = scoped_layer.get();
164 DCHECK(layer);
165 layer->SuppressPaint();
167 // When transitioning from detailed view to default view, animate the
168 // existing view (slide out towards the right).
169 if (bubble_type == BUBBLE_TYPE_DEFAULT) {
170 ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
171 settings.AddObserver(
172 new AnimationObserverDeleteLayer(scoped_layer.release()));
173 settings.SetTransitionDuration(swipe_duration);
174 settings.SetTweenType(ui::Tween::EASE_OUT);
175 gfx::Transform transform;
176 transform.Translate(layer->bounds().width(), 0.0);
177 layer->SetTransform(transform);
181 // Add a shadow layer to make the old layer darker as the animation
182 // progresses.
183 ui::Layer* shadow = new ui::Layer(ui::LAYER_SOLID_COLOR);
184 shadow->SetColor(SK_ColorBLACK);
185 shadow->SetOpacity(0.01f);
186 shadow->SetBounds(layer->bounds());
187 layer->Add(shadow);
188 layer->StackAtTop(shadow);
190 // Animate the darkening effect a little longer than the swipe-in. This
191 // is to make sure the darkening animation does not end up finishing
192 // early, because the dark layer goes away at the end of the animation,
193 // and there is a brief moment when the old view is still visible, but
194 // it does not have the shadow layer on top.
195 ui::ScopedLayerAnimationSettings settings(shadow->GetAnimator());
196 settings.AddObserver(new AnimationObserverDeleteLayer(shadow));
197 settings.SetTransitionDuration(swipe_duration +
198 base::TimeDelta::FromMilliseconds(150));
199 settings.SetTweenType(ui::Tween::LINEAR);
200 shadow->SetOpacity(0.15f);
205 DestroyItemViews();
206 bubble_view_->RemoveAllChildViews(true);
208 items_ = items;
209 bubble_type_ = bubble_type;
210 CreateItemViews(
211 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
213 // Close bubble view if we failed to create the item view.
214 if (!bubble_view_->has_children()) {
215 Close();
216 return;
219 bubble_view_->GetWidget()->GetContentsView()->Layout();
220 // Make sure that the bubble is large enough for the default view.
221 if (bubble_type_ == BUBBLE_TYPE_DEFAULT) {
222 bubble_view_->SetMaxHeight(0); // Clear max height limit.
225 if (scoped_layer) {
226 // When transitioning from default view to detailed view, animate the new
227 // view (slide in from the right).
228 if (bubble_type == BUBBLE_TYPE_DETAILED) {
229 ui::Layer* new_layer = bubble_view_->layer();
231 // Make sure the new layer is stacked above the old layer during the
232 // animation.
233 new_layer->parent()->StackAbove(new_layer, scoped_layer.get());
235 gfx::Rect bounds = new_layer->bounds();
236 gfx::Transform transform;
237 transform.Translate(bounds.width(), 0.0);
238 new_layer->SetTransform(transform);
240 ui::ScopedLayerAnimationSettings settings(new_layer->GetAnimator());
241 settings.AddObserver(
242 new AnimationObserverDeleteLayer(scoped_layer.release()));
243 settings.SetTransitionDuration(
244 base::TimeDelta::FromMilliseconds(kSwipeDelayMS));
245 settings.SetTweenType(ui::Tween::EASE_OUT);
246 new_layer->SetTransform(gfx::Transform());
252 void SystemTrayBubble::InitView(views::View* anchor,
253 user::LoginStatus login_status,
254 TrayBubbleView::InitParams* init_params) {
255 DCHECK(bubble_view_ == NULL);
257 if (bubble_type_ == BUBBLE_TYPE_DETAILED &&
258 init_params->max_height < kDetailedBubbleMaxHeight) {
259 init_params->max_height = kDetailedBubbleMaxHeight;
260 } else if (bubble_type_ == BUBBLE_TYPE_NOTIFICATION) {
261 init_params->close_on_deactivate = false;
263 bubble_view_ = TrayBubbleView::Create(
264 tray_->GetBubbleWindowContainer(), anchor, tray_, init_params);
265 bubble_view_->set_adjust_if_offscreen(false);
266 CreateItemViews(login_status);
268 if (bubble_view_->CanActivate()) {
269 bubble_view_->NotifyAccessibilityEvent(
270 ui::AccessibilityTypes::EVENT_ALERT, true);
274 void SystemTrayBubble::DestroyItemViews() {
275 for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
276 it != items_.end();
277 ++it) {
278 switch (bubble_type_) {
279 case BUBBLE_TYPE_DEFAULT:
280 (*it)->DestroyDefaultView();
281 break;
282 case BUBBLE_TYPE_DETAILED:
283 (*it)->DestroyDetailedView();
284 break;
285 case BUBBLE_TYPE_NOTIFICATION:
286 (*it)->DestroyNotificationView();
287 break;
292 void SystemTrayBubble::BubbleViewDestroyed() {
293 bubble_view_ = NULL;
296 void SystemTrayBubble::StartAutoCloseTimer(int seconds) {
297 autoclose_.Stop();
298 autoclose_delay_ = seconds;
299 if (autoclose_delay_) {
300 autoclose_.Start(FROM_HERE,
301 base::TimeDelta::FromSeconds(autoclose_delay_),
302 this, &SystemTrayBubble::Close);
306 void SystemTrayBubble::StopAutoCloseTimer() {
307 autoclose_.Stop();
310 void SystemTrayBubble::RestartAutoCloseTimer() {
311 if (autoclose_delay_)
312 StartAutoCloseTimer(autoclose_delay_);
315 void SystemTrayBubble::Close() {
316 tray_->HideBubbleWithView(bubble_view());
319 void SystemTrayBubble::SetVisible(bool is_visible) {
320 if (!bubble_view_)
321 return;
322 views::Widget* bubble_widget = bubble_view_->GetWidget();
323 if (is_visible)
324 bubble_widget->Show();
325 else
326 bubble_widget->Hide();
329 bool SystemTrayBubble::IsVisible() {
330 return bubble_view() && bubble_view()->GetWidget()->IsVisible();
333 bool SystemTrayBubble::ShouldShowLauncher() const {
334 for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin();
335 it != items_.end();
336 ++it) {
337 if ((*it)->ShouldShowLauncher())
338 return true;
340 return false;
343 void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) {
344 std::vector<views::View*> item_views;
345 for (size_t i = 0; i < items_.size(); ++i) {
346 views::View* view = NULL;
347 switch (bubble_type_) {
348 case BUBBLE_TYPE_DEFAULT:
349 view = items_[i]->CreateDefaultView(login_status);
350 break;
351 case BUBBLE_TYPE_DETAILED:
352 view = items_[i]->CreateDetailedView(login_status);
353 break;
354 case BUBBLE_TYPE_NOTIFICATION:
355 view = items_[i]->CreateNotificationView(login_status);
356 break;
358 if (view)
359 item_views.push_back(view);
362 bool is_default_bubble = bubble_type_ == BUBBLE_TYPE_DEFAULT;
363 for (size_t i = 0; i < item_views.size(); ++i) {
364 // For default view, draw bottom border for each item, except the last
365 // 2 items, which are the bottom header row and the one just above it.
366 bubble_view_->AddChildView(new TrayPopupItemContainer(
367 item_views[i], is_default_bubble,
368 is_default_bubble && (i < item_views.size() - 2)));
372 } // namespace internal
373 } // namespace ash