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
76const int WindowSelectorItem::kFadeInMilliseconds = 80;
77
78// Opacity for dimmed items.
79static const float kDimmedItemOpacity = 0.5f;
80
81WindowSelectorItem::WindowSelectorItem()
82    : dimmed_(false),
83      root_window_(NULL),
84      in_bounds_update_(false),
85      window_label_view_(NULL) {
86}
87
88WindowSelectorItem::~WindowSelectorItem() {
89}
90
91void WindowSelectorItem::RemoveWindow(const aura::Window* window) {
92  // If empty WindowSelectorItem will be destroyed immediately after this by
93  // its owner.
94  if (empty())
95    return;
96  window_label_.reset();
97  UpdateWindowLabels(target_bounds_, root_window_, false);
98  UpdateCloseButtonBounds(root_window_, false);
99}
100
101void WindowSelectorItem::SetBounds(aura::Window* root_window,
102                                   const gfx::Rect& target_bounds,
103                                   bool animate) {
104  if (in_bounds_update_)
105    return;
106  base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true);
107  root_window_ = root_window;
108  target_bounds_ = target_bounds;
109
110  // Set the bounds of the transparent window handler to cover the entire
111  // bounding box area.
112  if (!activate_window_button_) {
113    activate_window_button_.reset(
114        new TransparentActivateWindowButton(SelectionWindow()));
115  }
116  activate_window_button_->SetBounds(target_bounds);
117
118  UpdateWindowLabels(target_bounds, root_window, animate);
119
120  gfx::Rect inset_bounds(target_bounds);
121  inset_bounds.Inset(kWindowMargin, kWindowMargin);
122  SetItemBounds(root_window, inset_bounds, animate);
123  UpdateCloseButtonBounds(root_window, animate);
124}
125
126void WindowSelectorItem::RecomputeWindowTransforms() {
127  if (in_bounds_update_ || target_bounds_.IsEmpty())
128    return;
129  DCHECK(root_window_);
130  base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true);
131  gfx::Rect inset_bounds(target_bounds_);
132  inset_bounds.Inset(kWindowMargin, kWindowMargin);
133  SetItemBounds(root_window_, inset_bounds, false);
134  UpdateCloseButtonBounds(root_window_, false);
135}
136
137void WindowSelectorItem::SendFocusAlert() const {
138  activate_window_button_->SendFocusAlert();
139}
140
141void WindowSelectorItem::SetDimmed(bool dimmed) {
142  dimmed_ = dimmed;
143  SetOpacity(dimmed ? kDimmedItemOpacity : 1.0f);
144}
145
146void WindowSelectorItem::ButtonPressed(views::Button* sender,
147                                       const ui::Event& event) {
148  views::Widget::GetWidgetForNativeView(SelectionWindow())->Close();
149}
150
151void WindowSelectorItem::OnWindowTitleChanged(aura::Window* window) {
152  // TODO(flackr): Maybe add the new title to a vector of titles so that we can
153  // filter any of the titles the window had while in the overview session.
154  if (window == SelectionWindow())
155    window_label_view_->SetText(window->title());
156}
157
158void WindowSelectorItem::UpdateCloseButtonBounds(aura::Window* root_window,
159                                                 bool animate) {
160  gfx::RectF align_bounds(SelectionWindow()->layer()->bounds());
161  gfx::Transform window_transform;
162  window_transform.Translate(align_bounds.x(), align_bounds.y());
163  window_transform.PreconcatTransform(SelectionWindow()->layer()->
164                                          GetTargetTransform());
165  window_transform.Translate(-align_bounds.x(), -align_bounds.y());
166  window_transform.TransformRect(&align_bounds);
167  gfx::Rect target_bounds = ToEnclosingRect(align_bounds);
168
169  gfx::Transform close_button_transform;
170  close_button_transform.Translate(target_bounds.right(), target_bounds.y());
171
172  // If the root window has changed, force the close button to be recreated
173  // and faded in on the new root window.
174  if (close_button_ &&
175      close_button_->GetNativeWindow()->GetRootWindow() != root_window) {
176    close_button_.reset();
177  }
178
179  if (!close_button_) {
180    close_button_.reset(CreateCloseWindowButton(root_window, this));
181    gfx::Rect close_button_rect(close_button_->GetNativeWindow()->bounds());
182    // Align the center of the button with position (0, 0) so that the
183    // translate transform does not need to take the button dimensions into
184    // account.
185    close_button_rect.set_x(-close_button_rect.width() / 2);
186    close_button_rect.set_y(-close_button_rect.height() / 2);
187    close_button_->GetNativeWindow()->SetBounds(close_button_rect);
188    close_button_->GetNativeWindow()->SetTransform(close_button_transform);
189    // The close button is initialized when entering overview, fade the button
190    // in after the window should be in place.
191    ui::Layer* layer = close_button_->GetNativeWindow()->layer();
192    layer->SetOpacity(0);
193    layer->GetAnimator()->StopAnimating();
194    layer->GetAnimator()->SchedulePauseForProperties(
195        base::TimeDelta::FromMilliseconds(
196            ScopedTransformOverviewWindow::kTransitionMilliseconds),
197        ui::LayerAnimationElement::OPACITY);
198    {
199      ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
200      settings.SetPreemptionStrategy(
201          ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
202      settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
203          WindowSelectorItem::kFadeInMilliseconds));
204      layer->SetOpacity(1);
205    }
206  } else {
207    if (animate) {
208      ui::ScopedLayerAnimationSettings settings(
209          close_button_->GetNativeWindow()->layer()->GetAnimator());
210      settings.SetPreemptionStrategy(
211          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
212      settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
213          ScopedTransformOverviewWindow::kTransitionMilliseconds));
214      close_button_->GetNativeWindow()->SetTransform(close_button_transform);
215    } else {
216      close_button_->GetNativeWindow()->SetTransform(close_button_transform);
217    }
218  }
219}
220
221void WindowSelectorItem::SetOpacity(float opacity) {
222  window_label_->GetNativeWindow()->layer()->SetOpacity(opacity);
223  close_button_->GetNativeWindow()->layer()->SetOpacity(opacity);
224}
225
226void WindowSelectorItem::UpdateWindowLabels(const gfx::Rect& window_bounds,
227                                            aura::Window* root_window,
228                                            bool animate) {
229  gfx::Rect converted_bounds = ScreenUtil::ConvertRectFromScreen(root_window,
230                                                                 window_bounds);
231  gfx::Rect label_bounds(converted_bounds.x(),
232                         converted_bounds.bottom(),
233                         converted_bounds.width(),
234                         0);
235
236  // If the root window has changed, force the window label to be recreated
237  // and faded in on the new root window.
238  if (window_label_ &&
239      window_label_->GetNativeWindow()->GetRootWindow() != root_window) {
240    window_label_.reset();
241  }
242
243  if (!window_label_) {
244    CreateWindowLabel(SelectionWindow()->title());
245    label_bounds.set_height(window_label_view_->GetPreferredSize().height());
246    label_bounds.set_y(label_bounds.y() - window_label_view_->
247                           GetPreferredSize().height());
248    window_label_->GetNativeWindow()->SetBounds(label_bounds);
249    ui::Layer* layer = window_label_->GetNativeWindow()->layer();
250
251    layer->SetOpacity(0);
252    layer->GetAnimator()->StopAnimating();
253
254    layer->GetAnimator()->SchedulePauseForProperties(
255        base::TimeDelta::FromMilliseconds(
256            ScopedTransformOverviewWindow::kTransitionMilliseconds),
257        ui::LayerAnimationElement::OPACITY);
258
259    ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
260    settings.SetPreemptionStrategy(
261        ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
262    settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
263        kFadeInMilliseconds));
264    layer->SetOpacity(1);
265  } else {
266    label_bounds.set_height(window_label_->
267                                GetContentsView()->GetPreferredSize().height());
268    label_bounds.set_y(label_bounds.y() - window_label_->
269                           GetContentsView()->GetPreferredSize().height());
270    if (animate) {
271      ui::ScopedLayerAnimationSettings settings(
272          window_label_->GetNativeWindow()->layer()->GetAnimator());
273      settings.SetPreemptionStrategy(
274          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
275      settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
276          ScopedTransformOverviewWindow::kTransitionMilliseconds));
277      window_label_->GetNativeWindow()->SetBounds(label_bounds);
278    } else {
279      window_label_->GetNativeWindow()->SetBounds(label_bounds);
280    }
281  }
282}
283
284void WindowSelectorItem::CreateWindowLabel(const base::string16& title) {
285  window_label_.reset(new views::Widget);
286  views::Widget::InitParams params;
287  params.type = views::Widget::InitParams::TYPE_POPUP;
288  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
289  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
290  params.parent =
291      Shell::GetContainer(root_window_, ash::kShellWindowId_OverlayContainer);
292  params.accept_events = false;
293  params.visible_on_all_workspaces = true;
294  window_label_->set_focus_on_creation(false);
295  window_label_->Init(params);
296  window_label_view_ = new views::Label;
297  window_label_view_->SetEnabledColor(kLabelColor);
298  window_label_view_->SetBackgroundColor(kLabelBackground);
299  window_label_view_->SetShadows(gfx::ShadowValues(
300      1,
301      gfx::ShadowValue(
302          gfx::Point(0, kVerticalShadowOffset), kShadowBlur, kLabelShadow)));
303  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
304  window_label_view_->SetFontList(
305      bundle.GetFontList(ui::ResourceBundle::BoldFont));
306  window_label_view_->SetText(title);
307  views::BoxLayout* layout = new views::BoxLayout(views::BoxLayout::kVertical,
308                                                  0,
309                                                  kVerticalLabelPadding,
310                                                  0);
311  window_label_view_->SetLayoutManager(layout);
312  window_label_->SetContentsView(window_label_view_);
313  window_label_->Show();
314}
315
316}  // namespace ash
317