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/controls/button/image_button.h"
31 #include "ui/views/controls/label.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 // In the conceptual overview table, the window margin is the space reserved
41 // around the window within the cell. This margin does not overlap so the
42 // closest distance between adjacent windows will be twice this amount.
43 static const int kWindowMargin
= 30;
45 // Foreground label color.
46 static const SkColor kLabelColor
= SK_ColorWHITE
;
48 // Background label color.
49 static const SkColor kLabelBackground
= SK_ColorTRANSPARENT
;
51 // Label shadow color.
52 static const SkColor kLabelShadow
= 0xB0000000;
54 // Vertical padding for the label, both over and beneath it.
55 static const int kVerticalLabelPadding
= 20;
57 // Solid shadow length from the label
58 static const int kVerticalShadowOffset
= 1;
60 // Amount of blur applied to the label shadow
61 static const int kShadowBlur
= 10;
63 // Opacity for dimmed items.
64 static const float kDimmedItemOpacity
= 0.5f
;
66 // Calculates the |window| bounds after being transformed to the selector's
67 // space. The returned Rect is in virtual screen coordinates.
68 gfx::Rect
GetTransformedBounds(aura::Window
* window
) {
69 gfx::RectF
bounds(ScreenUtil::ConvertRectToScreen(window
->GetRootWindow(),
70 window
->layer()->GetTargetBounds()));
71 gfx::Transform new_transform
= TransformAboutPivot(
72 gfx::Point(bounds
.x(), bounds
.y()),
73 window
->layer()->GetTargetTransform());
74 new_transform
.TransformRect(&bounds
);
75 return ToEnclosingRect(bounds
);
78 // Convenvience method to fade in a Window with predefined animation settings.
79 // Note: The fade in animation will occur after a delay where the delay is how
80 // long the lay out animations take.
81 void SetupFadeInAfterLayout(aura::Window
* window
) {
82 ui::Layer
* layer
= window
->layer();
83 layer
->SetOpacity(0.0f
);
84 ScopedOverviewAnimationSettings
animation_settings(
85 OverviewAnimationType::OVERVIEW_ANIMATION_ENTER_OVERVIEW_MODE_FADE_IN
,
87 layer
->SetOpacity(1.0f
);
90 // An image button with a close window icon.
91 class OverviewCloseButton
: public views::ImageButton
{
93 explicit OverviewCloseButton(views::ButtonListener
* listener
);
94 ~OverviewCloseButton() override
;
97 DISALLOW_COPY_AND_ASSIGN(OverviewCloseButton
);
100 OverviewCloseButton::OverviewCloseButton(views::ButtonListener
* listener
)
101 : views::ImageButton(listener
) {
102 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
103 SetImage(views::CustomButton::STATE_NORMAL
,
104 rb
.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE
));
105 SetImage(views::CustomButton::STATE_HOVERED
,
106 rb
.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_H
));
107 SetImage(views::CustomButton::STATE_PRESSED
,
108 rb
.GetImageSkiaNamed(IDR_AURA_WINDOW_OVERVIEW_CLOSE_P
));
111 OverviewCloseButton::~OverviewCloseButton() {
116 WindowSelectorItem::WindowSelectorItem(aura::Window
* root_window
)
118 root_window_(root_window
),
119 in_bounds_update_(false),
120 window_label_view_(nullptr),
121 close_button_(new OverviewCloseButton(this)),
122 selector_item_activate_window_button_(
123 new TransparentActivateWindowButton(root_window
, this)) {
124 views::Widget::InitParams params
;
125 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
126 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
127 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
128 params
.parent
= Shell::GetContainer(root_window
,
129 kShellWindowId_OverlayContainer
);
130 close_button_widget_
.set_focus_on_creation(false);
131 close_button_widget_
.Init(params
);
132 close_button_
->SetVisible(false);
133 close_button_widget_
.SetContentsView(close_button_
);
134 close_button_widget_
.SetSize(close_button_
->GetPreferredSize());
135 close_button_widget_
.Show();
137 gfx::Rect
close_button_rect(close_button_widget_
.GetNativeWindow()->bounds());
138 // Align the center of the button with position (0, 0) so that the
139 // translate transform does not need to take the button dimensions into
141 close_button_rect
.set_x(-close_button_rect
.width() / 2);
142 close_button_rect
.set_y(-close_button_rect
.height() / 2);
143 close_button_widget_
.GetNativeWindow()->SetBounds(close_button_rect
);
146 WindowSelectorItem::~WindowSelectorItem() {
147 for (auto* transform_window
: transform_windows_
) {
148 transform_window
->window()->RemoveObserver(this);
152 void WindowSelectorItem::AddWindow(aura::Window
* window
) {
153 DCHECK(window
->GetRootWindow() == root_window_
);
154 window
->AddObserver(this);
155 ScopedTransformOverviewWindow
* transform_window
=
156 new ScopedTransformOverviewWindow(window
);
157 transform_windows_
.push_back(transform_window
);
158 // The transparent overlays are added at the front of the z-order when
159 // created so make sure the selector item's transparent overlay is behind the
160 // overlay for the window that was just added.
161 transform_window
->activate_button()->StackAbove(
162 selector_item_activate_window_button_
.get());
164 UpdateSelectorButtons();
165 UpdateCloseButtonAccessibilityName();
168 bool WindowSelectorItem::HasSelectableWindow(const aura::Window
* window
) const {
169 for (auto* transform_window
: transform_windows_
) {
170 if (transform_window
->window() == window
)
176 bool WindowSelectorItem::Contains(const aura::Window
* target
) const {
177 for (auto* transform_window
: transform_windows_
) {
178 if (transform_window
->Contains(target
))
184 void WindowSelectorItem::RestoreWindowOnExit(aura::Window
* window
) {
185 for (auto* transform_window
: transform_windows_
) {
186 if (transform_window
->Contains(window
)) {
187 transform_window
->RestoreWindowOnExit();
193 aura::Window
* WindowSelectorItem::SelectionWindow() const {
194 return SelectionTransformWindow()->window();
197 void WindowSelectorItem::RemoveWindow(const aura::Window
* window
) {
198 bool window_found
= false;
200 for (TransformWindows::iterator iter
= transform_windows_
.begin();
201 iter
!= transform_windows_
.end();
203 ScopedTransformOverviewWindow
* transform_window
= *iter
;
205 if (transform_window
->window() == window
) {
206 transform_window
->window()->RemoveObserver(this);
207 transform_window
->OnWindowDestroyed();
208 transform_windows_
.erase(iter
);
216 // If empty WindowSelectorItem will be destroyed immediately after this by
221 UpdateCloseButtonAccessibilityName();
222 window_label_
.reset();
223 UpdateWindowLabels(target_bounds_
,
224 OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
225 UpdateCloseButtonLayout(OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
226 UpdateSelectorButtons();
229 bool WindowSelectorItem::empty() const {
230 return transform_windows_
.empty();
233 void WindowSelectorItem::PrepareForOverview() {
234 for (auto* transform_window
: transform_windows_
)
235 transform_window
->PrepareForOverview();
238 void WindowSelectorItem::SetBounds(aura::Window
* root_window
,
239 const gfx::Rect
& target_bounds
,
240 OverviewAnimationType animation_type
) {
241 if (in_bounds_update_
)
243 base::AutoReset
<bool> auto_reset_in_bounds_update(&in_bounds_update_
, true);
244 target_bounds_
= target_bounds
;
246 UpdateWindowLabels(target_bounds
, animation_type
);
248 gfx::Rect
inset_bounds(target_bounds
);
249 inset_bounds
.Inset(kWindowMargin
, kWindowMargin
);
250 SetItemBounds(root_window
, inset_bounds
, animation_type
);
252 // SetItemBounds is called before UpdateCloseButtonLayout so the close button
253 // can properly use the updated windows bounds.
254 UpdateCloseButtonLayout(animation_type
);
255 UpdateSelectorButtons();
258 void WindowSelectorItem::RecomputeWindowTransforms() {
259 if (in_bounds_update_
|| target_bounds_
.IsEmpty())
261 DCHECK(root_window_
);
262 base::AutoReset
<bool> auto_reset_in_bounds_update(&in_bounds_update_
, true);
263 gfx::Rect
inset_bounds(target_bounds_
);
264 inset_bounds
.Inset(kWindowMargin
, kWindowMargin
);
265 SetItemBounds(root_window_
, inset_bounds
,
266 OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
268 UpdateCloseButtonLayout(OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
269 UpdateSelectorButtons();
272 void WindowSelectorItem::SendFocusAlert() const {
273 selector_item_activate_window_button_
->SendFocusAlert();
276 void WindowSelectorItem::SetDimmed(bool dimmed
) {
278 SetOpacity(dimmed
? kDimmedItemOpacity
: 1.0f
);
281 void WindowSelectorItem::ButtonPressed(views::Button
* sender
,
282 const ui::Event
& event
) {
283 CHECK(!transform_windows_
.empty());
284 SelectionTransformWindow()->Close();
287 void WindowSelectorItem::OnWindowTitleChanged(aura::Window
* window
) {
288 // TODO(flackr): Maybe add the new title to a vector of titles so that we can
289 // filter any of the titles the window had while in the overview session.
290 if (window
== SelectionWindow()) {
291 window_label_view_
->SetText(window
->title());
292 UpdateCloseButtonAccessibilityName();
294 UpdateCloseButtonLayout(OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
295 UpdateSelectorButtons();
298 void WindowSelectorItem::Select() {
299 aura::Window
* selection_window
= SelectionWindow();
300 if (selection_window
)
301 wm::GetWindowState(selection_window
)->Activate();
304 void WindowSelectorItem::SetItemBounds(aura::Window
* root_window
,
305 const gfx::Rect
& target_bounds
,
306 OverviewAnimationType animation_type
) {
307 gfx::Rect bounding_rect
;
308 for (auto* transform_window
: transform_windows_
) {
310 transform_window
->GetTargetBoundsInScreen());
313 ScopedTransformOverviewWindow::ShrinkRectToFitPreservingAspectRatio(
314 bounding_rect
, target_bounds
);
315 gfx::Transform bounding_transform
=
316 ScopedTransformOverviewWindow::GetTransformForRect(bounding_rect
, bounds
);
317 for (auto* transform_window
: transform_windows_
) {
318 gfx::Rect target_bounds
= transform_window
->GetTargetBoundsInScreen();
319 gfx::Transform transform
= TransformAboutPivot(
320 gfx::Point(bounding_rect
.x() - target_bounds
.x(),
321 bounding_rect
.y() - target_bounds
.y()),
324 ScopedTransformOverviewWindow::ScopedAnimationSettings animation_settings
;
325 transform_window
->BeginScopedAnimation(animation_type
, &animation_settings
);
326 transform_window
->SetTransform(root_window
, transform
);
327 transform_window
->set_overview_transform(transform
);
331 void WindowSelectorItem::SetOpacity(float opacity
) {
332 window_label_
->GetNativeWindow()->layer()->SetOpacity(opacity
);
333 close_button_widget_
.GetNativeWindow()->layer()->SetOpacity(opacity
);
335 // TODO(flackr): find a way to make panels that are hidden behind other panels
337 for (auto* transform_window
: transform_windows_
) {
338 transform_window
->SetOpacity(opacity
);
342 void WindowSelectorItem::UpdateWindowLabels(
343 const gfx::Rect
& window_bounds
,
344 OverviewAnimationType animation_type
) {
345 // If the root window has changed, force the window label to be recreated
346 // and faded in on the new root window.
347 DCHECK(!window_label_
||
348 window_label_
->GetNativeWindow()->GetRootWindow() == root_window_
);
350 if (!window_label_
) {
351 CreateWindowLabel(SelectionWindow()->title());
352 SetupFadeInAfterLayout(window_label_
->GetNativeWindow());
355 gfx::Rect converted_bounds
= ScreenUtil::ConvertRectFromScreen(root_window_
,
357 gfx::Rect
label_bounds(converted_bounds
.x(),
358 converted_bounds
.bottom(),
359 converted_bounds
.width(),
361 label_bounds
.set_height(window_label_
->GetContentsView()->
362 GetPreferredSize().height());
363 label_bounds
.set_y(label_bounds
.y() - window_label_
->
364 GetContentsView()->GetPreferredSize().height());
366 ScopedOverviewAnimationSettings
animation_settings(animation_type
,
367 window_label_
->GetNativeWindow());
369 window_label_
->GetNativeWindow()->SetBounds(label_bounds
);
372 void WindowSelectorItem::CreateWindowLabel(const base::string16
& title
) {
373 window_label_
.reset(new views::Widget
);
374 views::Widget::InitParams params
;
375 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
376 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
377 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
378 params
.parent
= Shell::GetContainer(root_window_
,
379 kShellWindowId_OverlayContainer
);
380 params
.accept_events
= false;
381 params
.visible_on_all_workspaces
= true;
382 window_label_
->set_focus_on_creation(false);
383 window_label_
->Init(params
);
384 window_label_view_
= new views::Label
;
385 window_label_view_
->SetEnabledColor(kLabelColor
);
386 window_label_view_
->SetBackgroundColor(kLabelBackground
);
387 window_label_view_
->SetShadows(gfx::ShadowValues(
390 gfx::Point(0, kVerticalShadowOffset
), kShadowBlur
, kLabelShadow
)));
391 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
392 window_label_view_
->SetFontList(
393 bundle
.GetFontList(ui::ResourceBundle::BoldFont
));
394 window_label_view_
->SetText(title
);
395 views::BoxLayout
* layout
= new views::BoxLayout(views::BoxLayout::kVertical
,
397 kVerticalLabelPadding
,
399 window_label_view_
->SetLayoutManager(layout
);
400 window_label_
->SetContentsView(window_label_view_
);
401 window_label_
->Show();
404 void WindowSelectorItem::UpdateSelectorButtons() {
405 CHECK(!transform_windows_
.empty());
407 selector_item_activate_window_button_
->SetBounds(target_bounds());
408 selector_item_activate_window_button_
->SetAccessibleName(
409 transform_windows_
.front()->window()->title());
411 for (auto* transform_window
: transform_windows_
) {
412 TransparentActivateWindowButton
* activate_button
=
413 transform_window
->activate_button();
415 // If there is only one window in this, then expand the transparent overlay
416 // so that touch exploration in ChromVox only provides spoken feedback once
417 // within |this| selector item's bounds.
418 gfx::Rect bounds
= transform_windows_
.size() == 1
419 ? target_bounds() : GetTransformedBounds(transform_window
->window());
420 activate_button
->SetBounds(bounds
);
421 activate_button
->SetAccessibleName(transform_window
->window()->title());
425 void WindowSelectorItem::UpdateCloseButtonLayout(
426 OverviewAnimationType animation_type
) {
427 if (!close_button_
->visible()) {
428 close_button_
->SetVisible(true);
429 SetupFadeInAfterLayout(close_button_widget_
.GetNativeWindow());
431 ScopedOverviewAnimationSettings
animation_settings(animation_type
,
432 close_button_widget_
.GetNativeWindow());
434 gfx::Rect transformed_window_bounds
= ScreenUtil::ConvertRectFromScreen(
435 close_button_widget_
.GetNativeWindow()->GetRootWindow(),
436 GetTransformedBounds(SelectionWindow()));
438 gfx::Transform close_button_transform
;
439 close_button_transform
.Translate(transformed_window_bounds
.right(),
440 transformed_window_bounds
.y());
441 close_button_widget_
.GetNativeWindow()->SetTransform(
442 close_button_transform
);
445 void WindowSelectorItem::UpdateCloseButtonAccessibilityName() {
446 close_button_
->SetAccessibleName(l10n_util::GetStringFUTF16(
447 IDS_ASH_OVERVIEW_CLOSE_ITEM_BUTTON_ACCESSIBLE_NAME
,
448 SelectionWindow()->title()));
451 ScopedTransformOverviewWindow
*
452 WindowSelectorItem::SelectionTransformWindow() const {
453 CHECK(!transform_windows_
.empty());
454 return transform_windows_
.front();