| // 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 |