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/wm/overview/window_selector_item.h"
6
7#include "ash/screen_util.h"
8#include "ash/shell.h"
9#include "ash/shell_window_ids.h"
10#include "ash/wm/overview/scoped_transform_overview_window.h"
11#include "ash/wm/overview/transparent_activate_window_button.h"
12#include "base/auto_reset.h"
13#include "grit/ash_resources.h"
14#include "ui/aura/window.h"
15#include "ui/base/resource/resource_bundle.h"
16#include "ui/compositor/scoped_layer_animation_settings.h"
17#include "ui/views/controls/button/image_button.h"
18#include "ui/views/controls/label.h"
19#include "ui/views/layout/box_layout.h"
20#include "ui/views/widget/widget.h"
21
22namespace ash {
23
24namespace {
25
26views::Widget* CreateCloseWindowButton(aura::Window* root_window,
27                                       views::ButtonListener* listener) {
28  views::Widget* widget = new views::Widget;
29  views::Widget::InitParams params;
30  params.type = views::Widget::InitParams::TYPE_POPUP;
31  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
32  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
33  params.parent =
34      Shell::GetContainer(root_window, ash::kShellWindowId_OverlayContainer);
35  widget->set_focus_on_creation(false);
36  widget->Init(params);
37  views::ImageButton* button = new views::ImageButton(listener);
38  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
39  button->SetImage(views::CustomButton::STATE_NORMAL,
40                   rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE));
41  button->SetImage(views::CustomButton::STATE_HOVERED,
42                   rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_H));
43  button->SetImage(views::CustomButton::STATE_PRESSED,
44                   rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_P));
45  widget->SetContentsView(button);
46  widget->SetSize(rb.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE)->size());
47  widget->Show();
48  return widget;
49}
50
51}  // namespace
52
53// In the conceptual overview table, the window margin is the space reserved
54// around the window within the cell. This margin does not overlap so the
55// closest distance between adjacent windows will be twice this amount.
56static const int kWindowMargin = 30;
57
58// Foreground label color.
59static const SkColor kLabelColor = SK_ColorWHITE;
60
61// Background label color.
62static const SkColor kLabelBackground = SK_ColorTRANSPARENT;
63
64// Label shadow color.
65static const SkColor kLabelShadow = 0xB0000000;
66
67// Vertical padding for the label, both over and beneath it.
68static const int kVerticalLabelPadding = 20;
69
70// Solid shadow length from the label
71static const int kVerticalShadowOffset = 1;
72
73// Amount of blur applied to the label shadow
74static const int kShadowBlur = 10;
75
76views::Widget* CreateWindowLabel(aura::Window* root_window,
77                                 const base::string16 title) {
78  views::Widget* widget = new views::Widget;
79  views::Widget::InitParams params;
80  params.type = views::Widget::InitParams::TYPE_POPUP;
81  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
82  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
83  params.parent =
84      Shell::GetContainer(root_window, ash::kShellWindowId_OverlayContainer);
85  params.accept_events = false;
86  params.visible_on_all_workspaces = true;
87  widget->set_focus_on_creation(false);
88  widget->Init(params);
89  views::Label* label = new views::Label;
90  label->SetEnabledColor(kLabelColor);
91  label->SetBackgroundColor(kLabelBackground);
92  label->set_shadows(gfx::ShadowValues(1, gfx::ShadowValue(
93      gfx::Point(0, kVerticalShadowOffset), kShadowBlur, kLabelShadow)));
94  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
95  label->SetFontList(bundle.GetFontList(ui::ResourceBundle::BoldFont));
96  label->SetText(title);
97  views::BoxLayout* layout = new views::BoxLayout(views::BoxLayout::kVertical,
98                                                  0,
99                                                  kVerticalLabelPadding,
100                                                  0);
101  label->SetLayoutManager(layout);
102  widget->SetContentsView(label);
103  widget->Show();
104  return widget;
105}
106
107const int WindowSelectorItem::kFadeInMilliseconds = 80;
108
109WindowSelectorItem::WindowSelectorItem()
110    : root_window_(NULL),
111      in_bounds_update_(false) {
112}
113
114WindowSelectorItem::~WindowSelectorItem() {
115}
116
117void WindowSelectorItem::RemoveWindow(const aura::Window* window) {
118  // If empty WindowSelectorItem will be destroyed immediately after this by
119  // its owner.
120  if (empty())
121    return;
122  window_label_.reset();
123  UpdateWindowLabels(target_bounds_, root_window_, false);
124  UpdateCloseButtonBounds(root_window_, false);
125}
126
127void WindowSelectorItem::SetBounds(aura::Window* root_window,
128                                   const gfx::Rect& target_bounds,
129                                   bool animate) {
130  if (in_bounds_update_)
131    return;
132  base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true);
133  root_window_ = root_window;
134  target_bounds_ = target_bounds;
135
136  // Set the bounds of the transparent window handler to cover the entire
137  // bounding box area.
138  if (!activate_window_button_) {
139    activate_window_button_.reset(
140        new TransparentActivateWindowButton(SelectionWindow()));
141  }
142  activate_window_button_->SetBounds(target_bounds);
143
144  // TODO(nsatragno): Handle window title updates.
145  UpdateWindowLabels(target_bounds, root_window, animate);
146
147  gfx::Rect inset_bounds(target_bounds);
148  inset_bounds.Inset(kWindowMargin, kWindowMargin);
149  SetItemBounds(root_window, inset_bounds, animate);
150  UpdateCloseButtonBounds(root_window, animate);
151}
152
153void WindowSelectorItem::RecomputeWindowTransforms() {
154  if (in_bounds_update_ || target_bounds_.IsEmpty())
155    return;
156  DCHECK(root_window_);
157  base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true);
158  gfx::Rect inset_bounds(target_bounds_);
159  inset_bounds.Inset(kWindowMargin, kWindowMargin);
160  SetItemBounds(root_window_, inset_bounds, false);
161  UpdateCloseButtonBounds(root_window_, false);
162}
163
164void WindowSelectorItem::SendFocusAlert() const {
165  activate_window_button_->SendFocusAlert();
166}
167
168void WindowSelectorItem::ButtonPressed(views::Button* sender,
169                                       const ui::Event& event) {
170  views::Widget::GetWidgetForNativeView(SelectionWindow())->Close();
171}
172
173void WindowSelectorItem::UpdateCloseButtonBounds(aura::Window* root_window,
174                                                 bool animate) {
175  gfx::RectF align_bounds(SelectionWindow()->layer()->bounds());
176  gfx::Transform window_transform;
177  window_transform.Translate(align_bounds.x(), align_bounds.y());
178  window_transform.PreconcatTransform(SelectionWindow()->layer()->
179                                          GetTargetTransform());
180  window_transform.Translate(-align_bounds.x(), -align_bounds.y());
181  window_transform.TransformRect(&align_bounds);
182  gfx::Rect target_bounds = ToEnclosingRect(align_bounds);
183
184  gfx::Transform close_button_transform;
185  close_button_transform.Translate(target_bounds.right(), target_bounds.y());
186
187  // If the root window has changed, force the close button to be recreated
188  // and faded in on the new root window.
189  if (close_button_ &&
190      close_button_->GetNativeWindow()->GetRootWindow() != root_window) {
191    close_button_.reset();
192  }
193
194  if (!close_button_) {
195    close_button_.reset(CreateCloseWindowButton(root_window, this));
196    gfx::Rect close_button_rect(close_button_->GetNativeWindow()->bounds());
197    // Align the center of the button with position (0, 0) so that the
198    // translate transform does not need to take the button dimensions into
199    // account.
200    close_button_rect.set_x(-close_button_rect.width() / 2);
201    close_button_rect.set_y(-close_button_rect.height() / 2);
202    close_button_->GetNativeWindow()->SetBounds(close_button_rect);
203    close_button_->GetNativeWindow()->SetTransform(close_button_transform);
204    // The close button is initialized when entering overview, fade the button
205    // in after the window should be in place.
206    ui::Layer* layer = close_button_->GetNativeWindow()->layer();
207    layer->SetOpacity(0);
208    layer->GetAnimator()->StopAnimating();
209    layer->GetAnimator()->SchedulePauseForProperties(
210        base::TimeDelta::FromMilliseconds(
211            ScopedTransformOverviewWindow::kTransitionMilliseconds),
212        ui::LayerAnimationElement::OPACITY);
213    {
214      ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
215      settings.SetPreemptionStrategy(
216          ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
217      settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
218          WindowSelectorItem::kFadeInMilliseconds));
219      layer->SetOpacity(1);
220    }
221  } else {
222    if (animate) {
223      ui::ScopedLayerAnimationSettings settings(
224          close_button_->GetNativeWindow()->layer()->GetAnimator());
225      settings.SetPreemptionStrategy(
226          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
227      settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
228          ScopedTransformOverviewWindow::kTransitionMilliseconds));
229      close_button_->GetNativeWindow()->SetTransform(close_button_transform);
230    } else {
231      close_button_->GetNativeWindow()->SetTransform(close_button_transform);
232    }
233  }
234}
235
236void WindowSelectorItem::UpdateWindowLabels(const gfx::Rect& window_bounds,
237                                            aura::Window* root_window,
238                                            bool animate) {
239  gfx::Rect converted_bounds = ScreenUtil::ConvertRectFromScreen(root_window,
240                                                                 window_bounds);
241  gfx::Rect label_bounds(converted_bounds.x(),
242                         converted_bounds.bottom(),
243                         converted_bounds.width(),
244                         0);
245
246  // If the root window has changed, force the window label to be recreated
247  // and faded in on the new root window.
248  if (window_label_ &&
249      window_label_->GetNativeWindow()->GetRootWindow() != root_window) {
250    window_label_.reset();
251  }
252
253  if (!window_label_) {
254    window_label_.reset(CreateWindowLabel(root_window,
255                                          SelectionWindow()->title()));
256    label_bounds.set_height(window_label_->
257                                GetContentsView()->GetPreferredSize().height());
258    label_bounds.set_y(label_bounds.y() - window_label_->
259                           GetContentsView()->GetPreferredSize().height());
260    window_label_->GetNativeWindow()->SetBounds(label_bounds);
261    ui::Layer* layer = window_label_->GetNativeWindow()->layer();
262
263    layer->SetOpacity(0);
264    layer->GetAnimator()->StopAnimating();
265
266    layer->GetAnimator()->SchedulePauseForProperties(
267        base::TimeDelta::FromMilliseconds(
268            ScopedTransformOverviewWindow::kTransitionMilliseconds),
269        ui::LayerAnimationElement::OPACITY);
270
271    ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
272    settings.SetPreemptionStrategy(
273        ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
274    settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
275        kFadeInMilliseconds));
276    layer->SetOpacity(1);
277  } else {
278    label_bounds.set_height(window_label_->
279                                GetContentsView()->GetPreferredSize().height());
280    label_bounds.set_y(label_bounds.y() - window_label_->
281                           GetContentsView()->GetPreferredSize().height());
282    if (animate) {
283      ui::ScopedLayerAnimationSettings settings(
284          window_label_->GetNativeWindow()->layer()->GetAnimator());
285      settings.SetPreemptionStrategy(
286          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
287      settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
288          ScopedTransformOverviewWindow::kTransitionMilliseconds));
289      window_label_->GetNativeWindow()->SetBounds(label_bounds);
290    } else {
291      window_label_->GetNativeWindow()->SetBounds(label_bounds);
292    }
293  }
294}
295
296}  // namespace ash
297