blob: e8d26c28e25ab9dd2af355199c39cf92a19344c7 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/overview/window_grid.h"
#include <algorithm>
#include <functional>
#include <set>
#include <utility>
#include <vector>
#include "ash/ash_switches.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/screen_util.h"
#include "ash/shelf/shelf.h"
#include "ash/wm/overview/cleanup_animation_observer.h"
#include "ash/wm/overview/scoped_overview_animation_settings.h"
#include "ash/wm/overview/window_selector.h"
#include "ash/wm/overview/window_selector_delegate.h"
#include "ash/wm/overview/window_selector_item.h"
#include "ash/wm/window_state.h"
#include "base/command_line.h"
#include "base/i18n/string_search.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/pathops/SkPathOps.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/shadow.h"
#include "ui/wm/core/shadow_types.h"
#include "ui/wm/core/window_animations.h"
namespace ash {
namespace {
// Time it takes for the selector widget to move to the next target. The same
// time is used for fading out shield widget when the overview mode is opened
// or closed.
const int kOverviewSelectorTransitionMilliseconds = 250;
// The color and opacity of the screen shield in overview.
const SkColor kShieldColor = SkColorSetARGB(255, 0, 0, 0);
const float kShieldOpacity = 0.7f;
// The color and opacity of the overview selector.
const SkColor kWindowSelectionColor = SkColorSetARGB(51, 255, 255, 255);
const SkColor kWindowSelectionBorderColor = SkColorSetARGB(76, 255, 255, 255);
// Border thickness of overview selector.
const int kWindowSelectionBorderThickness = 1;
// Corner radius of the overview selector border.
const int kWindowSelectionRadius = 4;
// In the conceptual overview table, the window margin is the space reserved
// around the window within the cell. This margin does not overlap so the
// closest distance between adjacent windows will be twice this amount.
const int kWindowMargin = 5;
// Windows are not allowed to get taller than this.
const int kMaxHeight = 512;
// Margins reserved in the overview mode.
const float kOverviewInsetRatio = 0.05f;
// Additional vertical inset reserved for windows in overview mode.
const float kOverviewVerticalInset = 0.1f;
// A View having rounded corners and a specified background color which is
// only painted within the bounds defined by the rounded corners.
// TODO(varkha): This duplicates code from RoundedImageView. Refactor these
// classes and move into ui/views.
class RoundedRectView : public views::View {
public:
RoundedRectView(int corner_radius, SkColor background)
: corner_radius_(corner_radius), background_(background) {}
~RoundedRectView() override {}
void OnPaint(gfx::Canvas* canvas) override {
views::View::OnPaint(canvas);
SkScalar radius = SkIntToScalar(corner_radius_);
const SkScalar kRadius[8] = {radius, radius, radius, radius,
radius, radius, radius, radius};
SkPath path;
gfx::Rect bounds(size());
bounds.set_height(bounds.height() + radius);
path.addRoundRect(gfx::RectToSkRect(bounds), kRadius);
canvas->ClipPath(path, true);
canvas->DrawColor(background_);
}
private:
int corner_radius_;
SkColor background_;
DISALLOW_COPY_AND_ASSIGN(RoundedRectView);
};
// BackgroundWith1PxBorder renders a solid background color, with a one pixel
// border with rounded corners. This accounts for the scaling of the canvas, so
// that the border is 1 pixel thick regardless of display scaling.
class BackgroundWith1PxBorder : public views::Background {
public:
BackgroundWith1PxBorder(SkColor background,
SkColor border_color,
int border_thickness,
int corner_radius);
void Paint(gfx::Canvas* canvas, views::View* view) const override;
private:
// Color for the one pixel border.
SkColor border_color_;
// Thickness of border inset.
int border_thickness_;
// Corner radius of the inside edge of the roundrect border stroke.
int corner_radius_;
DISALLOW_COPY_AND_ASSIGN(BackgroundWith1PxBorder);
};
BackgroundWith1PxBorder::BackgroundWith1PxBorder(SkColor background,
SkColor border_color,
int border_thickness,
int corner_radius)
: border_color_(border_color),
border_thickness_(border_thickness),
corner_radius_(corner_radius) {
SetNativeControlColor(background);
}
void BackgroundWith1PxBorder::Paint(gfx::Canvas* canvas,
views::View* view) const {
gfx::RectF border_rect_f(view->GetContentsBounds());
gfx::ScopedCanvas scoped_canvas(canvas);
const float scale = canvas->UndoDeviceScaleFactor();
border_rect_f.Scale(scale);
const float inset = border_thickness_ * scale - 0.5f;
border_rect_f.Inset(inset, inset);
SkPath path;
const SkScalar scaled_corner_radius =
SkFloatToScalar(corner_radius_ * scale + 0.5f);
path.addRoundRect(gfx::RectFToSkRect(border_rect_f), scaled_corner_radius,
scaled_corner_radius);
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(1);
flags.setAntiAlias(true);
SkPath stroke_path;
flags.getFillPath(path, &stroke_path);
SkPath fill_path;
Op(path, stroke_path, kDifference_SkPathOp, &fill_path);
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setColor(get_color());
canvas->sk_canvas()->drawPath(fill_path, flags);
if (border_thickness_ > 0) {
flags.setColor(border_color_);
canvas->sk_canvas()->drawPath(stroke_path, flags);
}
}
// Returns the vector for the fade in animation.
gfx::Vector2d GetSlideVectorForFadeIn(WindowSelector::Direction direction,
const gfx::Rect& bounds) {
gfx::Vector2d vector;
switch (direction) {
case WindowSelector::UP:
case WindowSelector::LEFT:
vector.set_x(-bounds.width());
break;
case WindowSelector::DOWN:
case WindowSelector::RIGHT:
vector.set_x(bounds.width());
break;
}
return vector;
}
// Creates and returns a background translucent widget parented in
// |root_window|'s default container and having |background_color|.
// When |border_thickness| is non-zero, a border is created having
// |border_color|, otherwise |border_color| parameter is ignored.
// The new background widget starts with |initial_opacity| and then fades in.
views::Widget* CreateBackgroundWidget(aura::Window* root_window,
ui::LayerType layer_type,
SkColor background_color,
int border_thickness,
int border_radius,
SkColor border_color,
float initial_opacity) {
views::Widget* widget = new views::Widget;
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_POPUP;
params.keep_on_top = false;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.layer_type = layer_type;
params.accept_events = false;
widget->set_focus_on_creation(false);
// Parenting in kShellWindowId_WallpaperContainer allows proper layering of
// the shield and selection widgets. Since that container is created with
// USE_LOCAL_COORDINATES BoundsInScreenBehavior local bounds in |root_window_|
// need to be provided.
params.parent = root_window->GetChildById(kShellWindowId_WallpaperContainer);
widget->Init(params);
aura::Window* widget_window = widget->GetNativeWindow();
// Disable the "bounce in" animation when showing the window.
::wm::SetWindowVisibilityAnimationTransition(widget_window,
::wm::ANIMATE_NONE);
// The background widget should not activate the shelf when passing under it.
wm::GetWindowState(widget_window)->set_ignored_by_shelf(true);
if (params.layer_type == ui::LAYER_SOLID_COLOR) {
widget_window->layer()->SetColor(background_color);
} else {
views::View* content_view =
new RoundedRectView(border_radius, SK_ColorTRANSPARENT);
content_view->SetBackground(base::MakeUnique<BackgroundWith1PxBorder>(
background_color, border_color, border_thickness, border_radius));
widget->SetContentsView(content_view);
}
widget_window->parent()->StackChildAtTop(widget_window);
widget->Show();
widget_window->layer()->SetOpacity(initial_opacity);
return widget;
}
bool IsMinimizedStateType(wm::WindowStateType type) {
return type == wm::WINDOW_STATE_TYPE_MINIMIZED;
}
} // namespace
WindowGrid::WindowGrid(aura::Window* root_window,
const std::vector<aura::Window*>& windows,
WindowSelector* window_selector)
: root_window_(root_window),
window_selector_(window_selector),
window_observer_(this),
window_state_observer_(this),
selected_index_(0),
num_columns_(0),
prepared_for_overview_(false) {
aura::Window::Windows windows_in_root;
for (auto* window : windows) {
if (window->GetRootWindow() == root_window)
windows_in_root.push_back(window);
}
for (auto* window : windows_in_root) {
window_observer_.Add(window);
window_state_observer_.Add(wm::GetWindowState(window));
window_list_.push_back(
base::MakeUnique<WindowSelectorItem>(window, window_selector_));
}
}
WindowGrid::~WindowGrid() {}
void WindowGrid::Shutdown() {
for (const auto& window : window_list_)
window->Shutdown();
if (shield_widget_) {
// Fade out the shield widget. This animation continues past the lifetime
// of |this|.
aura::Window* widget_window = shield_widget_->GetNativeWindow();
ui::ScopedLayerAnimationSettings animation_settings(
widget_window->layer()->GetAnimator());
animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
kOverviewSelectorTransitionMilliseconds));
animation_settings.SetTweenType(gfx::Tween::EASE_OUT);
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
// CleanupAnimationObserver will delete itself (and the shield widget) when
// the opacity animation is complete.
// Ownership over the observer is passed to the window_selector_->delegate()
// which has longer lifetime so that animations can continue even after the
// overview mode is shut down.
views::Widget* shield_widget = shield_widget_.get();
std::unique_ptr<CleanupAnimationObserver> observer(
new CleanupAnimationObserver(std::move(shield_widget_)));
animation_settings.AddObserver(observer.get());
window_selector_->delegate()->AddDelayedAnimationObserver(
std::move(observer));
shield_widget->SetOpacity(0.f);
}
}
void WindowGrid::PrepareForOverview() {
InitShieldWidget();
for (const auto& window : window_list_)
window->PrepareForOverview();
prepared_for_overview_ = true;
}
void WindowGrid::PositionWindows(bool animate) {
if (window_selector_->is_shut_down() || window_list_.empty())
return;
DCHECK(shield_widget_.get());
// Keep the background shield widget covering the whole screen.
aura::Window* widget_window = shield_widget_->GetNativeWindow();
const gfx::Rect bounds = widget_window->parent()->bounds();
widget_window->SetBounds(bounds);
gfx::Rect total_bounds = ScreenUtil::GetDisplayWorkAreaBoundsInParent(
root_window_->GetChildById(kShellWindowId_DefaultContainer));
::wm::ConvertRectToScreen(root_window_, &total_bounds);
// Windows occupy vertically centered area with additional vertical insets.
int horizontal_inset =
gfx::ToFlooredInt(std::min(kOverviewInsetRatio * total_bounds.width(),
kOverviewInsetRatio * total_bounds.height()));
int vertical_inset =
horizontal_inset +
kOverviewVerticalInset * (total_bounds.height() - 2 * horizontal_inset);
total_bounds.Inset(std::max(0, horizontal_inset - kWindowMargin),
std::max(0, vertical_inset - kWindowMargin));
std::vector<gfx::Rect> rects;
// Keep track of the lowest coordinate.
int max_bottom = total_bounds.y();
// Right bound of the narrowest row.
int min_right = total_bounds.right();
// Right bound of the widest row.
int max_right = total_bounds.x();
// Keep track of the difference between the narrowest and the widest row.
// Initially this is set to the worst it can ever be assuming the windows fit.
int width_diff = total_bounds.width();
// Initially allow the windows to occupy all available width. Shrink this
// available space horizontally to find the breakdown into rows that achieves
// the minimal |width_diff|.
int right_bound = total_bounds.right();
// Determine the optimal height bisecting between |low_height| and
// |high_height|. Once this optimal height is known, |height_fixed| is set to
// true and the rows are balanced by repeatedly squeezing the widest row to
// cause windows to overflow to the subsequent rows.
int low_height = 2 * kWindowMargin;
int high_height =
std::max(low_height, static_cast<int>(total_bounds.height() + 1));
int height = 0.5 * (low_height + high_height);
bool height_fixed = false;
// Repeatedly try to fit the windows |rects| within |right_bound|.
// If a maximum |height| is found such that all window |rects| fit, this
// fitting continues while shrinking the |right_bound| in order to balance the
// rows. If the windows fit the |right_bound| would have been decremented at
// least once so it needs to be incremented once before getting out of this
// loop and one additional pass made to actually fit the |rects|.
// If the |rects| cannot fit (e.g. there are too many windows) the bisection
// will still finish and we might increment the |right_bound| once pixel extra
// which is acceptable since there is an unused margin on the right.
bool make_last_adjustment = false;
while (true) {
gfx::Rect overview_bounds(total_bounds);
overview_bounds.set_width(right_bound - total_bounds.x());
bool windows_fit = FitWindowRectsInBounds(
overview_bounds, std::min(kMaxHeight + 2 * kWindowMargin, height),
&rects, &max_bottom, &min_right, &max_right);
if (height_fixed) {
if (!windows_fit) {
// Revert the previous change to |right_bound| and do one last pass.
right_bound++;
make_last_adjustment = true;
break;
}
// Break if all the windows are zero-width at the current scale.
if (max_right <= total_bounds.x())
break;
} else {
// Find the optimal row height bisecting between |low_height| and
// |high_height|.
if (windows_fit)
low_height = height;
else
high_height = height;
height = 0.5 * (low_height + high_height);
// When height can no longer be improved, start balancing the rows.
if (height == low_height)
height_fixed = true;
}
if (windows_fit && height_fixed) {
if (max_right - min_right <= width_diff) {
// Row alignment is getting better. Try to shrink the |right_bound| in
// order to squeeze the widest row.
right_bound = max_right - 1;
width_diff = max_right - min_right;
} else {
// Row alignment is getting worse.
// Revert the previous change to |right_bound| and do one last pass.
right_bound++;
make_last_adjustment = true;
break;
}
}
}
// Once the windows in |window_list_| no longer fit, the change to
// |right_bound| was reverted. Perform one last pass to position the |rects|.
if (make_last_adjustment) {
gfx::Rect overview_bounds(total_bounds);
overview_bounds.set_width(right_bound - total_bounds.x());
FitWindowRectsInBounds(overview_bounds,
std::min(kMaxHeight + 2 * kWindowMargin, height),
&rects, &max_bottom, &min_right, &max_right);
}
// Position the windows centering the left-aligned rows vertically.
gfx::Vector2d offset(0, (total_bounds.bottom() - max_bottom) / 2);
for (size_t i = 0; i < window_list_.size(); ++i) {
window_list_[i]->SetBounds(
rects[i] + offset,
animate
? OverviewAnimationType::OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS
: OverviewAnimationType::OVERVIEW_ANIMATION_NONE);
}
// If the selection widget is active, reposition it without any animation.
if (selection_widget_)
MoveSelectionWidgetToTarget(animate);
}
bool WindowGrid::Move(WindowSelector::Direction direction, bool animate) {
bool recreate_selection_widget = false;
bool out_of_bounds = false;
bool changed_selection_index = false;
gfx::Rect old_bounds;
if (SelectedWindow()) {
old_bounds = SelectedWindow()->target_bounds();
// Make the old selected window header non-transparent first.
SelectedWindow()->SetSelected(false);
}
// [up] key is equivalent to [left] key and [down] key is equivalent to
// [right] key.
if (!selection_widget_) {
switch (direction) {
case WindowSelector::UP:
case WindowSelector::LEFT:
selected_index_ = window_list_.size() - 1;
break;
case WindowSelector::DOWN:
case WindowSelector::RIGHT:
selected_index_ = 0;
break;
}
changed_selection_index = true;
}
while (!changed_selection_index ||
(!out_of_bounds && window_list_[selected_index_]->dimmed())) {
switch (direction) {
case WindowSelector::UP:
case WindowSelector::LEFT:
if (selected_index_ == 0)
out_of_bounds = true;
selected_index_--;
break;
case WindowSelector::DOWN:
case WindowSelector::RIGHT:
if (selected_index_ >= window_list_.size() - 1)
out_of_bounds = true;
selected_index_++;
break;
}
if (!out_of_bounds && SelectedWindow()) {
if (SelectedWindow()->target_bounds().y() != old_bounds.y())
recreate_selection_widget = true;
}
changed_selection_index = true;
}
MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds,
animate);
// Make the new selected window header fully transparent.
if (SelectedWindow())
SelectedWindow()->SetSelected(true);
return out_of_bounds;
}
WindowSelectorItem* WindowGrid::SelectedWindow() const {
if (!selection_widget_)
return nullptr;
CHECK(selected_index_ < window_list_.size());
return window_list_[selected_index_].get();
}
bool WindowGrid::Contains(const aura::Window* window) const {
for (const auto& window_item : window_list_) {
if (window_item->Contains(window))
return true;
}
return false;
}
void WindowGrid::FilterItems(const base::string16& pattern) {
base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents finder(pattern);
for (const auto& window : window_list_) {
if (finder.Search(window->GetWindow()->GetTitle(), nullptr, nullptr)) {
window->SetDimmed(false);
} else {
window->SetDimmed(true);
if (selection_widget_ && SelectedWindow() == window.get()) {
SelectedWindow()->SetSelected(false);
selection_widget_.reset();
selector_shadow_.reset();
}
}
}
}
void WindowGrid::WindowClosing(WindowSelectorItem* window) {
if (!selection_widget_ || SelectedWindow() != window)
return;
aura::Window* selection_widget_window = selection_widget_->GetNativeWindow();
ScopedOverviewAnimationSettings animation_settings_label(
OverviewAnimationType::OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM,
selection_widget_window);
selection_widget_->SetOpacity(0.f);
}
void WindowGrid::OnWindowDestroying(aura::Window* window) {
window_observer_.Remove(window);
window_state_observer_.Remove(wm::GetWindowState(window));
auto iter = std::find_if(window_list_.begin(), window_list_.end(),
[window](std::unique_ptr<WindowSelectorItem>& item) {
return item->GetWindow() == window;
});
DCHECK(iter != window_list_.end());
size_t removed_index = iter - window_list_.begin();
window_list_.erase(iter);
if (empty()) {
// If the grid is now empty, notify the window selector so that it erases us
// from its grid list.
window_selector_->OnGridEmpty(this);
return;
}
// If selecting, update the selection index.
if (selection_widget_) {
bool send_focus_alert = selected_index_ == removed_index;
if (selected_index_ >= removed_index && selected_index_ != 0)
selected_index_--;
SelectedWindow()->SetSelected(true);
if (send_focus_alert)
SelectedWindow()->SendAccessibleSelectionEvent();
}
PositionWindows(true);
}
void WindowGrid::OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
// During preparation, window bounds can change. Ignore bounds
// change notifications in this case; we'll reposition soon.
if (!prepared_for_overview_)
return;
auto iter = std::find_if(window_list_.begin(), window_list_.end(),
[window](std::unique_ptr<WindowSelectorItem>& item) {
return item->GetWindow() == window;
});
DCHECK(iter != window_list_.end());
// Immediately finish any active bounds animation.
window->layer()->GetAnimator()->StopAnimatingProperty(
ui::LayerAnimationElement::BOUNDS);
PositionWindows(false);
}
void WindowGrid::OnPostWindowStateTypeChange(wm::WindowState* window_state,
wm::WindowStateType old_type) {
// During preparation, window state can change, e.g. updating shelf
// visibility may show the temporarily hidden (minimized) panels.
if (!prepared_for_overview_)
return;
wm::WindowStateType new_type = window_state->GetStateType();
if (IsMinimizedStateType(old_type) == IsMinimizedStateType(new_type))
return;
auto iter =
std::find_if(window_list_.begin(), window_list_.end(),
[window_state](std::unique_ptr<WindowSelectorItem>& item) {
return item->Contains(window_state->window());
});
if (iter != window_list_.end()) {
(*iter)->OnMinimizedStateChanged();
PositionWindows(false);
}
}
void WindowGrid::InitShieldWidget() {
// TODO(varkha): The code assumes that SHELF_BACKGROUND_MAXIMIZED is
// synonymous with a black shelf background. Update this code if that
// assumption is no longer valid.
const float initial_opacity =
(Shelf::ForWindow(root_window_)->GetBackgroundType() ==
SHELF_BACKGROUND_MAXIMIZED)
? 1.f
: 0.f;
shield_widget_.reset(
CreateBackgroundWidget(root_window_, ui::LAYER_SOLID_COLOR, kShieldColor,
0, 0, SK_ColorTRANSPARENT, initial_opacity));
aura::Window* widget_window = shield_widget_->GetNativeWindow();
const gfx::Rect bounds = widget_window->parent()->bounds();
widget_window->SetBounds(bounds);
widget_window->SetName("OverviewModeShield");
ui::ScopedLayerAnimationSettings animation_settings(
widget_window->layer()->GetAnimator());
animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
kOverviewSelectorTransitionMilliseconds));
animation_settings.SetTweenType(gfx::Tween::EASE_OUT);
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
shield_widget_->SetOpacity(kShieldOpacity);
}
void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) {
selection_widget_.reset(CreateBackgroundWidget(
root_window_, ui::LAYER_TEXTURED, kWindowSelectionColor,
kWindowSelectionBorderThickness, kWindowSelectionRadius,
kWindowSelectionBorderColor, 0.f));
aura::Window* widget_window = selection_widget_->GetNativeWindow();
gfx::Rect target_bounds = SelectedWindow()->target_bounds();
::wm::ConvertRectFromScreen(root_window_, &target_bounds);
gfx::Vector2d fade_out_direction =
GetSlideVectorForFadeIn(direction, target_bounds);
widget_window->SetBounds(target_bounds - fade_out_direction);
widget_window->SetName("OverviewModeSelector");
selector_shadow_.reset(new ::wm::Shadow());
selector_shadow_->Init(::wm::ShadowElevation::LARGE);
selector_shadow_->layer()->SetVisible(true);
selection_widget_->GetLayer()->SetMasksToBounds(false);
selection_widget_->GetLayer()->Add(selector_shadow_->layer());
selector_shadow_->SetContentBounds(gfx::Rect(target_bounds.size()));
}
void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction,
bool recreate_selection_widget,
bool out_of_bounds,
bool animate) {
// If the selection widget is already active, fade it out in the selection
// direction.
if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) {
// Animate the old selection widget and then destroy it.
views::Widget* old_selection = selection_widget_.get();
aura::Window* old_selection_window = old_selection->GetNativeWindow();
gfx::Vector2d fade_out_direction =
GetSlideVectorForFadeIn(direction, old_selection_window->bounds());
ui::ScopedLayerAnimationSettings animation_settings(
old_selection_window->layer()->GetAnimator());
animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
kOverviewSelectorTransitionMilliseconds));
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN);
// CleanupAnimationObserver will delete itself (and the widget) when the
// motion animation is complete.
// Ownership over the observer is passed to the window_selector_->delegate()
// which has longer lifetime so that animations can continue even after the
// overview mode is shut down.
std::unique_ptr<CleanupAnimationObserver> observer(
new CleanupAnimationObserver(std::move(selection_widget_)));
animation_settings.AddObserver(observer.get());
window_selector_->delegate()->AddDelayedAnimationObserver(
std::move(observer));
old_selection->SetOpacity(0.f);
old_selection_window->SetBounds(old_selection_window->bounds() +
fade_out_direction);
old_selection->Hide();
}
if (out_of_bounds)
return;
if (!selection_widget_)
InitSelectionWidget(direction);
// Send an a11y alert so that if ChromeVox is enabled, the item label is
// read.
SelectedWindow()->SendAccessibleSelectionEvent();
// The selection widget is moved to the newly selected item in the same
// grid.
MoveSelectionWidgetToTarget(animate);
}
void WindowGrid::MoveSelectionWidgetToTarget(bool animate) {
gfx::Rect bounds = SelectedWindow()->target_bounds();
::wm::ConvertRectFromScreen(root_window_, &bounds);
if (animate) {
aura::Window* selection_widget_window =
selection_widget_->GetNativeWindow();
ui::ScopedLayerAnimationSettings animation_settings(
selection_widget_window->layer()->GetAnimator());
animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
kOverviewSelectorTransitionMilliseconds));
animation_settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
selection_widget_->SetBounds(bounds);
selection_widget_->SetOpacity(1.f);
if (selector_shadow_) {
ui::ScopedLayerAnimationSettings animation_settings_shadow(
selector_shadow_->shadow_layer()->GetAnimator());
animation_settings_shadow.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(
kOverviewSelectorTransitionMilliseconds));
animation_settings_shadow.SetTweenType(gfx::Tween::EASE_IN_OUT);
animation_settings_shadow.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
bounds.Inset(1, 1);
selector_shadow_->SetContentBounds(
gfx::Rect(gfx::Point(1, 1), bounds.size()));
}
return;
}
selection_widget_->SetBounds(bounds);
selection_widget_->SetOpacity(1.f);
if (selector_shadow_) {
bounds.Inset(1, 1);
selector_shadow_->SetContentBounds(
gfx::Rect(gfx::Point(1, 1), bounds.size()));
}
}
bool WindowGrid::FitWindowRectsInBounds(const gfx::Rect& bounds,
int height,
std::vector<gfx::Rect>* rects,
int* max_bottom,
int* min_right,
int* max_right) {
rects->resize(window_list_.size());
bool windows_fit = true;
// Start in the top-left corner of |bounds|.
int left = bounds.x();
int top = bounds.y();
// Keep track of the lowest coordinate.
*max_bottom = bounds.y();
// Right bound of the narrowest row.
*min_right = bounds.right();
// Right bound of the widest row.
*max_right = bounds.x();
// All elements are of same height and only the height is necessary to
// determine each item's scale.
const gfx::Size item_size(0, height);
size_t i = 0;
for (const auto& window : window_list_) {
const gfx::Rect target_bounds = window->GetTargetBoundsInScreen();
const int width =
std::max(1, gfx::ToFlooredInt(target_bounds.width() *
window->GetItemScale(item_size)) +
2 * kWindowMargin);
if (left + width > bounds.right()) {
// Move to the next row if possible.
if (*min_right > left)
*min_right = left;
if (*max_right < left)
*max_right = left;
top += height;
// Check if the new row reaches the bottom or if the first item in the new
// row does not fit within the available width.
if (top + height > bounds.bottom() ||
bounds.x() + width > bounds.right()) {
windows_fit = false;
break;
}
left = bounds.x();
}
// Position the current rect.
(*rects)[i].SetRect(left, top, width, height);
// Increment horizontal position using sanitized positive |width()|.
left += (*rects)[i].width();
if (++i == window_list_.size()) {
// Update the narrowest and widest row width for the last row.
if (*min_right > left)
*min_right = left;
if (*max_right < left)
*max_right = left;
}
*max_bottom = top + height;
}
return windows_fit;
}
} // namespace ash