| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "ash/common/wm/overview/window_grid.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <functional> | |
| 9 #include <set> | |
| 10 #include <utility> | |
| 11 #include <vector> | |
| 12 | |
| 13 #include "ash/common/ash_switches.h" | |
| 14 #include "ash/common/shelf/wm_shelf.h" | |
| 15 #include "ash/common/wm/overview/cleanup_animation_observer.h" | |
| 16 #include "ash/common/wm/overview/scoped_overview_animation_settings.h" | |
| 17 #include "ash/common/wm/overview/scoped_overview_animation_settings_factory.h" | |
| 18 #include "ash/common/wm/overview/window_selector.h" | |
| 19 #include "ash/common/wm/overview/window_selector_delegate.h" | |
| 20 #include "ash/common/wm/overview/window_selector_item.h" | |
| 21 #include "ash/common/wm/window_state.h" | |
| 22 #include "ash/common/wm/wm_screen_util.h" | |
| 23 #include "ash/common/wm_window.h" | |
| 24 #include "ash/public/cpp/shelf_types.h" | |
| 25 #include "ash/public/cpp/shell_window_ids.h" | |
| 26 #include "ash/root_window_controller.h" | |
| 27 #include "ash/wm/window_state_aura.h" | |
| 28 #include "base/command_line.h" | |
| 29 #include "base/i18n/string_search.h" | |
| 30 #include "base/memory/ptr_util.h" | |
| 31 #include "base/strings/string_number_conversions.h" | |
| 32 #include "third_party/skia/include/core/SkColor.h" | |
| 33 #include "third_party/skia/include/pathops/SkPathOps.h" | |
| 34 #include "ui/compositor/layer_animation_observer.h" | |
| 35 #include "ui/compositor/scoped_layer_animation_settings.h" | |
| 36 #include "ui/gfx/animation/tween.h" | |
| 37 #include "ui/gfx/canvas.h" | |
| 38 #include "ui/gfx/geometry/safe_integer_conversions.h" | |
| 39 #include "ui/gfx/geometry/vector2d.h" | |
| 40 #include "ui/gfx/scoped_canvas.h" | |
| 41 #include "ui/views/background.h" | |
| 42 #include "ui/views/border.h" | |
| 43 #include "ui/views/painter.h" | |
| 44 #include "ui/views/view.h" | |
| 45 #include "ui/views/widget/widget.h" | |
| 46 #include "ui/wm/core/shadow.h" | |
| 47 #include "ui/wm/core/shadow_types.h" | |
| 48 #include "ui/wm/core/window_animations.h" | |
| 49 | |
| 50 namespace ash { | |
| 51 namespace { | |
| 52 | |
| 53 using Windows = std::vector<WmWindow*>; | |
| 54 | |
| 55 // A comparator for locating a given target window. | |
| 56 struct WindowSelectorItemComparator { | |
| 57 explicit WindowSelectorItemComparator(const WmWindow* target_window) | |
| 58 : target(target_window) {} | |
| 59 | |
| 60 bool operator()(std::unique_ptr<WindowSelectorItem>& window) const { | |
| 61 return window->GetWindow() == target; | |
| 62 } | |
| 63 | |
| 64 const WmWindow* target; | |
| 65 }; | |
| 66 | |
| 67 // Time it takes for the selector widget to move to the next target. The same | |
| 68 // time is used for fading out shield widget when the overview mode is opened | |
| 69 // or closed. | |
| 70 const int kOverviewSelectorTransitionMilliseconds = 250; | |
| 71 | |
| 72 // The color and opacity of the screen shield in overview. | |
| 73 const SkColor kShieldColor = SkColorSetARGB(255, 0, 0, 0); | |
| 74 const float kShieldOpacity = 0.7f; | |
| 75 | |
| 76 // The color and opacity of the overview selector. | |
| 77 const SkColor kWindowSelectionColor = SkColorSetARGB(51, 255, 255, 255); | |
| 78 const SkColor kWindowSelectionBorderColor = SkColorSetARGB(76, 255, 255, 255); | |
| 79 | |
| 80 // Border thickness of overview selector. | |
| 81 const int kWindowSelectionBorderThickness = 1; | |
| 82 | |
| 83 // Corner radius of the overview selector border. | |
| 84 const int kWindowSelectionRadius = 4; | |
| 85 | |
| 86 // In the conceptual overview table, the window margin is the space reserved | |
| 87 // around the window within the cell. This margin does not overlap so the | |
| 88 // closest distance between adjacent windows will be twice this amount. | |
| 89 const int kWindowMargin = 5; | |
| 90 | |
| 91 // Windows are not allowed to get taller than this. | |
| 92 const int kMaxHeight = 512; | |
| 93 | |
| 94 // Margins reserved in the overview mode. | |
| 95 const float kOverviewInsetRatio = 0.05f; | |
| 96 | |
| 97 // Additional vertical inset reserved for windows in overview mode. | |
| 98 const float kOverviewVerticalInset = 0.1f; | |
| 99 | |
| 100 // A View having rounded corners and a specified background color which is | |
| 101 // only painted within the bounds defined by the rounded corners. | |
| 102 // TODO(varkha): This duplicates code from RoundedImageView. Refactor these | |
| 103 // classes and move into ui/views. | |
| 104 class RoundedRectView : public views::View { | |
| 105 public: | |
| 106 RoundedRectView(int corner_radius, SkColor background) | |
| 107 : corner_radius_(corner_radius), background_(background) {} | |
| 108 | |
| 109 ~RoundedRectView() override {} | |
| 110 | |
| 111 void OnPaint(gfx::Canvas* canvas) override { | |
| 112 views::View::OnPaint(canvas); | |
| 113 | |
| 114 SkScalar radius = SkIntToScalar(corner_radius_); | |
| 115 const SkScalar kRadius[8] = {radius, radius, radius, radius, | |
| 116 radius, radius, radius, radius}; | |
| 117 SkPath path; | |
| 118 gfx::Rect bounds(size()); | |
| 119 bounds.set_height(bounds.height() + radius); | |
| 120 path.addRoundRect(gfx::RectToSkRect(bounds), kRadius); | |
| 121 | |
| 122 canvas->ClipPath(path, true); | |
| 123 canvas->DrawColor(background_); | |
| 124 } | |
| 125 | |
| 126 private: | |
| 127 int corner_radius_; | |
| 128 SkColor background_; | |
| 129 | |
| 130 DISALLOW_COPY_AND_ASSIGN(RoundedRectView); | |
| 131 }; | |
| 132 | |
| 133 // BackgroundWith1PxBorder renders a solid background color, with a one pixel | |
| 134 // border with rounded corners. This accounts for the scaling of the canvas, so | |
| 135 // that the border is 1 pixel thick regardless of display scaling. | |
| 136 class BackgroundWith1PxBorder : public views::Background { | |
| 137 public: | |
| 138 BackgroundWith1PxBorder(SkColor background, | |
| 139 SkColor border_color, | |
| 140 int border_thickness, | |
| 141 int corner_radius); | |
| 142 | |
| 143 void Paint(gfx::Canvas* canvas, views::View* view) const override; | |
| 144 | |
| 145 private: | |
| 146 // Color for the one pixel border. | |
| 147 SkColor border_color_; | |
| 148 | |
| 149 // Thickness of border inset. | |
| 150 int border_thickness_; | |
| 151 | |
| 152 // Corner radius of the inside edge of the roundrect border stroke. | |
| 153 int corner_radius_; | |
| 154 | |
| 155 DISALLOW_COPY_AND_ASSIGN(BackgroundWith1PxBorder); | |
| 156 }; | |
| 157 | |
| 158 BackgroundWith1PxBorder::BackgroundWith1PxBorder(SkColor background, | |
| 159 SkColor border_color, | |
| 160 int border_thickness, | |
| 161 int corner_radius) | |
| 162 : border_color_(border_color), | |
| 163 border_thickness_(border_thickness), | |
| 164 corner_radius_(corner_radius) { | |
| 165 SetNativeControlColor(background); | |
| 166 } | |
| 167 | |
| 168 void BackgroundWith1PxBorder::Paint(gfx::Canvas* canvas, | |
| 169 views::View* view) const { | |
| 170 gfx::RectF border_rect_f(view->GetContentsBounds()); | |
| 171 | |
| 172 gfx::ScopedCanvas scoped_canvas(canvas); | |
| 173 const float scale = canvas->UndoDeviceScaleFactor(); | |
| 174 border_rect_f.Scale(scale); | |
| 175 const float inset = border_thickness_ * scale - 0.5f; | |
| 176 border_rect_f.Inset(inset, inset); | |
| 177 | |
| 178 SkPath path; | |
| 179 const SkScalar scaled_corner_radius = | |
| 180 SkFloatToScalar(corner_radius_ * scale + 0.5f); | |
| 181 path.addRoundRect(gfx::RectFToSkRect(border_rect_f), scaled_corner_radius, | |
| 182 scaled_corner_radius); | |
| 183 | |
| 184 cc::PaintFlags flags; | |
| 185 flags.setStyle(cc::PaintFlags::kStroke_Style); | |
| 186 flags.setStrokeWidth(1); | |
| 187 flags.setAntiAlias(true); | |
| 188 | |
| 189 SkPath stroke_path; | |
| 190 flags.getFillPath(path, &stroke_path); | |
| 191 | |
| 192 SkPath fill_path; | |
| 193 Op(path, stroke_path, kDifference_SkPathOp, &fill_path); | |
| 194 flags.setStyle(cc::PaintFlags::kFill_Style); | |
| 195 flags.setColor(get_color()); | |
| 196 canvas->sk_canvas()->drawPath(fill_path, flags); | |
| 197 | |
| 198 if (border_thickness_ > 0) { | |
| 199 flags.setColor(border_color_); | |
| 200 canvas->sk_canvas()->drawPath(stroke_path, flags); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 // Returns the vector for the fade in animation. | |
| 205 gfx::Vector2d GetSlideVectorForFadeIn(WindowSelector::Direction direction, | |
| 206 const gfx::Rect& bounds) { | |
| 207 gfx::Vector2d vector; | |
| 208 switch (direction) { | |
| 209 case WindowSelector::UP: | |
| 210 case WindowSelector::LEFT: | |
| 211 vector.set_x(-bounds.width()); | |
| 212 break; | |
| 213 case WindowSelector::DOWN: | |
| 214 case WindowSelector::RIGHT: | |
| 215 vector.set_x(bounds.width()); | |
| 216 break; | |
| 217 } | |
| 218 return vector; | |
| 219 } | |
| 220 | |
| 221 // Creates and returns a background translucent widget parented in | |
| 222 // |root_window|'s default container and having |background_color|. | |
| 223 // When |border_thickness| is non-zero, a border is created having | |
| 224 // |border_color|, otherwise |border_color| parameter is ignored. | |
| 225 // The new background widget starts with |initial_opacity| and then fades in. | |
| 226 views::Widget* CreateBackgroundWidget(WmWindow* root_window, | |
| 227 ui::LayerType layer_type, | |
| 228 SkColor background_color, | |
| 229 int border_thickness, | |
| 230 int border_radius, | |
| 231 SkColor border_color, | |
| 232 float initial_opacity) { | |
| 233 views::Widget* widget = new views::Widget; | |
| 234 views::Widget::InitParams params; | |
| 235 params.type = views::Widget::InitParams::TYPE_POPUP; | |
| 236 params.keep_on_top = false; | |
| 237 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
| 238 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; | |
| 239 params.layer_type = layer_type; | |
| 240 params.accept_events = false; | |
| 241 widget->set_focus_on_creation(false); | |
| 242 // Parenting in kShellWindowId_WallpaperContainer allows proper layering of | |
| 243 // the shield and selection widgets. Since that container is created with | |
| 244 // USE_LOCAL_COORDINATES BoundsInScreenBehavior local bounds in |root_window_| | |
| 245 // need to be provided. | |
| 246 root_window->GetRootWindowController()->ConfigureWidgetInitParamsForContainer( | |
| 247 widget, kShellWindowId_WallpaperContainer, ¶ms); | |
| 248 widget->Init(params); | |
| 249 WmWindow* widget_window = WmWindow::Get(widget->GetNativeWindow()); | |
| 250 // Disable the "bounce in" animation when showing the window. | |
| 251 widget_window->SetVisibilityAnimationTransition(::wm::ANIMATE_NONE); | |
| 252 // The background widget should not activate the shelf when passing under it. | |
| 253 widget_window->GetWindowState()->set_ignored_by_shelf(true); | |
| 254 if (params.layer_type == ui::LAYER_SOLID_COLOR) { | |
| 255 widget_window->GetLayer()->SetColor(background_color); | |
| 256 } else { | |
| 257 views::View* content_view = | |
| 258 new RoundedRectView(border_radius, SK_ColorTRANSPARENT); | |
| 259 content_view->set_background(new BackgroundWith1PxBorder( | |
| 260 background_color, border_color, border_thickness, border_radius)); | |
| 261 widget->SetContentsView(content_view); | |
| 262 } | |
| 263 widget_window->GetParent()->StackChildAtTop(widget_window); | |
| 264 widget->Show(); | |
| 265 widget_window->SetOpacity(initial_opacity); | |
| 266 return widget; | |
| 267 } | |
| 268 | |
| 269 bool IsMinimizedStateType(wm::WindowStateType type) { | |
| 270 return type == wm::WINDOW_STATE_TYPE_DOCKED_MINIMIZED || | |
| 271 type == wm::WINDOW_STATE_TYPE_MINIMIZED; | |
| 272 } | |
| 273 | |
| 274 } // namespace | |
| 275 | |
| 276 WindowGrid::WindowGrid(WmWindow* root_window, | |
| 277 const std::vector<WmWindow*>& windows, | |
| 278 WindowSelector* window_selector) | |
| 279 : root_window_(root_window), | |
| 280 window_selector_(window_selector), | |
| 281 window_observer_(this), | |
| 282 window_state_observer_(this), | |
| 283 selected_index_(0), | |
| 284 num_columns_(0), | |
| 285 prepared_for_overview_(false) { | |
| 286 std::vector<WmWindow*> windows_in_root; | |
| 287 for (auto* window : windows) { | |
| 288 if (window->GetRootWindow() == root_window) | |
| 289 windows_in_root.push_back(window); | |
| 290 } | |
| 291 | |
| 292 for (auto* window : windows_in_root) { | |
| 293 window_observer_.Add(window->aura_window()); | |
| 294 window_state_observer_.Add(window->GetWindowState()); | |
| 295 window_list_.push_back( | |
| 296 base::MakeUnique<WindowSelectorItem>(window, window_selector_)); | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 WindowGrid::~WindowGrid() {} | |
| 301 | |
| 302 void WindowGrid::Shutdown() { | |
| 303 for (const auto& window : window_list_) | |
| 304 window->Shutdown(); | |
| 305 | |
| 306 if (shield_widget_) { | |
| 307 // Fade out the shield widget. This animation continues past the lifetime | |
| 308 // of |this|. | |
| 309 WmWindow* widget_window = WmWindow::Get(shield_widget_->GetNativeWindow()); | |
| 310 ui::ScopedLayerAnimationSettings animation_settings( | |
| 311 widget_window->GetLayer()->GetAnimator()); | |
| 312 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( | |
| 313 kOverviewSelectorTransitionMilliseconds)); | |
| 314 animation_settings.SetTweenType(gfx::Tween::EASE_OUT); | |
| 315 animation_settings.SetPreemptionStrategy( | |
| 316 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 317 // CleanupAnimationObserver will delete itself (and the shield widget) when | |
| 318 // the opacity animation is complete. | |
| 319 // Ownership over the observer is passed to the window_selector_->delegate() | |
| 320 // which has longer lifetime so that animations can continue even after the | |
| 321 // overview mode is shut down. | |
| 322 views::Widget* shield_widget = shield_widget_.get(); | |
| 323 std::unique_ptr<CleanupAnimationObserver> observer( | |
| 324 new CleanupAnimationObserver(std::move(shield_widget_))); | |
| 325 animation_settings.AddObserver(observer.get()); | |
| 326 window_selector_->delegate()->AddDelayedAnimationObserver( | |
| 327 std::move(observer)); | |
| 328 shield_widget->SetOpacity(0.f); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 void WindowGrid::PrepareForOverview() { | |
| 333 InitShieldWidget(); | |
| 334 for (const auto& window : window_list_) | |
| 335 window->PrepareForOverview(); | |
| 336 prepared_for_overview_ = true; | |
| 337 } | |
| 338 | |
| 339 void WindowGrid::PositionWindows(bool animate) { | |
| 340 if (window_selector_->is_shut_down() || window_list_.empty()) | |
| 341 return; | |
| 342 DCHECK(shield_widget_.get()); | |
| 343 // Keep the background shield widget covering the whole screen. | |
| 344 WmWindow* widget_window = WmWindow::Get(shield_widget_->GetNativeWindow()); | |
| 345 const gfx::Rect bounds = widget_window->GetParent()->GetBounds(); | |
| 346 widget_window->SetBounds(bounds); | |
| 347 gfx::Rect total_bounds = | |
| 348 root_window_->ConvertRectToScreen(wm::GetDisplayWorkAreaBoundsInParent( | |
| 349 root_window_->GetChildByShellWindowId( | |
| 350 kShellWindowId_DefaultContainer))); | |
| 351 // Windows occupy vertically centered area with additional vertical insets. | |
| 352 int horizontal_inset = | |
| 353 gfx::ToFlooredInt(std::min(kOverviewInsetRatio * total_bounds.width(), | |
| 354 kOverviewInsetRatio * total_bounds.height())); | |
| 355 int vertical_inset = | |
| 356 horizontal_inset + | |
| 357 kOverviewVerticalInset * (total_bounds.height() - 2 * horizontal_inset); | |
| 358 total_bounds.Inset(std::max(0, horizontal_inset - kWindowMargin), | |
| 359 std::max(0, vertical_inset - kWindowMargin)); | |
| 360 std::vector<gfx::Rect> rects; | |
| 361 | |
| 362 // Keep track of the lowest coordinate. | |
| 363 int max_bottom = total_bounds.y(); | |
| 364 | |
| 365 // Right bound of the narrowest row. | |
| 366 int min_right = total_bounds.right(); | |
| 367 // Right bound of the widest row. | |
| 368 int max_right = total_bounds.x(); | |
| 369 | |
| 370 // Keep track of the difference between the narrowest and the widest row. | |
| 371 // Initially this is set to the worst it can ever be assuming the windows fit. | |
| 372 int width_diff = total_bounds.width(); | |
| 373 | |
| 374 // Initially allow the windows to occupy all available width. Shrink this | |
| 375 // available space horizontally to find the breakdown into rows that achieves | |
| 376 // the minimal |width_diff|. | |
| 377 int right_bound = total_bounds.right(); | |
| 378 | |
| 379 // Determine the optimal height bisecting between |low_height| and | |
| 380 // |high_height|. Once this optimal height is known, |height_fixed| is set to | |
| 381 // true and the rows are balanced by repeatedly squeezing the widest row to | |
| 382 // cause windows to overflow to the subsequent rows. | |
| 383 int low_height = 2 * kWindowMargin; | |
| 384 int high_height = | |
| 385 std::max(low_height, static_cast<int>(total_bounds.height() + 1)); | |
| 386 int height = 0.5 * (low_height + high_height); | |
| 387 bool height_fixed = false; | |
| 388 | |
| 389 // Repeatedly try to fit the windows |rects| within |right_bound|. | |
| 390 // If a maximum |height| is found such that all window |rects| fit, this | |
| 391 // fitting continues while shrinking the |right_bound| in order to balance the | |
| 392 // rows. If the windows fit the |right_bound| would have been decremented at | |
| 393 // least once so it needs to be incremented once before getting out of this | |
| 394 // loop and one additional pass made to actually fit the |rects|. | |
| 395 // If the |rects| cannot fit (e.g. there are too many windows) the bisection | |
| 396 // will still finish and we might increment the |right_bound| once pixel extra | |
| 397 // which is acceptable since there is an unused margin on the right. | |
| 398 bool make_last_adjustment = false; | |
| 399 while (true) { | |
| 400 gfx::Rect overview_bounds(total_bounds); | |
| 401 overview_bounds.set_width(right_bound - total_bounds.x()); | |
| 402 bool windows_fit = FitWindowRectsInBounds( | |
| 403 overview_bounds, std::min(kMaxHeight + 2 * kWindowMargin, height), | |
| 404 &rects, &max_bottom, &min_right, &max_right); | |
| 405 | |
| 406 if (height_fixed) { | |
| 407 if (!windows_fit) { | |
| 408 // Revert the previous change to |right_bound| and do one last pass. | |
| 409 right_bound++; | |
| 410 make_last_adjustment = true; | |
| 411 break; | |
| 412 } | |
| 413 // Break if all the windows are zero-width at the current scale. | |
| 414 if (max_right <= total_bounds.x()) | |
| 415 break; | |
| 416 } else { | |
| 417 // Find the optimal row height bisecting between |low_height| and | |
| 418 // |high_height|. | |
| 419 if (windows_fit) | |
| 420 low_height = height; | |
| 421 else | |
| 422 high_height = height; | |
| 423 height = 0.5 * (low_height + high_height); | |
| 424 // When height can no longer be improved, start balancing the rows. | |
| 425 if (height == low_height) | |
| 426 height_fixed = true; | |
| 427 } | |
| 428 | |
| 429 if (windows_fit && height_fixed) { | |
| 430 if (max_right - min_right <= width_diff) { | |
| 431 // Row alignment is getting better. Try to shrink the |right_bound| in | |
| 432 // order to squeeze the widest row. | |
| 433 right_bound = max_right - 1; | |
| 434 width_diff = max_right - min_right; | |
| 435 } else { | |
| 436 // Row alignment is getting worse. | |
| 437 // Revert the previous change to |right_bound| and do one last pass. | |
| 438 right_bound++; | |
| 439 make_last_adjustment = true; | |
| 440 break; | |
| 441 } | |
| 442 } | |
| 443 } | |
| 444 // Once the windows in |window_list_| no longer fit, the change to | |
| 445 // |right_bound| was reverted. Perform one last pass to position the |rects|. | |
| 446 if (make_last_adjustment) { | |
| 447 gfx::Rect overview_bounds(total_bounds); | |
| 448 overview_bounds.set_width(right_bound - total_bounds.x()); | |
| 449 FitWindowRectsInBounds(overview_bounds, | |
| 450 std::min(kMaxHeight + 2 * kWindowMargin, height), | |
| 451 &rects, &max_bottom, &min_right, &max_right); | |
| 452 } | |
| 453 // Position the windows centering the left-aligned rows vertically. | |
| 454 gfx::Vector2d offset(0, (total_bounds.bottom() - max_bottom) / 2); | |
| 455 for (size_t i = 0; i < window_list_.size(); ++i) { | |
| 456 window_list_[i]->SetBounds( | |
| 457 rects[i] + offset, | |
| 458 animate | |
| 459 ? OverviewAnimationType::OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS | |
| 460 : OverviewAnimationType::OVERVIEW_ANIMATION_NONE); | |
| 461 } | |
| 462 | |
| 463 // If the selection widget is active, reposition it without any animation. | |
| 464 if (selection_widget_) | |
| 465 MoveSelectionWidgetToTarget(animate); | |
| 466 } | |
| 467 | |
| 468 bool WindowGrid::Move(WindowSelector::Direction direction, bool animate) { | |
| 469 bool recreate_selection_widget = false; | |
| 470 bool out_of_bounds = false; | |
| 471 bool changed_selection_index = false; | |
| 472 gfx::Rect old_bounds; | |
| 473 if (SelectedWindow()) { | |
| 474 old_bounds = SelectedWindow()->target_bounds(); | |
| 475 // Make the old selected window header non-transparent first. | |
| 476 SelectedWindow()->SetSelected(false); | |
| 477 } | |
| 478 | |
| 479 // [up] key is equivalent to [left] key and [down] key is equivalent to | |
| 480 // [right] key. | |
| 481 if (!selection_widget_) { | |
| 482 switch (direction) { | |
| 483 case WindowSelector::UP: | |
| 484 case WindowSelector::LEFT: | |
| 485 selected_index_ = window_list_.size() - 1; | |
| 486 break; | |
| 487 case WindowSelector::DOWN: | |
| 488 case WindowSelector::RIGHT: | |
| 489 selected_index_ = 0; | |
| 490 break; | |
| 491 } | |
| 492 changed_selection_index = true; | |
| 493 } | |
| 494 while (!changed_selection_index || | |
| 495 (!out_of_bounds && window_list_[selected_index_]->dimmed())) { | |
| 496 switch (direction) { | |
| 497 case WindowSelector::UP: | |
| 498 case WindowSelector::LEFT: | |
| 499 if (selected_index_ == 0) | |
| 500 out_of_bounds = true; | |
| 501 selected_index_--; | |
| 502 break; | |
| 503 case WindowSelector::DOWN: | |
| 504 case WindowSelector::RIGHT: | |
| 505 if (selected_index_ >= window_list_.size() - 1) | |
| 506 out_of_bounds = true; | |
| 507 selected_index_++; | |
| 508 break; | |
| 509 } | |
| 510 if (!out_of_bounds && SelectedWindow()) { | |
| 511 if (SelectedWindow()->target_bounds().y() != old_bounds.y()) | |
| 512 recreate_selection_widget = true; | |
| 513 } | |
| 514 changed_selection_index = true; | |
| 515 } | |
| 516 MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds, | |
| 517 animate); | |
| 518 | |
| 519 // Make the new selected window header fully transparent. | |
| 520 if (SelectedWindow()) | |
| 521 SelectedWindow()->SetSelected(true); | |
| 522 return out_of_bounds; | |
| 523 } | |
| 524 | |
| 525 WindowSelectorItem* WindowGrid::SelectedWindow() const { | |
| 526 if (!selection_widget_) | |
| 527 return nullptr; | |
| 528 CHECK(selected_index_ < window_list_.size()); | |
| 529 return window_list_[selected_index_].get(); | |
| 530 } | |
| 531 | |
| 532 bool WindowGrid::Contains(const WmWindow* window) const { | |
| 533 for (const auto& window_item : window_list_) { | |
| 534 if (window_item->Contains(window)) | |
| 535 return true; | |
| 536 } | |
| 537 return false; | |
| 538 } | |
| 539 | |
| 540 void WindowGrid::FilterItems(const base::string16& pattern) { | |
| 541 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents finder(pattern); | |
| 542 for (const auto& window : window_list_) { | |
| 543 if (finder.Search(window->GetWindow()->GetTitle(), nullptr, nullptr)) { | |
| 544 window->SetDimmed(false); | |
| 545 } else { | |
| 546 window->SetDimmed(true); | |
| 547 if (selection_widget_ && SelectedWindow() == window.get()) { | |
| 548 SelectedWindow()->SetSelected(false); | |
| 549 selection_widget_.reset(); | |
| 550 selector_shadow_.reset(); | |
| 551 } | |
| 552 } | |
| 553 } | |
| 554 } | |
| 555 | |
| 556 void WindowGrid::WindowClosing(WindowSelectorItem* window) { | |
| 557 if (!selection_widget_ || SelectedWindow() != window) | |
| 558 return; | |
| 559 WmWindow* selection_widget_window = | |
| 560 WmWindow::Get(selection_widget_->GetNativeWindow()); | |
| 561 std::unique_ptr<ScopedOverviewAnimationSettings> animation_settings_label = | |
| 562 ScopedOverviewAnimationSettingsFactory::Get() | |
| 563 ->CreateOverviewAnimationSettings( | |
| 564 OverviewAnimationType::OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM, | |
| 565 selection_widget_window); | |
| 566 selection_widget_->SetOpacity(0.f); | |
| 567 } | |
| 568 | |
| 569 void WindowGrid::OnWindowDestroying(aura::Window* window) { | |
| 570 window_observer_.Remove(window); | |
| 571 window_state_observer_.Remove(wm::GetWindowState(window)); | |
| 572 auto iter = std::find_if(window_list_.begin(), window_list_.end(), | |
| 573 WindowSelectorItemComparator(WmWindow::Get(window))); | |
| 574 | |
| 575 DCHECK(iter != window_list_.end()); | |
| 576 | |
| 577 size_t removed_index = iter - window_list_.begin(); | |
| 578 window_list_.erase(iter); | |
| 579 | |
| 580 if (empty()) { | |
| 581 // If the grid is now empty, notify the window selector so that it erases us | |
| 582 // from its grid list. | |
| 583 window_selector_->OnGridEmpty(this); | |
| 584 return; | |
| 585 } | |
| 586 | |
| 587 // If selecting, update the selection index. | |
| 588 if (selection_widget_) { | |
| 589 bool send_focus_alert = selected_index_ == removed_index; | |
| 590 if (selected_index_ >= removed_index && selected_index_ != 0) | |
| 591 selected_index_--; | |
| 592 SelectedWindow()->SetSelected(true); | |
| 593 if (send_focus_alert) | |
| 594 SelectedWindow()->SendAccessibleSelectionEvent(); | |
| 595 } | |
| 596 | |
| 597 PositionWindows(true); | |
| 598 } | |
| 599 | |
| 600 void WindowGrid::OnWindowBoundsChanged(aura::Window* window, | |
| 601 const gfx::Rect& old_bounds, | |
| 602 const gfx::Rect& new_bounds) { | |
| 603 // During preparation, window bounds can change. Ignore bounds | |
| 604 // change notifications in this case; we'll reposition soon. | |
| 605 if (!prepared_for_overview_) | |
| 606 return; | |
| 607 | |
| 608 auto iter = std::find_if(window_list_.begin(), window_list_.end(), | |
| 609 WindowSelectorItemComparator(WmWindow::Get(window))); | |
| 610 DCHECK(iter != window_list_.end()); | |
| 611 | |
| 612 // Immediately finish any active bounds animation. | |
| 613 window->layer()->GetAnimator()->StopAnimatingProperty( | |
| 614 ui::LayerAnimationElement::BOUNDS); | |
| 615 PositionWindows(false); | |
| 616 } | |
| 617 | |
| 618 void WindowGrid::OnPostWindowStateTypeChange(wm::WindowState* window_state, | |
| 619 wm::WindowStateType old_type) { | |
| 620 // During preparation, window state can change, e.g. updating shelf | |
| 621 // visibility may show the temporarily hidden (minimized) panels. | |
| 622 if (!prepared_for_overview_) | |
| 623 return; | |
| 624 | |
| 625 wm::WindowStateType new_type = window_state->GetStateType(); | |
| 626 if (IsMinimizedStateType(old_type) == IsMinimizedStateType(new_type)) | |
| 627 return; | |
| 628 | |
| 629 auto iter = | |
| 630 std::find_if(window_list_.begin(), window_list_.end(), | |
| 631 [window_state](std::unique_ptr<WindowSelectorItem>& item) { | |
| 632 return item->Contains(window_state->window()); | |
| 633 }); | |
| 634 if (iter != window_list_.end()) { | |
| 635 (*iter)->OnMinimizedStateChanged(); | |
| 636 PositionWindows(false); | |
| 637 } | |
| 638 } | |
| 639 | |
| 640 void WindowGrid::InitShieldWidget() { | |
| 641 // TODO(varkha): The code assumes that SHELF_BACKGROUND_MAXIMIZED is | |
| 642 // synonymous with a black shelf background. Update this code if that | |
| 643 // assumption is no longer valid. | |
| 644 const float initial_opacity = | |
| 645 (WmShelf::ForWindow(root_window_)->GetBackgroundType() == | |
| 646 SHELF_BACKGROUND_MAXIMIZED) | |
| 647 ? 1.f | |
| 648 : 0.f; | |
| 649 shield_widget_.reset( | |
| 650 CreateBackgroundWidget(root_window_, ui::LAYER_SOLID_COLOR, kShieldColor, | |
| 651 0, 0, SK_ColorTRANSPARENT, initial_opacity)); | |
| 652 WmWindow* widget_window = WmWindow::Get(shield_widget_->GetNativeWindow()); | |
| 653 const gfx::Rect bounds = widget_window->GetParent()->GetBounds(); | |
| 654 widget_window->SetBounds(bounds); | |
| 655 widget_window->SetName("OverviewModeShield"); | |
| 656 | |
| 657 ui::ScopedLayerAnimationSettings animation_settings( | |
| 658 widget_window->GetLayer()->GetAnimator()); | |
| 659 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( | |
| 660 kOverviewSelectorTransitionMilliseconds)); | |
| 661 animation_settings.SetTweenType(gfx::Tween::EASE_OUT); | |
| 662 animation_settings.SetPreemptionStrategy( | |
| 663 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 664 shield_widget_->SetOpacity(kShieldOpacity); | |
| 665 } | |
| 666 | |
| 667 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) { | |
| 668 selection_widget_.reset(CreateBackgroundWidget( | |
| 669 root_window_, ui::LAYER_TEXTURED, kWindowSelectionColor, | |
| 670 kWindowSelectionBorderThickness, kWindowSelectionRadius, | |
| 671 kWindowSelectionBorderColor, 0.f)); | |
| 672 WmWindow* widget_window = WmWindow::Get(selection_widget_->GetNativeWindow()); | |
| 673 const gfx::Rect target_bounds = | |
| 674 root_window_->ConvertRectFromScreen(SelectedWindow()->target_bounds()); | |
| 675 gfx::Vector2d fade_out_direction = | |
| 676 GetSlideVectorForFadeIn(direction, target_bounds); | |
| 677 widget_window->SetBounds(target_bounds - fade_out_direction); | |
| 678 widget_window->SetName("OverviewModeSelector"); | |
| 679 | |
| 680 selector_shadow_.reset(new ::wm::Shadow()); | |
| 681 selector_shadow_->Init(::wm::ShadowElevation::LARGE); | |
| 682 selector_shadow_->layer()->SetVisible(true); | |
| 683 selection_widget_->GetLayer()->SetMasksToBounds(false); | |
| 684 selection_widget_->GetLayer()->Add(selector_shadow_->layer()); | |
| 685 selector_shadow_->SetContentBounds(gfx::Rect(target_bounds.size())); | |
| 686 } | |
| 687 | |
| 688 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction, | |
| 689 bool recreate_selection_widget, | |
| 690 bool out_of_bounds, | |
| 691 bool animate) { | |
| 692 // If the selection widget is already active, fade it out in the selection | |
| 693 // direction. | |
| 694 if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) { | |
| 695 // Animate the old selection widget and then destroy it. | |
| 696 views::Widget* old_selection = selection_widget_.get(); | |
| 697 WmWindow* old_selection_window = | |
| 698 WmWindow::Get(old_selection->GetNativeWindow()); | |
| 699 gfx::Vector2d fade_out_direction = | |
| 700 GetSlideVectorForFadeIn(direction, old_selection_window->GetBounds()); | |
| 701 | |
| 702 ui::ScopedLayerAnimationSettings animation_settings( | |
| 703 old_selection_window->GetLayer()->GetAnimator()); | |
| 704 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( | |
| 705 kOverviewSelectorTransitionMilliseconds)); | |
| 706 animation_settings.SetPreemptionStrategy( | |
| 707 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 708 animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN); | |
| 709 // CleanupAnimationObserver will delete itself (and the widget) when the | |
| 710 // motion animation is complete. | |
| 711 // Ownership over the observer is passed to the window_selector_->delegate() | |
| 712 // which has longer lifetime so that animations can continue even after the | |
| 713 // overview mode is shut down. | |
| 714 std::unique_ptr<CleanupAnimationObserver> observer( | |
| 715 new CleanupAnimationObserver(std::move(selection_widget_))); | |
| 716 animation_settings.AddObserver(observer.get()); | |
| 717 window_selector_->delegate()->AddDelayedAnimationObserver( | |
| 718 std::move(observer)); | |
| 719 old_selection->SetOpacity(0.f); | |
| 720 old_selection_window->SetBounds(old_selection_window->GetBounds() + | |
| 721 fade_out_direction); | |
| 722 old_selection->Hide(); | |
| 723 } | |
| 724 if (out_of_bounds) | |
| 725 return; | |
| 726 | |
| 727 if (!selection_widget_) | |
| 728 InitSelectionWidget(direction); | |
| 729 // Send an a11y alert so that if ChromeVox is enabled, the item label is | |
| 730 // read. | |
| 731 SelectedWindow()->SendAccessibleSelectionEvent(); | |
| 732 // The selection widget is moved to the newly selected item in the same | |
| 733 // grid. | |
| 734 MoveSelectionWidgetToTarget(animate); | |
| 735 } | |
| 736 | |
| 737 void WindowGrid::MoveSelectionWidgetToTarget(bool animate) { | |
| 738 gfx::Rect bounds = | |
| 739 root_window_->ConvertRectFromScreen(SelectedWindow()->target_bounds()); | |
| 740 if (animate) { | |
| 741 WmWindow* selection_widget_window = | |
| 742 WmWindow::Get(selection_widget_->GetNativeWindow()); | |
| 743 ui::ScopedLayerAnimationSettings animation_settings( | |
| 744 selection_widget_window->GetLayer()->GetAnimator()); | |
| 745 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds( | |
| 746 kOverviewSelectorTransitionMilliseconds)); | |
| 747 animation_settings.SetTweenType(gfx::Tween::EASE_IN_OUT); | |
| 748 animation_settings.SetPreemptionStrategy( | |
| 749 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 750 selection_widget_->SetBounds(bounds); | |
| 751 selection_widget_->SetOpacity(1.f); | |
| 752 | |
| 753 if (selector_shadow_) { | |
| 754 ui::ScopedLayerAnimationSettings animation_settings_shadow( | |
| 755 selector_shadow_->shadow_layer()->GetAnimator()); | |
| 756 animation_settings_shadow.SetTransitionDuration( | |
| 757 base::TimeDelta::FromMilliseconds( | |
| 758 kOverviewSelectorTransitionMilliseconds)); | |
| 759 animation_settings_shadow.SetTweenType(gfx::Tween::EASE_IN_OUT); | |
| 760 animation_settings_shadow.SetPreemptionStrategy( | |
| 761 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 762 bounds.Inset(1, 1); | |
| 763 selector_shadow_->SetContentBounds( | |
| 764 gfx::Rect(gfx::Point(1, 1), bounds.size())); | |
| 765 } | |
| 766 return; | |
| 767 } | |
| 768 selection_widget_->SetBounds(bounds); | |
| 769 selection_widget_->SetOpacity(1.f); | |
| 770 if (selector_shadow_) { | |
| 771 bounds.Inset(1, 1); | |
| 772 selector_shadow_->SetContentBounds( | |
| 773 gfx::Rect(gfx::Point(1, 1), bounds.size())); | |
| 774 } | |
| 775 } | |
| 776 | |
| 777 bool WindowGrid::FitWindowRectsInBounds(const gfx::Rect& bounds, | |
| 778 int height, | |
| 779 std::vector<gfx::Rect>* rects, | |
| 780 int* max_bottom, | |
| 781 int* min_right, | |
| 782 int* max_right) { | |
| 783 rects->resize(window_list_.size()); | |
| 784 bool windows_fit = true; | |
| 785 | |
| 786 // Start in the top-left corner of |bounds|. | |
| 787 int left = bounds.x(); | |
| 788 int top = bounds.y(); | |
| 789 | |
| 790 // Keep track of the lowest coordinate. | |
| 791 *max_bottom = bounds.y(); | |
| 792 | |
| 793 // Right bound of the narrowest row. | |
| 794 *min_right = bounds.right(); | |
| 795 // Right bound of the widest row. | |
| 796 *max_right = bounds.x(); | |
| 797 | |
| 798 // All elements are of same height and only the height is necessary to | |
| 799 // determine each item's scale. | |
| 800 const gfx::Size item_size(0, height); | |
| 801 size_t i = 0; | |
| 802 for (const auto& window : window_list_) { | |
| 803 const gfx::Rect target_bounds = window->GetTargetBoundsInScreen(); | |
| 804 const int width = | |
| 805 std::max(1, gfx::ToFlooredInt(target_bounds.width() * | |
| 806 window->GetItemScale(item_size)) + | |
| 807 2 * kWindowMargin); | |
| 808 if (left + width > bounds.right()) { | |
| 809 // Move to the next row if possible. | |
| 810 if (*min_right > left) | |
| 811 *min_right = left; | |
| 812 if (*max_right < left) | |
| 813 *max_right = left; | |
| 814 top += height; | |
| 815 | |
| 816 // Check if the new row reaches the bottom or if the first item in the new | |
| 817 // row does not fit within the available width. | |
| 818 if (top + height > bounds.bottom() || | |
| 819 bounds.x() + width > bounds.right()) { | |
| 820 windows_fit = false; | |
| 821 break; | |
| 822 } | |
| 823 left = bounds.x(); | |
| 824 } | |
| 825 | |
| 826 // Position the current rect. | |
| 827 (*rects)[i].SetRect(left, top, width, height); | |
| 828 | |
| 829 // Increment horizontal position using sanitized positive |width()|. | |
| 830 left += (*rects)[i].width(); | |
| 831 | |
| 832 if (++i == window_list_.size()) { | |
| 833 // Update the narrowest and widest row width for the last row. | |
| 834 if (*min_right > left) | |
| 835 *min_right = left; | |
| 836 if (*max_right < left) | |
| 837 *max_right = left; | |
| 838 } | |
| 839 *max_bottom = top + height; | |
| 840 } | |
| 841 return windows_fit; | |
| 842 } | |
| 843 | |
| 844 } // namespace ash | |
| OLD | NEW |