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.
4
5#include "ash/wm/workspace/phantom_window_controller.h"
6
7#include <math.h>
8
9#include "ash/shell.h"
10#include "ash/shell_window_ids.h"
11#include "ash/wm/coordinate_conversion.h"
12#include "grit/ash_resources.h"
13#include "ui/aura/window.h"
14#include "ui/compositor/layer.h"
15#include "ui/compositor/scoped_layer_animation_settings.h"
16#include "ui/views/background.h"
17#include "ui/views/painter.h"
18#include "ui/views/view.h"
19#include "ui/views/widget/widget.h"
20
21namespace ash {
22namespace {
23
24// The duration of the show animation.
25const int kAnimationDurationMs = 200;
26
27// The size of the phantom window at the beginning of the show animation in
28// relation to the size of the phantom window at the end of the animation.
29const float kStartBoundsRatio = 0.85f;
30
31// The amount of pixels that the phantom window's shadow should extend past
32// the bounds passed into Show().
33const int kShadowThickness = 15;
34
35// The minimum size of a phantom window including the shadow. The minimum size
36// is derived from the size of the IDR_AURA_PHANTOM_WINDOW image assets.
37const int kMinSizeWithShadow = 100;
38
39// Adjusts the phantom window's bounds so that the bounds:
40// - Include the size of the shadow.
41// - Have a size equal to or larger than the minimum phantom window size.
42gfx::Rect GetAdjustedBounds(const gfx::Rect& bounds) {
43  int x_inset = std::max(
44      static_cast<int>(ceil((kMinSizeWithShadow - bounds.width()) / 2.0f)),
45      kShadowThickness);
46  int y_inset = std::max(
47      static_cast<int>(ceil((kMinSizeWithShadow - bounds.height()) / 2.0f)),
48      kShadowThickness);
49
50  gfx::Rect adjusted_bounds(bounds);
51  adjusted_bounds.Inset(-x_inset, -y_inset);
52  return adjusted_bounds;
53}
54
55// Starts an animation of |widget| to |new_bounds_in_screen|. No-op if |widget|
56// is NULL.
57void AnimateToBounds(views::Widget* widget,
58                     const gfx::Rect& new_bounds_in_screen) {
59  if (!widget)
60    return;
61
62  ui::ScopedLayerAnimationSettings scoped_setter(
63      widget->GetNativeWindow()->layer()->GetAnimator());
64  scoped_setter.SetTweenType(gfx::Tween::EASE_IN);
65  scoped_setter.SetPreemptionStrategy(
66      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
67  scoped_setter.SetTransitionDuration(
68      base::TimeDelta::FromMilliseconds(kAnimationDurationMs));
69  widget->SetBounds(new_bounds_in_screen);
70}
71
72}  // namespace
73
74// PhantomWindowController ----------------------------------------------------
75
76PhantomWindowController::PhantomWindowController(aura::Window* window)
77    : window_(window) {
78}
79
80PhantomWindowController::~PhantomWindowController() {
81}
82
83void PhantomWindowController::Show(const gfx::Rect& bounds_in_screen) {
84  gfx::Rect adjusted_bounds_in_screen = GetAdjustedBounds(bounds_in_screen);
85  if (adjusted_bounds_in_screen == target_bounds_in_screen_)
86    return;
87  target_bounds_in_screen_ = adjusted_bounds_in_screen;
88
89  gfx::Rect start_bounds_in_screen = target_bounds_in_screen_;
90  int start_width = std::max(
91      kMinSizeWithShadow,
92      static_cast<int>(start_bounds_in_screen.width() * kStartBoundsRatio));
93  int start_height = std::max(
94      kMinSizeWithShadow,
95      static_cast<int>(start_bounds_in_screen.height() * kStartBoundsRatio));
96  start_bounds_in_screen.Inset(
97      floor((start_bounds_in_screen.width() - start_width) / 2.0f),
98      floor((start_bounds_in_screen.height() - start_height) / 2.0f));
99  phantom_widget_ = CreatePhantomWidget(
100      wm::GetRootWindowMatching(target_bounds_in_screen_),
101      start_bounds_in_screen);
102
103  AnimateToBounds(phantom_widget_.get(), target_bounds_in_screen_);
104}
105
106scoped_ptr<views::Widget> PhantomWindowController::CreatePhantomWidget(
107    aura::Window* root_window,
108    const gfx::Rect& bounds_in_screen) {
109  scoped_ptr<views::Widget> phantom_widget(new views::Widget);
110  views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
111  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
112  // PhantomWindowController is used by FrameMaximizeButton to highlight the
113  // launcher button. Put the phantom in the same window as the launcher so that
114  // the phantom is visible.
115  params.parent = Shell::GetContainer(root_window,
116                                      kShellWindowId_ShelfContainer);
117  params.keep_on_top = true;
118  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
119  phantom_widget->set_focus_on_creation(false);
120  phantom_widget->Init(params);
121  phantom_widget->SetVisibilityChangedAnimationsEnabled(false);
122  phantom_widget->GetNativeWindow()->SetName("PhantomWindow");
123  phantom_widget->GetNativeWindow()->set_id(kShellWindowId_PhantomWindow);
124  phantom_widget->SetBounds(bounds_in_screen);
125  phantom_widget->StackAbove(window_);
126
127  const int kImages[] = IMAGE_GRID(IDR_AURA_PHANTOM_WINDOW);
128  views::Painter* background_painter =
129      views::Painter::CreateImageGridPainter(kImages);
130  views::View* content_view = new views::View;
131  content_view->set_background(
132      views::Background::CreateBackgroundPainter(true, background_painter));
133  phantom_widget->SetContentsView(content_view);
134
135  // Show the widget after all the setups.
136  phantom_widget->Show();
137
138  // Fade the window in.
139  ui::Layer* widget_layer = phantom_widget->GetNativeWindow()->layer();
140  widget_layer->SetOpacity(0);
141  ui::ScopedLayerAnimationSettings scoped_setter(widget_layer->GetAnimator());
142  scoped_setter.SetTransitionDuration(
143      base::TimeDelta::FromMilliseconds(kAnimationDurationMs));
144  widget_layer->SetOpacity(1);
145
146  return phantom_widget.Pass();
147}
148
149}  // namespace ash
150