Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(855)

Side by Side Diff: ash/wm/overview/window_grid.cc

Issue 2087153003: Moves common code in ash/wm/overview to ash/common/wm/overview (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ash/wm/overview/window_grid.h ('k') | ash/wm/overview/window_selector.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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/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/material_design/material_design_controller.h"
15 #include "ash/common/shell_window_ids.h"
16 #include "ash/common/wm/window_state.h"
17 #include "ash/common/wm/wm_screen_util.h"
18 #include "ash/common/wm_lookup.h"
19 #include "ash/common/wm_root_window_controller.h"
20 #include "ash/common/wm_window.h"
21 #include "ash/wm/overview/scoped_transform_overview_window.h"
22 #include "ash/wm/overview/window_selector.h"
23 #include "ash/wm/overview/window_selector_item.h"
24 #include "base/command_line.h"
25 #include "base/i18n/string_search.h"
26 #include "base/memory/scoped_vector.h"
27 #include "third_party/skia/include/core/SkColor.h"
28 #include "third_party/skia/include/pathops/SkPathOps.h"
29 #include "ui/compositor/layer_animation_observer.h"
30 #include "ui/compositor/scoped_layer_animation_settings.h"
31 #include "ui/gfx/animation/tween.h"
32 #include "ui/gfx/canvas.h"
33 #include "ui/gfx/geometry/safe_integer_conversions.h"
34 #include "ui/gfx/geometry/vector2d.h"
35 #include "ui/gfx/scoped_canvas.h"
36 #include "ui/views/background.h"
37 #include "ui/views/border.h"
38 #include "ui/views/painter.h"
39 #include "ui/views/view.h"
40 #include "ui/views/widget/widget.h"
41 #include "ui/wm/core/window_animations.h"
42
43 namespace ash {
44 namespace {
45
46 using Windows = std::vector<WmWindow*>;
47
48 // An observer which holds onto the passed widget until the animation is
49 // complete.
50 class CleanupWidgetAfterAnimationObserver
51 : public ui::ImplicitAnimationObserver {
52 public:
53 explicit CleanupWidgetAfterAnimationObserver(
54 std::unique_ptr<views::Widget> widget);
55 ~CleanupWidgetAfterAnimationObserver() override;
56
57 // ui::ImplicitAnimationObserver:
58 void OnImplicitAnimationsCompleted() override;
59
60 private:
61 std::unique_ptr<views::Widget> widget_;
62
63 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
64 };
65
66 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
67 std::unique_ptr<views::Widget> widget)
68 : widget_(std::move(widget)) {}
69
70 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
71 }
72
73 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
74 delete this;
75 }
76
77 // A comparator for locating a given target window.
78 struct WindowSelectorItemComparator {
79 explicit WindowSelectorItemComparator(const WmWindow* target_window)
80 : target(target_window) {}
81
82 bool operator()(WindowSelectorItem* window) const {
83 return window->GetWindow() == target;
84 }
85
86 const WmWindow* target;
87 };
88
89 // Conceptually the window overview is a table or grid of cells having this
90 // fixed aspect ratio. The number of columns is determined by maximizing the
91 // area of them based on the number of window_list.
92 const float kCardAspectRatio = 4.0f / 3.0f;
93
94 // The minimum number of cards along the major axis (i.e. horizontally on a
95 // landscape orientation).
96 const int kMinCardsMajor = 3;
97
98 const int kOverviewSelectorTransitionMilliseconds = 200;
99
100 // The color and opacity of the screen shield in overview.
101 const SkColor kShieldColor = SkColorSetARGB(178, 0, 0, 0);
102
103 // The color and opacity of the overview selector.
104 const SkColor kWindowSelectionColor = SkColorSetARGB(128, 0, 0, 0);
105 const SkColor kWindowSelectionColorMD = SkColorSetARGB(51, 255, 255, 255);
106 const SkColor kWindowSelectionBorderColor = SkColorSetARGB(38, 255, 255, 255);
107 const SkColor kWindowSelectionBorderColorMD = SkColorSetARGB(76, 255, 255, 255);
108
109 // Border thickness of overview selector.
110 const int kWindowSelectionBorderThickness = 2;
111 const int kWindowSelectionBorderThicknessMD = 1;
112
113 // Corner radius of the overview selector border.
114 const int kWindowSelectionRadius = 0;
115 const int kWindowSelectionRadiusMD = 3;
116
117 // The minimum amount of spacing between the bottom of the text filtering
118 // text field and the top of the selection widget on the first row of items.
119 const int kTextFilterBottomMargin = 5;
120
121 // In the conceptual overview table, the window margin is the space reserved
122 // around the window within the cell. This margin does not overlap so the
123 // closest distance between adjacent windows will be twice this amount.
124 const int kWindowMarginMD = 5;
125
126 // Additional inset of overview selector (4 is the visible selector thickness).
127 const int kSelectionInset = kWindowMarginMD - 5;
128
129 // Windows are not allowed to get taller than this.
130 const int kMaxHeight = 512;
131
132 // Margins reserved in the overview mode.
133 const float kOverviewInsetRatio = 0.05f;
134
135 // Additional vertical inset reserved for windows in overview mode.
136 const float kOverviewVerticalInset = 0.1f;
137
138 // A View having rounded corners and a specified background color which is
139 // only painted within the bounds defined by the rounded corners.
140 // TODO(varkha): This duplicates code from RoundedImageView. Refactor these
141 // classes and move into ui/views.
142 class RoundedRectView : public views::View {
143 public:
144 RoundedRectView(int corner_radius, SkColor background)
145 : corner_radius_(corner_radius), background_(background) {}
146
147 ~RoundedRectView() override {}
148
149 void OnPaint(gfx::Canvas* canvas) override {
150 views::View::OnPaint(canvas);
151
152 SkScalar radius = SkIntToScalar(corner_radius_);
153 const SkScalar kRadius[8] = {radius, radius, radius, radius,
154 radius, radius, radius, radius};
155 SkPath path;
156 gfx::Rect bounds(size());
157 bounds.set_height(bounds.height() + radius);
158 path.addRoundRect(gfx::RectToSkRect(bounds), kRadius);
159
160 SkPaint paint;
161 paint.setAntiAlias(true);
162 canvas->ClipPath(path, true);
163 canvas->DrawColor(background_);
164 }
165
166 private:
167 int corner_radius_;
168 SkColor background_;
169
170 DISALLOW_COPY_AND_ASSIGN(RoundedRectView);
171 };
172
173 // BackgroundWith1PxBorder renders a solid background color, with a one pixel
174 // border with rounded corners. This accounts for the scaling of the canvas, so
175 // that the border is 1 pixel thick regardless of display scaling.
176 class BackgroundWith1PxBorder : public views::Background {
177 public:
178 BackgroundWith1PxBorder(SkColor background,
179 SkColor border_color,
180 int border_thickness,
181 int corner_radius);
182
183 void Paint(gfx::Canvas* canvas, views::View* view) const override;
184
185 private:
186 // Color for the one pixel border.
187 SkColor border_color_;
188
189 // Thickness of border inset.
190 int border_thickness_;
191
192 // Corner radius of the inside edge of the roundrect border stroke.
193 int corner_radius_;
194
195 DISALLOW_COPY_AND_ASSIGN(BackgroundWith1PxBorder);
196 };
197
198 BackgroundWith1PxBorder::BackgroundWith1PxBorder(SkColor background,
199 SkColor border_color,
200 int border_thickness,
201 int corner_radius)
202 : border_color_(border_color),
203 border_thickness_(border_thickness),
204 corner_radius_(corner_radius) {
205 SetNativeControlColor(background);
206 }
207
208 void BackgroundWith1PxBorder::Paint(gfx::Canvas* canvas,
209 views::View* view) const {
210 gfx::RectF border_rect_f(view->GetContentsBounds());
211
212 gfx::ScopedCanvas scoped_canvas(canvas);
213 const float scale = canvas->UndoDeviceScaleFactor();
214 border_rect_f.Scale(scale);
215 if (border_thickness_ > 0) {
216 const float inset = border_thickness_ * scale - 0.5f;
217 border_rect_f.Inset(inset, inset);
218 }
219
220 SkPath path;
221 const SkScalar scaled_corner_radius =
222 SkFloatToScalar(corner_radius_ * scale + 0.5f);
223 path.addRoundRect(gfx::RectFToSkRect(border_rect_f), scaled_corner_radius,
224 scaled_corner_radius);
225
226 SkPaint paint;
227 paint.setStyle(SkPaint::kStroke_Style);
228 paint.setStrokeWidth(1);
229 paint.setAntiAlias(true);
230
231 SkPath stroke_path;
232 paint.getFillPath(path, &stroke_path);
233
234 SkPath fill_path;
235 Op(path, stroke_path, kDifference_SkPathOp, &fill_path);
236 paint.setStyle(SkPaint::kFill_Style);
237 paint.setColor(get_color());
238 canvas->sk_canvas()->drawPath(fill_path, paint);
239
240 if (border_thickness_ > 0) {
241 paint.setColor(border_color_);
242 canvas->sk_canvas()->drawPath(stroke_path, paint);
243 }
244 }
245
246 // Returns the vector for the fade in animation.
247 gfx::Vector2d GetSlideVectorForFadeIn(WindowSelector::Direction direction,
248 const gfx::Rect& bounds) {
249 gfx::Vector2d vector;
250 const bool material = ash::MaterialDesignController::IsOverviewMaterial();
251 switch (direction) {
252 case WindowSelector::UP:
253 if (!material) {
254 vector.set_y(-bounds.height());
255 break;
256 }
257 case WindowSelector::LEFT:
258 vector.set_x(-bounds.width());
259 break;
260 case WindowSelector::DOWN:
261 if (!material) {
262 vector.set_y(bounds.height());
263 break;
264 }
265 case WindowSelector::RIGHT:
266 vector.set_x(bounds.width());
267 break;
268 }
269 return vector;
270 }
271
272 // Given |root_window|, calculates the item size necessary to fit |items|
273 // items in the window selection. |bounding_rect| is set to the centered
274 // rectangle containing the grid and |item_size| is set to the size of each
275 // individual item.
276 void CalculateOverviewSizes(WmWindow* root_window,
277 size_t items,
278 int text_filter_bottom,
279 gfx::Rect* bounding_rect,
280 gfx::Size* item_size) {
281 gfx::Rect total_bounds = root_window->ConvertRectToScreen(
282 wm::GetDisplayWorkAreaBoundsInParent(root_window->GetChildByShellWindowId(
283 kShellWindowId_DefaultContainer)));
284
285 // Reserve space at the top for the text filtering textbox to appear.
286 total_bounds.Inset(0, text_filter_bottom + kTextFilterBottomMargin, 0, 0);
287
288 // Find the minimum number of windows per row that will fit all of the
289 // windows on screen.
290 int num_columns = std::max(
291 total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
292 static_cast<int>(ceil(sqrt(total_bounds.width() * items /
293 (kCardAspectRatio * total_bounds.height())))));
294 int num_rows = ((items + num_columns - 1) / num_columns);
295 item_size->set_width(std::min(
296 static_cast<int>(total_bounds.width() / num_columns),
297 static_cast<int>(total_bounds.height() * kCardAspectRatio / num_rows)));
298 item_size->set_height(
299 static_cast<int>(item_size->width() / kCardAspectRatio));
300 item_size->SetToMax(gfx::Size(1, 1));
301
302 bounding_rect->set_width(std::min(static_cast<int>(items), num_columns) *
303 item_size->width());
304 bounding_rect->set_height(num_rows * item_size->height());
305 // Calculate the X and Y offsets necessary to center the grid.
306 bounding_rect->set_x(total_bounds.x() +
307 (total_bounds.width() - bounding_rect->width()) / 2);
308 bounding_rect->set_y(total_bounds.y() +
309 (total_bounds.height() - bounding_rect->height()) / 2);
310 }
311
312 // Reorders the list of windows |items| in |root_window| in an attempt to
313 // minimize the distance each window will travel to enter overview. For
314 // equidistant windows preserves a stable order between overview sessions
315 // by comparing window pointers.
316 void ReorderItemsGreedyLeastMovement(std::vector<WmWindow*>* items,
317 WmWindow* root_window,
318 int text_filter_bottom) {
319 if (items->empty())
320 return;
321 gfx::Rect bounding_rect;
322 gfx::Size item_size;
323 CalculateOverviewSizes(root_window, items->size(), text_filter_bottom,
324 &bounding_rect, &item_size);
325 int num_columns = std::min(static_cast<int>(items->size()),
326 bounding_rect.width() / item_size.width());
327 for (size_t i = 0; i < items->size(); ++i) {
328 int column = i % num_columns;
329 int row = i / num_columns;
330 gfx::Point overview_item_center(
331 bounding_rect.x() + column * item_size.width() + item_size.width() / 2,
332 bounding_rect.y() + row * item_size.height() + item_size.height() / 2);
333 // Find the nearest window for this position.
334 size_t swap_index = i;
335 int64_t shortest_distance = std::numeric_limits<int64_t>::max();
336 for (size_t j = i; j < items->size(); ++j) {
337 WmWindow* window = (*items)[j];
338 const gfx::Rect screen_target_bounds =
339 window->ConvertRectToScreen(window->GetTargetBounds());
340 int64_t distance =
341 (screen_target_bounds.CenterPoint() - overview_item_center)
342 .LengthSquared();
343 // We compare raw pointers to create a stable ordering given two windows
344 // with the same center point.
345 if (distance < shortest_distance ||
346 (distance == shortest_distance && window < (*items)[swap_index])) {
347 shortest_distance = distance;
348 swap_index = j;
349 }
350 }
351 if (swap_index > i)
352 std::swap((*items)[i], (*items)[swap_index]);
353 }
354 }
355
356 // Creates and returns a background translucent widget parented in
357 // |root_window|'s default container and having |background_color|.
358 // When |border_thickness| is non-zero, a border is created having
359 // |border_color|, otherwise |border_color| parameter is ignored.
360 views::Widget* CreateBackgroundWidget(WmWindow* root_window,
361 SkColor background_color,
362 int border_thickness,
363 int border_radius,
364 SkColor border_color) {
365 views::Widget* widget = new views::Widget;
366 views::Widget::InitParams params;
367 params.type = views::Widget::InitParams::TYPE_POPUP;
368 params.keep_on_top = false;
369 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
370 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
371 params.accept_events = false;
372 widget->set_focus_on_creation(false);
373 // Parenting in kShellWindowId_DesktopBackgroundContainer allows proper
374 // layering of the shield and selection widgets. Since that container is
375 // created with USE_LOCAL_COORDINATES BoundsInScreenBehavior local bounds in
376 // |root_window_| need to be provided.
377 root_window->GetRootWindowController()->ConfigureWidgetInitParamsForContainer(
378 widget, kShellWindowId_DesktopBackgroundContainer, &params);
379 widget->Init(params);
380 WmWindow* widget_window = WmLookup::Get()->GetWindowForWidget(widget);
381 // Disable the "bounce in" animation when showing the window.
382 widget_window->SetVisibilityAnimationTransition(::wm::ANIMATE_NONE);
383 // The background widget should not activate the shelf when passing under it.
384 widget_window->GetWindowState()->set_ignored_by_shelf(true);
385
386 views::View* content_view =
387 new RoundedRectView(border_radius, SK_ColorTRANSPARENT);
388 if (ash::MaterialDesignController::IsOverviewMaterial()) {
389 content_view->set_background(new BackgroundWith1PxBorder(
390 background_color, border_color, border_thickness, border_radius));
391 } else {
392 content_view->set_background(
393 views::Background::CreateSolidBackground(background_color));
394 if (border_thickness) {
395 content_view->SetBorder(
396 views::Border::CreateSolidBorder(border_thickness, border_color));
397 }
398 }
399 widget->SetContentsView(content_view);
400 widget_window->GetParent()->StackChildAtTop(widget_window);
401 widget->Show();
402 // New background widget starts with 0 opacity and then fades in.
403 widget_window->SetOpacity(0.f);
404 return widget;
405 }
406
407 } // namespace
408
409 WindowGrid::WindowGrid(WmWindow* root_window,
410 const std::vector<WmWindow*>& windows,
411 WindowSelector* window_selector)
412 : root_window_(root_window),
413 window_selector_(window_selector),
414 selected_index_(0),
415 num_columns_(0) {
416 std::vector<WmWindow*> windows_in_root;
417 for (auto window : windows) {
418 if (window->GetRootWindow() == root_window)
419 windows_in_root.push_back(window);
420 }
421
422 if (!ash::MaterialDesignController::IsOverviewMaterial() &&
423 base::CommandLine::ForCurrentProcess()->HasSwitch(
424 switches::kAshEnableStableOverviewOrder)) {
425 // Reorder windows to try to minimize movement to target overview positions.
426 // This also creates a stable window ordering.
427 ReorderItemsGreedyLeastMovement(&windows_in_root, root_window_,
428 window_selector_->text_filter_bottom());
429 }
430 for (auto window : windows_in_root) {
431 window->AddObserver(this);
432 observed_windows_.insert(window);
433 window_list_.push_back(new WindowSelectorItem(window, window_selector_));
434 }
435 }
436
437 WindowGrid::~WindowGrid() {
438 for (WmWindow* window : observed_windows_)
439 window->RemoveObserver(this);
440 }
441
442 void WindowGrid::PrepareForOverview() {
443 if (ash::MaterialDesignController::IsOverviewMaterial())
444 InitShieldWidget();
445 for (auto iter = window_list_.begin(); iter != window_list_.end(); ++iter)
446 (*iter)->PrepareForOverview();
447 }
448
449 void WindowGrid::PositionWindowsMD(bool animate) {
450 if (window_list_.empty())
451 return;
452 gfx::Rect total_bounds =
453 root_window_->ConvertRectToScreen(wm::GetDisplayWorkAreaBoundsInParent(
454 root_window_->GetChildByShellWindowId(
455 kShellWindowId_DefaultContainer)));
456 // Windows occupy vertically centered area with additional vertical insets.
457 int horizontal_inset =
458 gfx::ToFlooredInt(std::min(kOverviewInsetRatio * total_bounds.width(),
459 kOverviewInsetRatio * total_bounds.height()));
460 int vertical_inset =
461 horizontal_inset +
462 kOverviewVerticalInset * (total_bounds.height() - 2 * horizontal_inset);
463 total_bounds.Inset(std::max(0, horizontal_inset - kWindowMarginMD),
464 std::max(0, vertical_inset - kWindowMarginMD));
465 std::vector<gfx::Rect> rects;
466
467 // Keep track of the lowest coordinate.
468 int max_bottom = total_bounds.y();
469
470 // Right bound of the narrowest row.
471 int min_right = total_bounds.right();
472 // Right bound of the widest row.
473 int max_right = total_bounds.x();
474
475 // Keep track of the difference between the narrowest and the widest row.
476 // Initially this is set to the worst it can ever be assuming the windows fit.
477 int width_diff = total_bounds.width();
478
479 // Initially allow the windows to occupy all available width. Shrink this
480 // available space horizontally to find the breakdown into rows that achieves
481 // the minimal |width_diff|.
482 int right_bound = total_bounds.right();
483
484 // Determine the optimal height bisecting between |low_height| and
485 // |high_height|. Once this optimal height is known, |height_fixed| is set to
486 // true and the rows are balanced by repeatedly squeezing the widest row to
487 // cause windows to overflow to the subsequent rows.
488 int low_height = 2 * kWindowMarginMD;
489 int high_height =
490 std::max(low_height, static_cast<int>(total_bounds.height() + 1));
491 int height = 0.5 * (low_height + high_height);
492 bool height_fixed = false;
493
494 // Repeatedly try to fit the windows |rects| within |right_bound|.
495 // If a maximum |height| is found such that all window |rects| fit, this
496 // fitting continues while shrinking the |right_bound| in order to balance the
497 // rows. If the windows fit the |right_bound| would have been decremented at
498 // least once so it needs to be incremented once before getting out of this
499 // loop and one additional pass made to actually fit the |rects|.
500 // If the |rects| cannot fit (e.g. there are too many windows) the bisection
501 // will still finish and we might increment the |right_bound| once pixel extra
502 // which is acceptable since there is an unused margin on the right.
503 bool make_last_adjustment = false;
504 while (true) {
505 gfx::Rect overview_bounds(total_bounds);
506 overview_bounds.set_width(right_bound - total_bounds.x());
507 bool windows_fit = FitWindowRectsInBounds(
508 overview_bounds, std::min(kMaxHeight + 2 * kWindowMarginMD, height),
509 &rects, &max_bottom, &min_right, &max_right);
510
511 if (height_fixed) {
512 if (!windows_fit) {
513 // Revert the previous change to |right_bound| and do one last pass.
514 right_bound++;
515 make_last_adjustment = true;
516 break;
517 }
518 // Break if all the windows are zero-width at the current scale.
519 if (max_right <= total_bounds.x())
520 break;
521 } else {
522 // Find the optimal row height bisecting between |low_height| and
523 // |high_height|.
524 if (windows_fit)
525 low_height = height;
526 else
527 high_height = height;
528 height = 0.5 * (low_height + high_height);
529 // When height can no longer be improved, start balancing the rows.
530 if (height == low_height)
531 height_fixed = true;
532 }
533
534 if (windows_fit && height_fixed) {
535 if (max_right - min_right <= width_diff) {
536 // Row alignment is getting better. Try to shrink the |right_bound| in
537 // order to squeeze the widest row.
538 right_bound = max_right - 1;
539 width_diff = max_right - min_right;
540 } else {
541 // Row alignment is getting worse.
542 // Revert the previous change to |right_bound| and do one last pass.
543 right_bound++;
544 make_last_adjustment = true;
545 break;
546 }
547 }
548 }
549 // Once the windows in |window_list_| no longer fit, the change to
550 // |right_bound| was reverted. Perform one last pass to position the |rects|.
551 if (make_last_adjustment) {
552 gfx::Rect overview_bounds(total_bounds);
553 overview_bounds.set_width(right_bound - total_bounds.x());
554 FitWindowRectsInBounds(overview_bounds,
555 std::min(kMaxHeight + 2 * kWindowMarginMD, height),
556 &rects, &max_bottom, &min_right, &max_right);
557 }
558 // Position the windows centering the left-aligned rows vertically.
559 gfx::Vector2d offset(0, (total_bounds.bottom() - max_bottom) / 2);
560 for (size_t i = 0; i < window_list_.size(); ++i) {
561 window_list_[i]->SetBounds(
562 rects[i] + offset,
563 animate
564 ? OverviewAnimationType::OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS
565 : OverviewAnimationType::OVERVIEW_ANIMATION_NONE);
566 }
567
568 // If the selection widget is active, reposition it without any animation.
569 if (selection_widget_)
570 MoveSelectionWidgetToTarget(animate);
571 }
572
573 void WindowGrid::PositionWindows(bool animate) {
574 if (ash::MaterialDesignController::IsOverviewMaterial()) {
575 DCHECK(shield_widget_.get());
576 // Keep the background shield widget covering the whole screen.
577 WmWindow* widget_window =
578 WmLookup::Get()->GetWindowForWidget(shield_widget_.get());
579 const gfx::Rect bounds = widget_window->GetParent()->GetBounds();
580 widget_window->SetBounds(bounds);
581 PositionWindowsMD(animate);
582 return;
583 }
584 CHECK(!window_list_.empty());
585 gfx::Rect bounding_rect;
586 gfx::Size item_size;
587 CalculateOverviewSizes(root_window_, window_list_.size(),
588 window_selector_->text_filter_bottom(), &bounding_rect,
589 &item_size);
590 num_columns_ = std::min(static_cast<int>(window_list_.size()),
591 bounding_rect.width() / item_size.width());
592 for (size_t i = 0; i < window_list_.size(); ++i) {
593 gfx::Transform transform;
594 int column = i % num_columns_;
595 int row = i / num_columns_;
596 gfx::Rect target_bounds(item_size.width() * column + bounding_rect.x(),
597 item_size.height() * row + bounding_rect.y(),
598 item_size.width(), item_size.height());
599 window_list_[i]->SetBounds(target_bounds, animate ?
600 OverviewAnimationType::OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS :
601 OverviewAnimationType::OVERVIEW_ANIMATION_NONE);
602 }
603
604 // If the selection widget is active, reposition it without any animation.
605 if (selection_widget_)
606 MoveSelectionWidgetToTarget(animate);
607 }
608
609 bool WindowGrid::Move(WindowSelector::Direction direction, bool animate) {
610 bool recreate_selection_widget = false;
611 bool out_of_bounds = false;
612 bool changed_selection_index = false;
613 const bool material = ash::MaterialDesignController::IsOverviewMaterial();
614 gfx::Rect old_bounds;
615 if (SelectedWindow()) {
616 old_bounds = SelectedWindow()->target_bounds();
617 // Make the old selected window header non-transparent first.
618 SelectedWindow()->SetSelected(false);
619 }
620
621 // With Material Design enabled [up] key is equivalent to [left] key and
622 // [down] key is equivalent to [right] key.
623 if (!selection_widget_) {
624 switch (direction) {
625 case WindowSelector::UP:
626 if (!material) {
627 selected_index_ =
628 (window_list_.size() / num_columns_) * num_columns_ - 1;
629 break;
630 }
631 case WindowSelector::LEFT:
632 selected_index_ = window_list_.size() - 1;
633 break;
634 case WindowSelector::DOWN:
635 case WindowSelector::RIGHT:
636 selected_index_ = 0;
637 break;
638 }
639 changed_selection_index = true;
640 }
641 while (!changed_selection_index ||
642 (!out_of_bounds && window_list_[selected_index_]->dimmed())) {
643 switch (direction) {
644 case WindowSelector::UP:
645 if (!material) {
646 if (selected_index_ == 0)
647 out_of_bounds = true;
648 if (selected_index_ < num_columns_) {
649 selected_index_ +=
650 num_columns_ *
651 ((window_list_.size() - selected_index_) / num_columns_) -
652 1;
653 recreate_selection_widget = true;
654 } else {
655 selected_index_ -= num_columns_;
656 }
657 break;
658 }
659 case WindowSelector::LEFT:
660 if (selected_index_ == 0)
661 out_of_bounds = true;
662 selected_index_--;
663 if (!material && (selected_index_ + 1) % num_columns_ == 0)
664 recreate_selection_widget = true;
665 break;
666 case WindowSelector::DOWN:
667 if (!material) {
668 selected_index_ += num_columns_;
669 if (selected_index_ >= window_list_.size()) {
670 selected_index_ = (selected_index_ + 1) % num_columns_;
671 if (selected_index_ == 0)
672 out_of_bounds = true;
673 recreate_selection_widget = true;
674 }
675 break;
676 }
677 case WindowSelector::RIGHT:
678 if (selected_index_ >= window_list_.size() - 1)
679 out_of_bounds = true;
680 selected_index_++;
681 if (!material && selected_index_ % num_columns_ == 0)
682 recreate_selection_widget = true;
683 break;
684 }
685 if (material) {
686 if (!out_of_bounds && SelectedWindow()) {
687 if (SelectedWindow()->target_bounds().y() != old_bounds.y())
688 recreate_selection_widget = true;
689 }
690 }
691 changed_selection_index = true;
692 }
693
694 MoveSelectionWidget(direction, recreate_selection_widget,
695 out_of_bounds, animate);
696
697 // Make the new selected window header fully transparent.
698 if (SelectedWindow())
699 SelectedWindow()->SetSelected(true);
700 return out_of_bounds;
701 }
702
703 WindowSelectorItem* WindowGrid::SelectedWindow() const {
704 if (!selection_widget_)
705 return nullptr;
706 CHECK(selected_index_ < window_list_.size());
707 return window_list_[selected_index_];
708 }
709
710 bool WindowGrid::Contains(const WmWindow* window) const {
711 for (const WindowSelectorItem* window_item : window_list_) {
712 if (window_item->Contains(window))
713 return true;
714 }
715 return false;
716 }
717
718 void WindowGrid::FilterItems(const base::string16& pattern) {
719 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents finder(pattern);
720 for (auto iter = window_list_.begin(); iter != window_list_.end(); iter++) {
721 if (finder.Search((*iter)->GetWindow()->GetTitle(), nullptr, nullptr)) {
722 (*iter)->SetDimmed(false);
723 } else {
724 (*iter)->SetDimmed(true);
725 if (selection_widget_ && SelectedWindow() == *iter) {
726 SelectedWindow()->SetSelected(false);
727 selection_widget_.reset();
728 }
729 }
730 }
731 }
732
733 void WindowGrid::OnWindowDestroying(WmWindow* window) {
734 window->RemoveObserver(this);
735 observed_windows_.erase(window);
736 ScopedVector<WindowSelectorItem>::iterator iter =
737 std::find_if(window_list_.begin(), window_list_.end(),
738 WindowSelectorItemComparator(window));
739
740 DCHECK(iter != window_list_.end());
741
742 size_t removed_index = iter - window_list_.begin();
743 window_list_.erase(iter);
744
745 if (empty()) {
746 // If the grid is now empty, notify the window selector so that it erases us
747 // from its grid list.
748 window_selector_->OnGridEmpty(this);
749 return;
750 }
751
752 // If selecting, update the selection index.
753 if (selection_widget_) {
754 bool send_focus_alert = selected_index_ == removed_index;
755 if (selected_index_ >= removed_index && selected_index_ != 0)
756 selected_index_--;
757 if (send_focus_alert)
758 SelectedWindow()->SendAccessibleSelectionEvent();
759 }
760
761 PositionWindows(true);
762 }
763
764 void WindowGrid::OnWindowBoundsChanged(WmWindow* window,
765 const gfx::Rect& old_bounds,
766 const gfx::Rect& new_bounds) {
767 auto iter = std::find_if(window_list_.begin(), window_list_.end(),
768 WindowSelectorItemComparator(window));
769 DCHECK(iter != window_list_.end());
770
771 // Immediately finish any active bounds animation.
772 window->StopAnimatingProperty(ui::LayerAnimationElement::BOUNDS);
773
774 // Recompute the transform for the window.
775 (*iter)->RecomputeWindowTransforms();
776 }
777
778 void WindowGrid::InitShieldWidget() {
779 shield_widget_.reset(CreateBackgroundWidget(root_window_, kShieldColor, 0, 0,
780 SK_ColorTRANSPARENT));
781
782 WmWindow* widget_window =
783 WmLookup::Get()->GetWindowForWidget(shield_widget_.get());
784 const gfx::Rect bounds = widget_window->GetParent()->GetBounds();
785 widget_window->SetBounds(bounds);
786
787 ui::ScopedLayerAnimationSettings animation_settings(
788 widget_window->GetLayer()->GetAnimator());
789 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
790 kOverviewSelectorTransitionMilliseconds));
791 animation_settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
792 animation_settings.SetPreemptionStrategy(
793 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
794 shield_widget_->SetOpacity(1.f);
795 }
796
797 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) {
798 const bool material = ash::MaterialDesignController::IsOverviewMaterial();
799 const int border_thickness = material ? kWindowSelectionBorderThicknessMD
800 : kWindowSelectionBorderThickness;
801 const int border_color =
802 material ? kWindowSelectionBorderColorMD : kWindowSelectionBorderColor;
803 const int selection_color =
804 material ? kWindowSelectionColorMD : kWindowSelectionColor;
805 const int border_radius =
806 material ? kWindowSelectionRadiusMD : kWindowSelectionRadius;
807 selection_widget_.reset(CreateBackgroundWidget(root_window_, selection_color,
808 border_thickness,
809 border_radius, border_color));
810
811 WmWindow* widget_window =
812 WmLookup::Get()->GetWindowForWidget(selection_widget_.get());
813 const gfx::Rect target_bounds =
814 root_window_->ConvertRectFromScreen(SelectedWindow()->target_bounds());
815 gfx::Vector2d fade_out_direction =
816 GetSlideVectorForFadeIn(direction, target_bounds);
817 widget_window->SetBounds(target_bounds - fade_out_direction);
818 }
819
820 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction,
821 bool recreate_selection_widget,
822 bool out_of_bounds,
823 bool animate) {
824 // If the selection widget is already active, fade it out in the selection
825 // direction.
826 if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) {
827 // Animate the old selection widget and then destroy it.
828 views::Widget* old_selection = selection_widget_.get();
829 WmWindow* old_selection_window =
830 WmLookup::Get()->GetWindowForWidget(old_selection);
831 gfx::Vector2d fade_out_direction =
832 GetSlideVectorForFadeIn(direction, old_selection_window->GetBounds());
833
834 ui::ScopedLayerAnimationSettings animation_settings(
835 old_selection_window->GetLayer()->GetAnimator());
836 animation_settings.SetTransitionDuration(
837 base::TimeDelta::FromMilliseconds(
838 kOverviewSelectorTransitionMilliseconds));
839 animation_settings.SetPreemptionStrategy(
840 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
841 animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN);
842 // CleanupWidgetAfterAnimationObserver will delete itself (and the
843 // widget) when the movement animation is complete.
844 animation_settings.AddObserver(
845 new CleanupWidgetAfterAnimationObserver(std::move(selection_widget_)));
846 old_selection->SetOpacity(0.f);
847 old_selection_window->SetBounds(old_selection_window->GetBounds() +
848 fade_out_direction);
849 old_selection->Hide();
850 }
851 if (out_of_bounds)
852 return;
853
854 if (!selection_widget_)
855 InitSelectionWidget(direction);
856 // Send an a11y alert so that if ChromeVox is enabled, the item label is
857 // read.
858 SelectedWindow()->SendAccessibleSelectionEvent();
859 // The selection widget is moved to the newly selected item in the same
860 // grid.
861 MoveSelectionWidgetToTarget(animate);
862 }
863
864 void WindowGrid::MoveSelectionWidgetToTarget(bool animate) {
865 gfx::Rect bounds =
866 root_window_->ConvertRectFromScreen(SelectedWindow()->target_bounds());
867 if (ash::MaterialDesignController::IsOverviewMaterial())
868 bounds.Inset(kSelectionInset, kSelectionInset);
869 if (animate) {
870 WmWindow* selection_widget_window =
871 WmLookup::Get()->GetWindowForWidget(selection_widget_.get());
872 ui::ScopedLayerAnimationSettings animation_settings(
873 selection_widget_window->GetLayer()->GetAnimator());
874 animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
875 kOverviewSelectorTransitionMilliseconds));
876 animation_settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
877 animation_settings.SetPreemptionStrategy(
878 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
879 selection_widget_->SetBounds(bounds);
880 selection_widget_->SetOpacity(1.f);
881 return;
882 }
883 selection_widget_->SetBounds(bounds);
884 selection_widget_->SetOpacity(1.f);
885 }
886
887 bool WindowGrid::FitWindowRectsInBounds(const gfx::Rect& bounds,
888 int height,
889 std::vector<gfx::Rect>* rects,
890 int* max_bottom,
891 int* min_right,
892 int* max_right) {
893 rects->resize(window_list_.size());
894 bool windows_fit = true;
895
896 // Start in the top-left corner of |bounds|.
897 int left = bounds.x();
898 int top = bounds.y();
899
900 // Keep track of the lowest coordinate.
901 *max_bottom = bounds.y();
902
903 // Right bound of the narrowest row.
904 *min_right = bounds.right();
905 // Right bound of the widest row.
906 *max_right = bounds.x();
907
908 // With Material Design all elements are of same height and only the height is
909 // necessary to determine each item's scale.
910 const gfx::Size item_size(0, height);
911 size_t i = 0;
912 for (auto window : window_list_) {
913 const gfx::Rect target_bounds = window->GetWindow()->GetTargetBounds();
914 const int width =
915 std::max(1, gfx::ToFlooredInt(target_bounds.width() *
916 window->GetItemScale(item_size)) +
917 2 * kWindowMarginMD);
918 if (left + width > bounds.right()) {
919 // Move to the next row if possible.
920 if (*min_right > left)
921 *min_right = left;
922 if (*max_right < left)
923 *max_right = left;
924 top += height;
925
926 // Check if the new row reaches the bottom or if the first item in the new
927 // row does not fit within the available width.
928 if (top + height > bounds.bottom() ||
929 bounds.x() + width > bounds.right()) {
930 windows_fit = false;
931 break;
932 }
933 left = bounds.x();
934 }
935
936 // Position the current rect.
937 (*rects)[i].SetRect(left, top, width, height);
938
939 // Increment horizontal position using sanitized positive |width()|.
940 left += (*rects)[i].width();
941
942 if (++i == window_list_.size()) {
943 // Update the narrowest and widest row width for the last row.
944 if (*min_right > left)
945 *min_right = left;
946 if (*max_right < left)
947 *max_right = left;
948 }
949 *max_bottom = top + height;
950 }
951 return windows_fit;
952 }
953
954 } // namespace ash
OLDNEW
« no previous file with comments | « ash/wm/overview/window_grid.h ('k') | ash/wm/overview/window_selector.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698