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 |