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.
5 #include "ash/wm/overview/window_selector_item.h"
10 #include "ash/screen_util.h"
11 #include "ash/shell.h"
12 #include "ash/shell_window_ids.h"
13 #include "ash/wm/overview/overview_animation_type.h"
14 #include "ash/wm/overview/scoped_overview_animation_settings.h"
15 #include "ash/wm/overview/scoped_transform_overview_window.h"
16 #include "ash/wm/overview/window_selector_controller.h"
17 #include "ash/wm/window_state.h"
18 #include "base/auto_reset.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/time/time.h"
22 #include "grit/ash_resources.h"
23 #include "grit/ash_strings.h"
24 #include "ui/aura/window.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/geometry/vector2d.h"
28 #include "ui/gfx/transform_util.h"
29 #include "ui/strings/grit/ui_strings.h"
30 #include "ui/views/border.h"
31 #include "ui/views/controls/button/image_button.h"
32 #include "ui/views/layout/box_layout.h"
33 #include "ui/views/widget/widget.h"
34 #include "ui/wm/core/window_util.h"
40 // The minimum fling velocity which will cause a window to be closed. Unit is
42 const float kMinimumFlingVelocity
= 4000.0f
;
44 // The minimum opacity used during touch scroll gestures.
45 const float kMinimumOpacity
= 0.2f
;
47 // In the conceptual overview table, the window margin is the space reserved
48 // around the window within the cell. This margin does not overlap so the
49 // closest distance between adjacent windows will be twice this amount.
50 static const int kWindowMargin
= 30;
52 // Foreground label color.
53 static const SkColor kLabelColor
= SK_ColorWHITE
;
55 // Label shadow color.
56 static const SkColor kLabelShadow
= 0xB0000000;
58 // Vertical padding for the label, on top of it.
59 static const int kVerticalLabelPadding
= 20;
61 // Solid shadow length from the label
62 static const int kVerticalShadowOffset
= 1;
64 // Amount of blur applied to the label shadow
65 static const int kShadowBlur
= 10;
67 // Opacity for dimmed items.
68 static const float kDimmedItemOpacity
= 0.5f
;
70 // Calculates the |window| bounds after being transformed to the selector's
71 // space. The returned Rect is in virtual screen coordinates.
72 gfx::Rect
GetTransformedBounds(aura::Window
* window
) {
73 gfx::RectF
bounds(ScreenUtil::ConvertRectToScreen(window
->GetRootWindow(),
74 window
->layer()->GetTargetBounds()));
75 gfx::Transform new_transform
= TransformAboutPivot(
76 gfx::Point(bounds
.x(), bounds
.y()),
77 window
->layer()->GetTargetTransform());
78 new_transform
.TransformRect(&bounds
);
79 return ToEnclosingRect(bounds
);
82 // Convenvience method to fade in a Window with predefined animation settings.
83 // Note: The fade in animation will occur after a delay where the delay is how
84 // long the lay out animations take.
85 void SetupFadeInAfterLayout(aura::Window
* window
) {
86 ui::Layer
* layer
= window
->layer();
87 layer
->SetOpacity(0.0f
);
88 ScopedOverviewAnimationSettings
animation_settings(
89 OverviewAnimationType::OVERVIEW_ANIMATION_ENTER_OVERVIEW_MODE_FADE_IN
,
91 layer
->SetOpacity(1.0f
);
94 // Convenience method to fade out a window using the animation settings defined
95 // by OverviewAnimationType::OVERVIEW_ANIMATION_ENTER_OVERVIEW_MODE_FADE_OUT.
96 void SetupFadeOut(aura::Window
* window
) {
97 ScopedOverviewAnimationSettings
animation_settings(
98 OverviewAnimationType::OVERVIEW_ANIMATION_ENTER_OVERVIEW_MODE_FADE_OUT
,
100 window
->layer()->SetOpacity(0.0f
);
103 // Calculates the window opacity from the given scroll |distance| and the
104 // |min opacity_distance|.
105 float CalculateOpacityFromScrollDistance(int distance
,
106 int min_opacity_distance
) {
108 1.0f
- static_cast<float>(abs(distance
)) / min_opacity_distance
;
109 return std::min(1.0f
, std::max(kMinimumOpacity
, opacity
));
112 // An image button with a close window icon.
113 class OverviewCloseButton
: public views::ImageButton
{
115 explicit OverviewCloseButton(views::ButtonListener
* listener
);
116 ~OverviewCloseButton() override
;
119 DISALLOW_COPY_AND_ASSIGN(OverviewCloseButton
);
122 OverviewCloseButton::OverviewCloseButton(views::ButtonListener
* listener
)
123 : views::ImageButton(listener
) {
124 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
125 SetImage(views::CustomButton::STATE_NORMAL
,
126 rb
.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE
));
127 SetImage(views::CustomButton::STATE_HOVERED
,
128 rb
.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_H
));
129 SetImage(views::CustomButton::STATE_PRESSED
,
130 rb
.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_P
));
133 OverviewCloseButton::~OverviewCloseButton() {
138 WindowSelectorItem::OverviewLabelButton::OverviewLabelButton(
139 WindowSelectorItem
* selector_item
,
140 const base::string16
& text
)
141 : LabelButton(selector_item
, text
),
142 selector_item_(selector_item
),
146 WindowSelectorItem::OverviewLabelButton::~OverviewLabelButton() {
149 gfx::Rect
WindowSelectorItem::OverviewLabelButton::GetChildAreaBounds() {
150 gfx::Rect bounds
= GetLocalBounds();
151 bounds
.Inset(0, top_padding_
, 0, 0);
155 void WindowSelectorItem::OverviewLabelButton::OnGestureEvent(
156 ui::GestureEvent
* event
) {
157 selector_item_
->OnGestureEvent(event
);
158 views::LabelButton::OnGestureEvent(event
);
161 WindowSelectorItem::WindowSelectorItem(aura::Window
* window
)
163 root_window_(window
->GetRootWindow()),
164 transform_window_(window
),
165 in_bounds_update_(false),
166 window_label_button_view_(nullptr),
167 close_button_(new OverviewCloseButton(this)) {
168 CreateWindowLabel(window
->title());
169 views::Widget::InitParams params
;
170 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
171 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
172 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
173 params
.parent
= Shell::GetContainer(root_window_
,
174 kShellWindowId_OverlayContainer
);
175 close_button_widget_
.set_focus_on_creation(false);
176 close_button_widget_
.Init(params
);
177 close_button_
->SetVisible(false);
178 close_button_widget_
.SetContentsView(close_button_
);
179 close_button_widget_
.SetSize(close_button_
->GetPreferredSize());
180 close_button_widget_
.Show();
182 gfx::Rect
close_button_rect(close_button_widget_
.GetNativeWindow()->bounds());
183 // Align the center of the button with position (0, 0) so that the
184 // translate transform does not need to take the button dimensions into
186 close_button_rect
.set_x(-close_button_rect
.width() / 2);
187 close_button_rect
.set_y(-close_button_rect
.height() / 2);
188 close_button_widget_
.GetNativeWindow()->SetBounds(close_button_rect
);
190 GetWindow()->AddObserver(this);
193 WindowSelectorItem::~WindowSelectorItem() {
194 GetWindow()->RemoveObserver(this);
197 aura::Window
* WindowSelectorItem::GetWindow() {
198 return transform_window_
.window();
201 void WindowSelectorItem::RestoreWindow() {
202 transform_window_
.RestoreWindow();
205 void WindowSelectorItem::ShowWindowOnExit() {
206 transform_window_
.ShowWindowOnExit();
209 void WindowSelectorItem::PrepareForOverview() {
210 transform_window_
.PrepareForOverview();
213 bool WindowSelectorItem::Contains(const aura::Window
* target
) const {
214 return transform_window_
.Contains(target
);
217 void WindowSelectorItem::SetBounds(const gfx::Rect
& target_bounds
,
218 OverviewAnimationType animation_type
) {
219 if (in_bounds_update_
)
221 base::AutoReset
<bool> auto_reset_in_bounds_update(&in_bounds_update_
, true);
222 target_bounds_
= target_bounds
;
224 gfx::Rect
inset_bounds(target_bounds
);
225 inset_bounds
.Inset(kWindowMargin
, kWindowMargin
);
226 SetItemBounds(inset_bounds
, animation_type
);
228 // SetItemBounds is called before UpdateCloseButtonLayout so the close button
229 // can properly use the updated windows bounds.
230 UpdateCloseButtonLayout(animation_type
);
231 UpdateWindowLabel(target_bounds
, animation_type
);
234 void WindowSelectorItem::RecomputeWindowTransforms() {
235 if (in_bounds_update_
|| target_bounds_
.IsEmpty())
237 base::AutoReset
<bool> auto_reset_in_bounds_update(&in_bounds_update_
, true);
238 gfx::Rect
inset_bounds(target_bounds_
);
239 inset_bounds
.Inset(kWindowMargin
, kWindowMargin
);
240 SetItemBounds(inset_bounds
, OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
241 UpdateCloseButtonLayout(OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
244 void WindowSelectorItem::SendFocusAlert() const {
245 window_label_button_view_
->NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, true);
248 void WindowSelectorItem::SetDimmed(bool dimmed
) {
250 SetOpacity(dimmed
? kDimmedItemOpacity
: 1.0f
);
253 void WindowSelectorItem::ButtonPressed(views::Button
* sender
,
254 const ui::Event
& event
) {
255 if (sender
== close_button_
) {
256 transform_window_
.Close();
259 CHECK(sender
== window_label_button_view_
);
260 wm::GetWindowState(transform_window_
.window())->Activate();
263 void WindowSelectorItem::OnGestureEvent(ui::GestureEvent
* event
) {
264 if (Shell::GetInstance()->window_selector_controller()->
265 swipe_to_close_disabled())
269 if (event
->type() == ui::ET_GESTURE_SCROLL_BEGIN
)
270 scroll_x_origin_
= event
->x();
272 delta_x
= event
->x() - scroll_x_origin_
;
274 switch (event
->type()) {
275 case ui::ET_GESTURE_SCROLL_BEGIN
: {
276 // We need to call SetHandled() for the ET_GESTURE_SCROLL_BEGIN event so
277 // that future ET_GESTURE_SCROLL_* events are sent here.
279 close_button_
->SetEnabled(false);
280 SetupFadeOut(close_button_widget_
.GetNativeWindow());
283 case ui::ET_GESTURE_SCROLL_UPDATE
: {
285 ScopedTransformOverviewWindow::ScopedAnimationSettings
287 transform_window_
.BeginScopedAnimation(
288 OverviewAnimationType::OVERVIEW_ANIMATION_SCROLL_SELECTOR_ITEM
,
289 &animation_settings
);
291 gfx::Transform new_transform
;
292 new_transform
.Translate(delta_x
, 0);
293 new_transform
.PreconcatTransform(
294 transform_window_
.get_overview_transform());
295 transform_window_
.SetTransform(root_window(), new_transform
);
297 const float opacity
= CalculateOpacityFromScrollDistance(delta_x
,
298 GetMinimumCloseDistance());
299 transform_window_
.SetOpacity(opacity
);
302 case ui::ET_GESTURE_SCROLL_END
: {
304 if (abs(delta_x
) > GetMinimumCloseDistance()) {
305 transform_window_
.Close();
308 ResetScrolledWindow();
311 case ui::ET_SCROLL_FLING_START
: {
313 if (abs(delta_x
) > GetMinimumCloseDistance() ||
314 fabs(event
->details().velocity_x()) > kMinimumFlingVelocity
) {
315 transform_window_
.Close();
318 ResetScrolledWindow();
321 case ui::ET_GESTURE_END
:
322 scroll_x_origin_
= 0;
329 void WindowSelectorItem::OnWindowDestroying(aura::Window
* window
) {
330 window
->RemoveObserver(this);
331 transform_window_
.OnWindowDestroyed();
334 void WindowSelectorItem::OnWindowTitleChanged(aura::Window
* window
) {
335 // TODO(flackr): Maybe add the new title to a vector of titles so that we can
336 // filter any of the titles the window had while in the overview session.
337 window_label_button_view_
->SetText(window
->title());
338 UpdateCloseButtonAccessibilityName();
341 void WindowSelectorItem::ResetScrolledWindow() {
342 ScopedTransformOverviewWindow::ScopedAnimationSettings animation_settings
;
343 transform_window_
.BeginScopedAnimation(
344 OverviewAnimationType::OVERVIEW_ANIMATION_CANCEL_SELECTOR_ITEM_SCROLL
,
345 &animation_settings
);
347 transform_window_
.SetTransform(root_window(),
348 transform_window_
.get_overview_transform());
349 transform_window_
.SetOpacity(1.0);
351 SetupFadeInAfterLayout(close_button_widget_
.GetNativeWindow());
352 close_button_
->SetEnabled(true);
355 void WindowSelectorItem::SetItemBounds(const gfx::Rect
& target_bounds
,
356 OverviewAnimationType animation_type
) {
357 DCHECK(root_window_
== GetWindow()->GetRootWindow());
358 gfx::Rect screen_bounds
= transform_window_
.GetTargetBoundsInScreen();
359 gfx::Rect selector_item_bounds
=
360 ScopedTransformOverviewWindow::ShrinkRectToFitPreservingAspectRatio(
361 screen_bounds
, target_bounds
);
362 gfx::Transform transform
=
363 ScopedTransformOverviewWindow::GetTransformForRect(screen_bounds
,
364 selector_item_bounds
);
365 ScopedTransformOverviewWindow::ScopedAnimationSettings animation_settings
;
366 transform_window_
.BeginScopedAnimation(animation_type
, &animation_settings
);
367 transform_window_
.SetTransform(root_window_
, transform
);
368 transform_window_
.set_overview_transform(transform
);
371 void WindowSelectorItem::SetOpacity(float opacity
) {
372 window_label_
->GetNativeWindow()->layer()->SetOpacity(opacity
);
373 close_button_widget_
.GetNativeWindow()->layer()->SetOpacity(opacity
);
375 transform_window_
.SetOpacity(opacity
);
378 void WindowSelectorItem::UpdateWindowLabel(
379 const gfx::Rect
& window_bounds
,
380 OverviewAnimationType animation_type
) {
381 // If the root window has changed, force the window label to be recreated
382 // and faded in on the new root window.
383 DCHECK(!window_label_
||
384 window_label_
->GetNativeWindow()->GetRootWindow() == root_window_
);
386 if (!window_label_
->IsVisible()) {
387 window_label_
->Show();
388 SetupFadeInAfterLayout(window_label_
->GetNativeWindow());
391 gfx::Rect converted_bounds
=
392 ScreenUtil::ConvertRectFromScreen(root_window_
, window_bounds
);
393 gfx::Rect
label_bounds(converted_bounds
.x(), converted_bounds
.y(),
394 converted_bounds
.width(), converted_bounds
.height());
395 window_label_button_view_
->set_top_padding(label_bounds
.height() -
396 kVerticalLabelPadding
);
397 ScopedOverviewAnimationSettings
animation_settings(
398 animation_type
, window_label_
->GetNativeWindow());
400 window_label_
->GetNativeWindow()->SetBounds(label_bounds
);
403 void WindowSelectorItem::CreateWindowLabel(const base::string16
& title
) {
404 window_label_
.reset(new views::Widget
);
405 views::Widget::InitParams params
;
406 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
407 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
408 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
410 Shell::GetContainer(root_window_
, kShellWindowId_OverlayContainer
);
411 params
.visible_on_all_workspaces
= true;
412 window_label_
->set_focus_on_creation(false);
413 window_label_
->Init(params
);
414 window_label_button_view_
= new OverviewLabelButton(this, title
);
415 window_label_button_view_
->SetBorder(views::Border::NullBorder());
416 window_label_button_view_
->SetTextColor(views::LabelButton::STATE_NORMAL
,
418 window_label_button_view_
->SetTextColor(views::LabelButton::STATE_HOVERED
,
420 window_label_button_view_
->SetTextColor(views::LabelButton::STATE_PRESSED
,
422 window_label_button_view_
->set_animate_on_state_change(false);
423 window_label_button_view_
->SetHorizontalAlignment(gfx::ALIGN_CENTER
);
424 window_label_button_view_
->SetTextShadows(gfx::ShadowValues(
425 1, gfx::ShadowValue(gfx::Point(0, kVerticalShadowOffset
), kShadowBlur
,
427 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
428 window_label_button_view_
->SetFontList(
429 bundle
.GetFontList(ui::ResourceBundle::BoldFont
));
430 window_label_
->SetContentsView(window_label_button_view_
);
433 void WindowSelectorItem::UpdateCloseButtonLayout(
434 OverviewAnimationType animation_type
) {
435 if (!close_button_
->visible()) {
436 close_button_
->SetVisible(true);
437 SetupFadeInAfterLayout(close_button_widget_
.GetNativeWindow());
439 ScopedOverviewAnimationSettings
animation_settings(animation_type
,
440 close_button_widget_
.GetNativeWindow());
442 gfx::Rect transformed_window_bounds
= ScreenUtil::ConvertRectFromScreen(
443 close_button_widget_
.GetNativeWindow()->GetRootWindow(),
444 GetTransformedBounds(GetWindow()));
446 gfx::Transform close_button_transform
;
447 close_button_transform
.Translate(transformed_window_bounds
.right(),
448 transformed_window_bounds
.y());
449 close_button_widget_
.GetNativeWindow()->SetTransform(
450 close_button_transform
);
453 void WindowSelectorItem::UpdateCloseButtonAccessibilityName() {
454 close_button_
->SetAccessibleName(l10n_util::GetStringFUTF16(
455 IDS_ASH_OVERVIEW_CLOSE_ITEM_BUTTON_ACCESSIBLE_NAME
,
456 GetWindow()->title()));
459 int WindowSelectorItem::GetMinimumCloseDistance() const {
460 return target_bounds_
.size().width() / 2;