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/shelf/shelf_widget.h"
7 #include "ash/ash_switches.h"
8 #include "ash/focus_cycler.h"
9 #include "ash/launcher/launcher_delegate.h"
10 #include "ash/launcher/launcher_model.h"
11 #include "ash/launcher/launcher_navigator.h"
12 #include "ash/launcher/launcher_view.h"
13 #include "ash/root_window_controller.h"
14 #include "ash/session_state_delegate.h"
15 #include "ash/shelf/shelf_layout_manager.h"
16 #include "ash/shelf/shelf_widget.h"
17 #include "ash/shell.h"
18 #include "ash/shell_window_ids.h"
19 #include "ash/system/tray/test_system_tray_delegate.h"
20 #include "ash/wm/property_util.h"
21 #include "ash/wm/status_area_layout_manager.h"
22 #include "ash/wm/window_properties.h"
23 #include "ash/wm/workspace_controller.h"
24 #include "grit/ash_resources.h"
25 #include "ui/aura/client/activation_client.h"
26 #include "ui/aura/root_window.h"
27 #include "ui/aura/window.h"
28 #include "ui/aura/window_observer.h"
29 #include "ui/base/events/event_constants.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/compositor/layer.h"
32 #include "ui/compositor/scoped_layer_animation_settings.h"
33 #include "ui/gfx/canvas.h"
34 #include "ui/gfx/image/image.h"
35 #include "ui/gfx/image/image_skia_operations.h"
36 #include "ui/gfx/skbitmap_operations.h"
37 #include "ui/views/accessible_pane_view.h"
38 #include "ui/views/widget/widget.h"
39 #include "ui/views/widget/widget_delegate.h"
42 // Size of black border at bottom (or side) of launcher.
43 const int kNumBlackPixels
= 3;
44 // Alpha to paint dimming image with.
45 const int kDimAlpha
= 128;
47 // The time to dim and un-dim.
48 const int kTimeToDimMs
= 3000; // Slow in dimming.
49 const int kTimeToUnDimMs
= 200; // Fast in activating.
50 const int kTimeToSwitchBackgroundMs
= 1000;
52 // Class used to slightly dim shelf items when maximized and visible.
53 class DimmerView
: public views::View
,
54 public views::WidgetDelegate
,
55 ash::internal::BackgroundAnimatorDelegate
{
57 // If |disable_dimming_animations_for_test| is set, all alpha animations will
58 // be performed instantly.
59 DimmerView(ash::ShelfWidget
* shelf_widget
,
60 bool disable_dimming_animations_for_test
);
61 virtual ~DimmerView();
63 // Called by |DimmerEventFilter| when the mouse |hovered| state changes.
64 void SetHovered(bool hovered
);
66 // Force the dimmer to be undimmed.
67 void ForceUndimming(bool force
);
69 // views::WidgetDelegate overrides:
70 virtual views::Widget
* GetWidget() OVERRIDE
{
71 return View::GetWidget();
73 virtual const views::Widget
* GetWidget() const OVERRIDE
{
74 return View::GetWidget();
77 // ash::internal::BackgroundAnimatorDelegate overrides:
78 virtual void UpdateBackground(int alpha
) OVERRIDE
{
83 // views::View overrides:
84 virtual void OnPaintBackground(gfx::Canvas
* canvas
) OVERRIDE
;
86 // A function to test the current alpha used.
87 int get_dimming_alpha_for_test() { return alpha_
; }
90 // This class monitors mouse events to see if it is on top of the launcher.
91 class DimmerEventFilter
: public ui::EventHandler
{
93 explicit DimmerEventFilter(DimmerView
* owner
);
94 virtual ~DimmerEventFilter();
96 // Overridden from ui::EventHandler:
97 virtual void OnMouseEvent(ui::MouseEvent
* event
) OVERRIDE
;
98 virtual void OnTouchEvent(ui::TouchEvent
* event
) OVERRIDE
;
104 // TRUE if the mouse is inside the shelf.
107 // TRUE if a touch event is inside the shelf.
110 DISALLOW_COPY_AND_ASSIGN(DimmerEventFilter
);
114 ash::ShelfWidget
* shelf_
;
116 // The alpha to use for covering the shelf.
119 // True if the event filter claims that we should not be dimmed.
122 // True if someone forces us not to be dimmed (e.g. a menu is open).
125 // True if animations should be suppressed for a test.
126 bool disable_dimming_animations_for_test_
;
128 // The animator for the background transitions.
129 ash::internal::BackgroundAnimator background_animator_
;
131 // Notification of entering / exiting of the shelf area by mouse.
132 scoped_ptr
<DimmerEventFilter
> event_filter_
;
134 DISALLOW_COPY_AND_ASSIGN(DimmerView
);
137 DimmerView::DimmerView(ash::ShelfWidget
* shelf_widget
,
138 bool disable_dimming_animations_for_test
)
139 : shelf_(shelf_widget
),
142 force_hovered_(false),
143 disable_dimming_animations_for_test_(disable_dimming_animations_for_test
),
144 background_animator_(this, 0, kDimAlpha
) {
145 event_filter_
.reset(new DimmerEventFilter(this));
146 // Make sure it is undimmed at the beginning and then fire off the dimming
148 background_animator_
.SetPaintsBackground(false,
149 ash::internal::BackgroundAnimator::CHANGE_IMMEDIATE
);
153 DimmerView::~DimmerView() {
156 void DimmerView::SetHovered(bool hovered
) {
157 // Remember the hovered state so that we can correct the state once a
158 // possible force state has disappeared.
159 is_hovered_
= hovered
;
160 // Undimm also if we were forced to by e.g. an open menu.
161 hovered
|= force_hovered_
;
162 background_animator_
.SetDuration(hovered
? kTimeToUnDimMs
: kTimeToDimMs
);
163 background_animator_
.SetPaintsBackground(!hovered
,
164 disable_dimming_animations_for_test_
?
165 ash::internal::BackgroundAnimator::CHANGE_IMMEDIATE
:
166 ash::internal::BackgroundAnimator::CHANGE_ANIMATE
);
169 void DimmerView::ForceUndimming(bool force
) {
170 bool previous
= force_hovered_
;
171 force_hovered_
= force
;
172 // If the forced change does change the result we apply the change.
173 if (is_hovered_
|| force_hovered_
!= is_hovered_
|| previous
)
174 SetHovered(is_hovered_
);
177 void DimmerView::OnPaintBackground(gfx::Canvas
* canvas
) {
179 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
180 gfx::ImageSkia launcher_background
=
181 *rb
.GetImageNamed(IDR_AURA_LAUNCHER_DIMMING
).ToImageSkia();
183 if (shelf_
->GetAlignment() != ash::SHELF_ALIGNMENT_BOTTOM
) {
184 launcher_background
= gfx::ImageSkiaOperations::CreateRotatedImage(
186 shelf_
->shelf_layout_manager()->SelectValueForShelfAlignment(
187 SkBitmapOperations::ROTATION_90_CW
,
188 SkBitmapOperations::ROTATION_90_CW
,
189 SkBitmapOperations::ROTATION_270_CW
,
190 SkBitmapOperations::ROTATION_180_CW
));
192 paint
.setAlpha(alpha_
);
193 canvas
->DrawImageInt(
195 0, 0, launcher_background
.width(), launcher_background
.height(),
196 0, 0, width(), height(),
201 DimmerView::DimmerEventFilter::DimmerEventFilter(DimmerView
* owner
)
203 mouse_inside_(false),
204 touch_inside_(false) {
205 ash::Shell::GetInstance()->AddPreTargetHandler(this);
208 DimmerView::DimmerEventFilter::~DimmerEventFilter() {
209 ash::Shell::GetInstance()->RemovePreTargetHandler(this);
212 void DimmerView::DimmerEventFilter::OnMouseEvent(ui::MouseEvent
* event
) {
213 if (event
->type() != ui::ET_MOUSE_MOVED
&&
214 event
->type() != ui::ET_MOUSE_DRAGGED
)
216 bool inside
= owner_
->GetBoundsInScreen().Contains(event
->root_location());
217 if (mouse_inside_
|| touch_inside_
!= inside
|| touch_inside_
)
218 owner_
->SetHovered(inside
|| touch_inside_
);
219 mouse_inside_
= inside
;
222 void DimmerView::DimmerEventFilter::OnTouchEvent(ui::TouchEvent
* event
) {
223 bool touch_inside
= false;
224 if (event
->type() != ui::ET_TOUCH_RELEASED
&&
225 event
->type() != ui::ET_TOUCH_CANCELLED
)
226 touch_inside
= owner_
->GetBoundsInScreen().Contains(event
->root_location());
228 if (mouse_inside_
|| touch_inside_
!= mouse_inside_
|| touch_inside
)
229 owner_
->SetHovered(mouse_inside_
|| touch_inside
);
230 touch_inside_
= touch_inside
;
237 // The contents view of the Shelf. This view contains LauncherView and
238 // sizes it to the width of the shelf minus the size of the status area.
239 class ShelfWidget::DelegateView
: public views::WidgetDelegate
,
240 public views::AccessiblePaneView
,
241 public internal::BackgroundAnimatorDelegate
{
243 explicit DelegateView(ShelfWidget
* shelf
);
244 virtual ~DelegateView();
246 void set_focus_cycler(internal::FocusCycler
* focus_cycler
) {
247 focus_cycler_
= focus_cycler
;
249 internal::FocusCycler
* focus_cycler() {
250 return focus_cycler_
;
253 ui::Layer
* opaque_background() { return &opaque_background_
; }
255 // Set if the shelf area is dimmed (eg when a window is maximized).
256 void SetDimmed(bool dimmed
);
257 bool GetDimmed() const;
259 void SetParentLayer(ui::Layer
* layer
);
261 // views::View overrides:
262 virtual void OnPaintBackground(gfx::Canvas
* canvas
) OVERRIDE
;
264 // views::WidgetDelegateView overrides:
265 virtual views::Widget
* GetWidget() OVERRIDE
{
266 return View::GetWidget();
268 virtual const views::Widget
* GetWidget() const OVERRIDE
{
269 return View::GetWidget();
272 virtual bool CanActivate() const OVERRIDE
;
273 virtual void Layout() OVERRIDE
;
274 virtual void ReorderChildLayers(ui::Layer
* parent_layer
) OVERRIDE
;
275 virtual void OnBoundsChanged(const gfx::Rect
& old_bounds
) OVERRIDE
;
277 // BackgroundAnimatorDelegate overrides:
278 virtual void UpdateBackground(int alpha
) OVERRIDE
;
280 // Force the shelf to be presented in an undimmed state.
281 void ForceUndimming(bool force
);
283 // A function to test the current alpha used by the dimming bar. If there is
284 // no dimmer active, the function will return -1.
285 int GetDimmingAlphaForTest();
287 // A function to test the bounds of the dimming bar. Returns gfx::Rect() if
288 // the dimmer is inactive.
289 gfx::Rect
GetDimmerBoundsForTest();
291 // Disable dimming animations for running tests. This needs to be called
292 // prior to the creation of of the |dimmer_|.
293 void disable_dimming_animations_for_test() {
294 disable_dimming_animations_for_test_
= true;
299 scoped_ptr
<views::Widget
> dimmer_
;
300 internal::FocusCycler
* focus_cycler_
;
302 ui::Layer opaque_background_
;
304 // The view which does the dimming.
305 DimmerView
* dimmer_view_
;
307 // True if dimming animations should be turned off.
308 bool disable_dimming_animations_for_test_
;
310 DISALLOW_COPY_AND_ASSIGN(DelegateView
);
313 ShelfWidget::DelegateView::DelegateView(ShelfWidget
* shelf
)
317 opaque_background_(ui::LAYER_SOLID_COLOR
),
319 disable_dimming_animations_for_test_(false) {
320 set_allow_deactivate_on_esc(true);
321 opaque_background_
.SetColor(SK_ColorBLACK
);
322 opaque_background_
.SetBounds(GetLocalBounds());
323 opaque_background_
.SetOpacity(0.0f
);
326 ShelfWidget::DelegateView::~DelegateView() {
329 void ShelfWidget::DelegateView::SetDimmed(bool value
) {
330 if (value
== (dimmer_
.get() != NULL
))
334 dimmer_
.reset(new views::Widget
);
335 views::Widget::InitParams
params(
336 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS
);
337 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
338 params
.can_activate
= false;
339 params
.accept_events
= false;
340 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
341 params
.parent
= shelf_
->GetNativeView();
342 dimmer_
->Init(params
);
343 dimmer_
->GetNativeWindow()->SetName("ShelfDimmer");
344 dimmer_
->SetBounds(shelf_
->GetWindowBoundsInScreen());
345 // The launcher should not take focus when it is initially shown.
346 dimmer_
->set_focus_on_creation(false);
347 dimmer_view_
= new DimmerView(shelf_
, disable_dimming_animations_for_test_
);
348 dimmer_
->SetContentsView(dimmer_view_
);
349 dimmer_
->GetNativeView()->SetName("ShelfDimmerView");
357 bool ShelfWidget::DelegateView::GetDimmed() const {
358 return dimmer_
.get() && dimmer_
->IsVisible();
361 void ShelfWidget::DelegateView::SetParentLayer(ui::Layer
* layer
) {
362 layer
->Add(&opaque_background_
);
366 void ShelfWidget::DelegateView::OnPaintBackground(gfx::Canvas
* canvas
) {
367 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
368 gfx::ImageSkia launcher_background
=
369 *rb
.GetImageSkiaNamed(IDR_AURA_LAUNCHER_BACKGROUND
);
370 if (SHELF_ALIGNMENT_BOTTOM
!= shelf_
->GetAlignment())
371 launcher_background
= gfx::ImageSkiaOperations::CreateRotatedImage(
373 shelf_
->shelf_layout_manager()->SelectValueForShelfAlignment(
374 SkBitmapOperations::ROTATION_90_CW
,
375 SkBitmapOperations::ROTATION_90_CW
,
376 SkBitmapOperations::ROTATION_270_CW
,
377 SkBitmapOperations::ROTATION_180_CW
));
379 gfx::Rect black_rect
=
380 shelf_
->shelf_layout_manager()->SelectValueForShelfAlignment(
381 gfx::Rect(0, height() - kNumBlackPixels
, width(), kNumBlackPixels
),
382 gfx::Rect(0, 0, kNumBlackPixels
, height()),
383 gfx::Rect(width() - kNumBlackPixels
, 0, kNumBlackPixels
, height()),
384 gfx::Rect(0, 0, width(), kNumBlackPixels
));
387 paint
.setAlpha(alpha_
);
388 canvas
->DrawImageInt(
390 0, 0, launcher_background
.width(), launcher_background
.height(),
391 0, 0, width(), height(),
394 canvas
->FillRect(black_rect
, SK_ColorBLACK
);
397 bool ShelfWidget::DelegateView::CanActivate() const {
398 // Allow to activate as fallback.
399 if (shelf_
->activating_as_fallback_
)
401 // Allow to activate from the focus cycler.
402 if (focus_cycler_
&& focus_cycler_
->widget_activating() == GetWidget())
404 // Disallow activating in other cases, especially when using mouse.
408 void ShelfWidget::DelegateView::Layout() {
409 for(int i
= 0; i
< child_count(); ++i
) {
410 if (shelf_
->shelf_layout_manager()->IsHorizontalAlignment()) {
411 child_at(i
)->SetBounds(child_at(i
)->x(), child_at(i
)->y(),
412 child_at(i
)->width(), height());
414 child_at(i
)->SetBounds(child_at(i
)->x(), child_at(i
)->y(),
415 width(), child_at(i
)->height());
420 void ShelfWidget::DelegateView::ReorderChildLayers(ui::Layer
* parent_layer
) {
421 views::View::ReorderChildLayers(parent_layer
);
422 parent_layer
->StackAtBottom(&opaque_background_
);
425 void ShelfWidget::DelegateView::OnBoundsChanged(const gfx::Rect
& old_bounds
) {
426 opaque_background_
.SetBounds(GetLocalBounds());
428 dimmer_
->SetBounds(GetBoundsInScreen());
431 void ShelfWidget::DelegateView::ForceUndimming(bool force
) {
433 dimmer_view_
->ForceUndimming(force
);
436 int ShelfWidget::DelegateView::GetDimmingAlphaForTest() {
438 return dimmer_view_
->get_dimming_alpha_for_test();
442 gfx::Rect
ShelfWidget::DelegateView::GetDimmerBoundsForTest() {
444 return dimmer_view_
->GetBoundsInScreen();
448 void ShelfWidget::DelegateView::UpdateBackground(int alpha
) {
453 ShelfWidget::ShelfWidget(aura::Window
* shelf_container
,
454 aura::Window
* status_container
,
455 internal::WorkspaceController
* workspace_controller
)
456 : delegate_view_(new DelegateView(this)),
457 background_animator_(delegate_view_
, 0, kLauncherBackgroundAlpha
),
458 activating_as_fallback_(false),
459 window_container_(shelf_container
) {
460 views::Widget::InitParams
params(
461 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS
);
462 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
463 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
464 params
.parent
= shelf_container
;
465 params
.delegate
= delegate_view_
;
468 // The shelf should not take focus when initially shown.
469 set_focus_on_creation(false);
470 SetContentsView(delegate_view_
);
471 delegate_view_
->SetParentLayer(GetLayer());
473 status_area_widget_
= new internal::StatusAreaWidget(status_container
);
474 status_area_widget_
->CreateTrayViews();
475 if (Shell::GetInstance()->session_state_delegate()->
476 IsActiveUserSessionStarted()) {
477 status_area_widget_
->Show();
479 Shell::GetInstance()->focus_cycler()->AddWidget(status_area_widget_
);
481 shelf_layout_manager_
= new internal::ShelfLayoutManager(this);
482 shelf_container
->SetLayoutManager(shelf_layout_manager_
);
483 shelf_layout_manager_
->set_workspace_controller(workspace_controller
);
484 workspace_controller
->SetShelf(shelf_layout_manager_
);
486 status_container
->SetLayoutManager(
487 new internal::StatusAreaLayoutManager(this));
489 views::Widget::AddObserver(this);
492 ShelfWidget::~ShelfWidget() {
493 RemoveObserver(this);
496 void ShelfWidget::SetPaintsBackground(
497 ShelfBackgroundType background_type
,
498 internal::BackgroundAnimator::ChangeType change_type
) {
499 ui::Layer
* opaque_background
= delegate_view_
->opaque_background();
500 float target_opacity
=
501 (background_type
== SHELF_BACKGROUND_MAXIMIZED
) ? 1.0f
: 0.0f
;
502 scoped_ptr
<ui::ScopedLayerAnimationSettings
> opaque_background_animation
;
503 if (change_type
!= internal::BackgroundAnimator::CHANGE_IMMEDIATE
) {
504 opaque_background_animation
.reset(new ui::ScopedLayerAnimationSettings(
505 opaque_background
->GetAnimator()));
506 opaque_background_animation
->SetTransitionDuration(
507 base::TimeDelta::FromMilliseconds(kTimeToSwitchBackgroundMs
));
509 opaque_background
->SetOpacity(target_opacity
);
511 // TODO(mukai): use ui::Layer on both opaque_background and normal background
512 // retire background_animator_ at all. It would be simpler.
513 background_animator_
.SetPaintsBackground(
514 background_type
!= SHELF_BACKGROUND_DEFAULT
,
518 ShelfBackgroundType
ShelfWidget::GetBackgroundType() const {
519 if (delegate_view_
->opaque_background()->GetTargetOpacity() == 1.0f
)
520 return SHELF_BACKGROUND_MAXIMIZED
;
521 if (background_animator_
.paints_background())
522 return SHELF_BACKGROUND_OVERLAP
;
524 return SHELF_BACKGROUND_DEFAULT
;
528 bool ShelfWidget::ShelfAlignmentAllowed() {
529 if (!ash::switches::ShowShelfAlignmentMenu())
531 user::LoginStatus login_status
=
532 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
534 switch (login_status
) {
535 case user::LOGGED_IN_USER
:
536 case user::LOGGED_IN_OWNER
:
538 case user::LOGGED_IN_LOCKED
:
539 case user::LOGGED_IN_PUBLIC
:
540 case user::LOGGED_IN_LOCALLY_MANAGED
:
541 case user::LOGGED_IN_GUEST
:
542 case user::LOGGED_IN_RETAIL_MODE
:
543 case user::LOGGED_IN_KIOSK_APP
:
544 case user::LOGGED_IN_NONE
:
552 ShelfAlignment
ShelfWidget::GetAlignment() const {
553 return shelf_layout_manager_
->GetAlignment();
556 void ShelfWidget::SetAlignment(ShelfAlignment alignment
) {
558 launcher_
->SetAlignment(alignment
);
559 status_area_widget_
->SetShelfAlignment(alignment
);
560 delegate_view_
->SchedulePaint();
563 void ShelfWidget::SetDimsShelf(bool dimming
) {
564 delegate_view_
->SetDimmed(dimming
);
565 // Repaint all children, allowing updates to reflect dimmed state eg:
566 // status area background, app list button and overflow button.
568 launcher_
->SchedulePaint();
569 status_area_widget_
->GetContentsView()->SchedulePaint();
572 bool ShelfWidget::GetDimsShelf() const {
573 return delegate_view_
->GetDimmed();
576 void ShelfWidget::CreateLauncher() {
580 Shell
* shell
= Shell::GetInstance();
581 // This needs to be called before launcher_model().
582 LauncherDelegate
* launcher_delegate
= shell
->GetLauncherDelegate();
583 if (!launcher_delegate
)
584 return; // Not ready to create Launcher
586 launcher_
.reset(new Launcher(shell
->launcher_model(),
587 shell
->GetLauncherDelegate(),
589 SetFocusCycler(shell
->focus_cycler());
591 // Inform the root window controller.
592 internal::RootWindowController::ForWindow(window_container_
)->
595 launcher_
->SetVisible(
596 shell
->session_state_delegate()->IsActiveUserSessionStarted());
597 shelf_layout_manager_
->LayoutShelf();
601 bool ShelfWidget::IsLauncherVisible() const {
602 return launcher_
.get() && launcher_
->IsVisible();
605 void ShelfWidget::SetLauncherVisibility(bool visible
) {
607 launcher_
->SetVisible(visible
);
610 void ShelfWidget::SetFocusCycler(internal::FocusCycler
* focus_cycler
) {
611 delegate_view_
->set_focus_cycler(focus_cycler
);
613 focus_cycler
->AddWidget(this);
616 internal::FocusCycler
* ShelfWidget::GetFocusCycler() {
617 return delegate_view_
->focus_cycler();
620 void ShelfWidget::ShutdownStatusAreaWidget() {
621 if (status_area_widget_
)
622 status_area_widget_
->Shutdown();
623 status_area_widget_
= NULL
;
626 void ShelfWidget::ForceUndimming(bool force
) {
627 delegate_view_
->ForceUndimming(force
);
630 void ShelfWidget::OnWidgetActivationChanged(views::Widget
* widget
,
632 activating_as_fallback_
= false;
634 delegate_view_
->SetPaneFocusAndFocusDefault();
636 delegate_view_
->GetFocusManager()->ClearFocus();
639 int ShelfWidget::GetDimmingAlphaForTest() {
641 return delegate_view_
->GetDimmingAlphaForTest();
645 gfx::Rect
ShelfWidget::GetDimmerBoundsForTest() {
647 return delegate_view_
->GetDimmerBoundsForTest();
651 void ShelfWidget::DisableDimmingAnimationsForTest() {
652 DCHECK(delegate_view_
);
653 return delegate_view_
->disable_dimming_animations_for_test();