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

Side by Side Diff: ash/common/wm/window_cycle_list.cc

Issue 2734653002: chromeos: Move files in //ash/common to //ash (Closed)
Patch Set: fix a11y tests, fix docs Created 3 years, 9 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/common/wm/window_cycle_list.h ('k') | ash/common/wm/window_dimmer.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/common/wm/window_cycle_list.h"
6
7 #include <list>
8 #include <map>
9
10 #include "ash/common/wm/mru_window_tracker.h"
11 #include "ash/common/wm/window_state.h"
12 #include "ash/common/wm_shell.h"
13 #include "ash/common/wm_window.h"
14 #include "ash/public/cpp/shell_window_ids.h"
15 #include "ash/root_window_controller.h"
16 #include "base/command_line.h"
17 #include "ui/accessibility/ax_node_data.h"
18 #include "ui/compositor/scoped_layer_animation_settings.h"
19 #include "ui/display/display.h"
20 #include "ui/display/screen.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/views/background.h"
23 #include "ui/views/border.h"
24 #include "ui/views/controls/label.h"
25 #include "ui/views/layout/box_layout.h"
26 #include "ui/views/painter.h"
27 #include "ui/views/view.h"
28 #include "ui/views/widget/widget.h"
29 #include "ui/views/widget/widget_delegate.h"
30 #include "ui/wm/core/visibility_controller.h"
31
32 namespace ash {
33
34 namespace {
35
36 bool g_disable_initial_delay = false;
37
38 // Used for the highlight view and the shield (black background).
39 constexpr float kBackgroundCornerRadius = 4.f;
40
41 // This background paints a |Painter| but fills the view's layer's size rather
42 // than the view's size.
43 class LayerFillBackgroundPainter : public views::Background {
44 public:
45 explicit LayerFillBackgroundPainter(std::unique_ptr<views::Painter> painter)
46 : painter_(std::move(painter)) {}
47
48 ~LayerFillBackgroundPainter() override {}
49
50 void Paint(gfx::Canvas* canvas, views::View* view) const override {
51 views::Painter::PaintPainterAt(canvas, painter_.get(),
52 gfx::Rect(view->layer()->size()));
53 }
54
55 private:
56 std::unique_ptr<views::Painter> painter_;
57
58 DISALLOW_COPY_AND_ASSIGN(LayerFillBackgroundPainter);
59 };
60
61 } // namespace
62
63 // This view represents a single WmWindow by displaying a title and a thumbnail
64 // of the window's contents.
65 class WindowPreviewView : public views::View, public aura::WindowObserver {
66 public:
67 explicit WindowPreviewView(WmWindow* window)
68 : window_title_(new views::Label),
69 preview_background_(new views::View),
70 mirror_view_(window->CreateViewWithRecreatedLayers().release()),
71 window_observer_(this) {
72 window_observer_.Add(window->aura_window());
73 window_title_->SetText(window->GetTitle());
74 window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
75 window_title_->SetEnabledColor(SK_ColorWHITE);
76 window_title_->SetAutoColorReadabilityEnabled(false);
77 // Background is not fully opaque, so subpixel rendering won't look good.
78 window_title_->SetSubpixelRenderingEnabled(false);
79 // The base font is 12pt (for English) so this comes out to 14pt.
80 const int kLabelSizeDelta = 2;
81 window_title_->SetFontList(
82 window_title_->font_list().DeriveWithSizeDelta(kLabelSizeDelta));
83 const int kAboveLabelPadding = 5;
84 const int kBelowLabelPadding = 10;
85 window_title_->SetBorder(
86 views::CreateEmptyBorder(kAboveLabelPadding, 0, kBelowLabelPadding, 0));
87 AddChildView(window_title_);
88
89 // Preview padding is black at 50% opacity.
90 preview_background_->set_background(
91 views::Background::CreateSolidBackground(
92 SkColorSetA(SK_ColorBLACK, 0xFF / 2)));
93 AddChildView(preview_background_);
94
95 AddChildView(mirror_view_);
96
97 SetFocusBehavior(FocusBehavior::ALWAYS);
98 }
99 ~WindowPreviewView() override {}
100
101 // views::View:
102 gfx::Size GetPreferredSize() const override {
103 gfx::Size size = GetSizeForPreviewArea();
104 size.Enlarge(0, window_title_->GetPreferredSize().height());
105 return size;
106 }
107
108 void Layout() override {
109 const gfx::Size preview_area_size = GetSizeForPreviewArea();
110 // The window title is positioned above the preview area.
111 window_title_->SetBounds(0, 0, width(),
112 height() - preview_area_size.height());
113
114 gfx::Rect preview_area_bounds(preview_area_size);
115 preview_area_bounds.set_y(height() - preview_area_size.height());
116 mirror_view_->SetSize(GetMirrorViewScaledSize());
117 if (mirror_view_->size() == preview_area_size) {
118 // Padding is not needed, hide the background and set the mirror view
119 // to take up the entire preview area.
120 mirror_view_->SetPosition(preview_area_bounds.origin());
121 preview_background_->SetVisible(false);
122 return;
123 }
124
125 // Padding is needed, so show the background and set the mirror view to be
126 // centered within it.
127 preview_background_->SetBoundsRect(preview_area_bounds);
128 preview_background_->SetVisible(true);
129 preview_area_bounds.ClampToCenteredSize(mirror_view_->size());
130 mirror_view_->SetPosition(preview_area_bounds.origin());
131 }
132
133 void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
134 node_data->role = ui::AX_ROLE_WINDOW;
135 node_data->SetName(window_title_->text());
136 }
137
138 // aura::WindowObserver:
139 void OnWindowDestroying(aura::Window* window) override {
140 window_observer_.Remove(window);
141 }
142
143 void OnWindowTitleChanged(aura::Window* window) override {
144 window_title_->SetText(window->GetTitle());
145 }
146
147 private:
148 // The maximum width of a window preview.
149 static const int kMaxPreviewWidth = 512;
150 // All previews are the same height (this is achieved via a combination of
151 // scaling and padding).
152 static const int kFixedPreviewHeight = 256;
153
154 // Returns the size for the mirror view, scaled to fit within the max bounds.
155 // Scaling is always 1:1 and we only scale down, never up.
156 gfx::Size GetMirrorViewScaledSize() const {
157 gfx::Size mirror_pref_size = mirror_view_->GetPreferredSize();
158
159 if (mirror_pref_size.width() > kMaxPreviewWidth ||
160 mirror_pref_size.height() > kFixedPreviewHeight) {
161 float scale = std::min(
162 kMaxPreviewWidth / static_cast<float>(mirror_pref_size.width()),
163 kFixedPreviewHeight / static_cast<float>(mirror_pref_size.height()));
164 mirror_pref_size =
165 gfx::ScaleToFlooredSize(mirror_pref_size, scale, scale);
166 }
167
168 return mirror_pref_size;
169 }
170
171 // Returns the size for the entire preview area (mirror view and additional
172 // padding). All previews will be the same height, so if the mirror view isn't
173 // tall enough we will add top and bottom padding. Previews can range in width
174 // from kMaxPreviewWidth down to half that value. Again, padding will be added
175 // to the sides to achieve this if the preview is too narrow.
176 gfx::Size GetSizeForPreviewArea() const {
177 gfx::Size mirror_size = GetMirrorViewScaledSize();
178 float aspect_ratio =
179 static_cast<float>(mirror_size.width()) / mirror_size.height();
180 gfx::Size preview_size = mirror_size;
181 // Very narrow windows get vertical bars of padding on the sides.
182 if (aspect_ratio < 0.5f)
183 preview_size.set_width(mirror_size.height() / 2);
184
185 // All previews are the same height (this may add padding on top and
186 // bottom).
187 preview_size.set_height(kFixedPreviewHeight);
188 // Previews should never be narrower than half their max width (128dip).
189 preview_size.set_width(
190 std::max(preview_size.width(), kMaxPreviewWidth / 2));
191
192 return preview_size;
193 }
194
195 // Displays the title of the window above the preview.
196 views::Label* window_title_;
197 // When visible, shows a darkened background area behind |mirror_view_|
198 // (effectively padding the preview to fit the desired bounds).
199 views::View* preview_background_;
200 // The view that actually renders a thumbnail version of the window.
201 views::View* mirror_view_;
202
203 ScopedObserver<aura::Window, aura::WindowObserver> window_observer_;
204
205 DISALLOW_COPY_AND_ASSIGN(WindowPreviewView);
206 };
207
208 // A view that shows a collection of windows the user can tab through.
209 class WindowCycleView : public views::WidgetDelegateView {
210 public:
211 explicit WindowCycleView(const WindowCycleList::WindowList& windows)
212 : mirror_container_(new views::View()),
213 highlight_view_(new views::View()),
214 target_window_(nullptr) {
215 DCHECK(!windows.empty());
216 SetPaintToLayer();
217 layer()->SetFillsBoundsOpaquely(false);
218 layer()->SetMasksToBounds(true);
219 layer()->SetOpacity(0.0);
220 {
221 ui::ScopedLayerAnimationSettings animate_fade(layer()->GetAnimator());
222 animate_fade.SetTransitionDuration(
223 base::TimeDelta::FromMilliseconds(100));
224 layer()->SetOpacity(1.0);
225 }
226
227 const int kInsideBorderPaddingDip = 64;
228 const int kBetweenChildPaddingDip = 10;
229 views::BoxLayout* layout = new views::BoxLayout(
230 views::BoxLayout::kHorizontal, kInsideBorderPaddingDip,
231 kInsideBorderPaddingDip, kBetweenChildPaddingDip);
232 layout->set_cross_axis_alignment(
233 views::BoxLayout::CROSS_AXIS_ALIGNMENT_START);
234 mirror_container_->SetLayoutManager(layout);
235 mirror_container_->SetPaintToLayer();
236 mirror_container_->layer()->SetFillsBoundsOpaquely(false);
237
238 for (WmWindow* window : windows) {
239 // |mirror_container_| owns |view|.
240 views::View* view = new WindowPreviewView(window);
241 window_view_map_[window] = view;
242 mirror_container_->AddChildView(view);
243 }
244
245 // The background needs to be painted to fill the layer, not the View,
246 // because the layer animates bounds changes but the View's bounds change
247 // immediately.
248 highlight_view_->set_background(new LayerFillBackgroundPainter(
249 views::Painter::CreateRoundRectWith1PxBorderPainter(
250 SkColorSetA(SK_ColorWHITE, 0x4D), SkColorSetA(SK_ColorWHITE, 0x33),
251 kBackgroundCornerRadius)));
252 highlight_view_->SetPaintToLayer();
253
254 highlight_view_->layer()->SetFillsBoundsOpaquely(false);
255
256 AddChildView(highlight_view_);
257 AddChildView(mirror_container_);
258 }
259
260 ~WindowCycleView() override {}
261
262 void SetTargetWindow(WmWindow* target) {
263 target_window_ = target;
264 if (GetWidget()) {
265 Layout();
266 if (target_window_)
267 window_view_map_[target_window_]->RequestFocus();
268 }
269 }
270
271 void HandleWindowDestruction(WmWindow* destroying_window,
272 WmWindow* new_target) {
273 auto view_iter = window_view_map_.find(destroying_window);
274 views::View* preview = view_iter->second;
275 views::View* parent = preview->parent();
276 DCHECK_EQ(mirror_container_, parent);
277 window_view_map_.erase(view_iter);
278 delete preview;
279 // With one of its children now gone, we must re-layout |mirror_container_|.
280 // This must happen before SetTargetWindow() to make sure our own Layout()
281 // works correctly when it's calculating highlight bounds.
282 parent->Layout();
283 SetTargetWindow(new_target);
284 }
285
286 void DestroyContents() {
287 window_view_map_.clear();
288 RemoveAllChildViews(true);
289 }
290
291 // views::WidgetDelegateView overrides:
292 gfx::Size GetPreferredSize() const override {
293 return mirror_container_->GetPreferredSize();
294 }
295
296 void Layout() override {
297 if (!target_window_ || bounds().IsEmpty())
298 return;
299
300 bool first_layout = mirror_container_->bounds().IsEmpty();
301 // If |mirror_container_| has not yet been laid out, we must lay it and its
302 // descendants out so that the calculations based on |target_view| work
303 // properly.
304 if (first_layout)
305 mirror_container_->SizeToPreferredSize();
306
307 views::View* target_view = window_view_map_[target_window_];
308 gfx::RectF target_bounds(target_view->GetLocalBounds());
309 views::View::ConvertRectToTarget(target_view, mirror_container_,
310 &target_bounds);
311 gfx::Rect container_bounds(mirror_container_->GetPreferredSize());
312 // Case one: the container is narrower than the screen. Center the
313 // container.
314 int x_offset = (width() - container_bounds.width()) / 2;
315 if (x_offset < 0) {
316 // Case two: the container is wider than the screen. Center the target
317 // view by moving the list just enough to ensure the target view is in the
318 // center.
319 x_offset = width() / 2 -
320 mirror_container_->GetMirroredXInView(
321 target_bounds.CenterPoint().x());
322
323 // However, the container must span the screen, i.e. the maximum x is 0
324 // and the minimum for its right boundary is the width of the screen.
325 x_offset = std::min(x_offset, 0);
326 x_offset = std::max(x_offset, width() - container_bounds.width());
327 }
328 container_bounds.set_x(x_offset);
329 mirror_container_->SetBoundsRect(container_bounds);
330
331 // Calculate the target preview's bounds relative to |this|.
332 views::View::ConvertRectToTarget(mirror_container_, this, &target_bounds);
333 const int kHighlightPaddingDip = 5;
334 target_bounds.Inset(gfx::InsetsF(-kHighlightPaddingDip));
335 target_bounds.set_x(
336 GetMirroredXWithWidthInView(target_bounds.x(), target_bounds.width()));
337 highlight_view_->SetBoundsRect(gfx::ToEnclosingRect(target_bounds));
338
339 // Enable animations only after the first Layout() pass.
340 if (first_layout) {
341 // The preview list animates bounds changes (other animatable properties
342 // never change).
343 mirror_container_->layer()->SetAnimator(
344 ui::LayerAnimator::CreateImplicitAnimator());
345 // The selection highlight also animates all bounds changes and never
346 // changes other animatable properties.
347 highlight_view_->layer()->SetAnimator(
348 ui::LayerAnimator::CreateImplicitAnimator());
349 }
350 }
351
352 void OnPaintBackground(gfx::Canvas* canvas) override {
353 // We can't set a bg on the mirror container itself because the highlight
354 // view needs to be on top of the bg but behind the target windows.
355 const gfx::RectF shield_bounds(mirror_container_->bounds());
356 cc::PaintFlags flags;
357 flags.setColor(SkColorSetA(SK_ColorBLACK, 0xE6));
358 flags.setStyle(cc::PaintFlags::kFill_Style);
359 float corner_radius = 0.f;
360 if (shield_bounds.width() < width()) {
361 flags.setAntiAlias(true);
362 corner_radius = kBackgroundCornerRadius;
363 }
364 canvas->DrawRoundRect(shield_bounds, corner_radius, flags);
365 }
366
367 View* GetInitiallyFocusedView() override {
368 return window_view_map_[target_window_];
369 }
370
371 WmWindow* target_window() { return target_window_; }
372
373 private:
374 std::map<WmWindow*, views::View*> window_view_map_;
375 views::View* mirror_container_;
376 views::View* highlight_view_;
377 WmWindow* target_window_;
378
379 DISALLOW_COPY_AND_ASSIGN(WindowCycleView);
380 };
381
382 WindowCycleList::WindowCycleList(const WindowList& windows)
383 : windows_(windows),
384 screen_observer_(this) {
385 if (!ShouldShowUi())
386 WmShell::Get()->mru_window_tracker()->SetIgnoreActivations(true);
387
388 for (WmWindow* window : windows_)
389 window->aura_window()->AddObserver(this);
390
391 if (ShouldShowUi()) {
392 if (g_disable_initial_delay) {
393 InitWindowCycleView();
394 } else {
395 show_ui_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(150),
396 this, &WindowCycleList::InitWindowCycleView);
397 }
398 }
399 }
400
401 WindowCycleList::~WindowCycleList() {
402 if (!ShouldShowUi())
403 WmShell::Get()->mru_window_tracker()->SetIgnoreActivations(false);
404
405 for (WmWindow* window : windows_)
406 window->aura_window()->RemoveObserver(this);
407
408 if (!windows_.empty() && user_did_accept_) {
409 WmWindow* target_window = windows_[current_index_];
410 target_window->Show();
411 target_window->GetWindowState()->Activate();
412 }
413
414 if (cycle_ui_widget_)
415 cycle_ui_widget_->Close();
416
417 // |this| is responsible for notifying |cycle_view_| when windows are
418 // destroyed. Since |this| is going away, clobber |cycle_view_|. Otherwise
419 // there will be a race where a window closes after now but before the
420 // Widget::Close() call above actually destroys |cycle_view_|. See
421 // crbug.com/681207
422 if (cycle_view_)
423 cycle_view_->DestroyContents();
424 }
425
426 void WindowCycleList::Step(WindowCycleController::Direction direction) {
427 if (windows_.empty())
428 return;
429
430 // When there is only one window, we should give feedback to the user. If the
431 // window is minimized, we should also show it.
432 if (windows_.size() == 1) {
433 windows_[0]->Animate(::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
434 windows_[0]->Show();
435 windows_[0]->GetWindowState()->Activate();
436 return;
437 }
438
439 DCHECK(static_cast<size_t>(current_index_) < windows_.size());
440
441 if (!cycle_view_ && current_index_ == 0) {
442 // Special case the situation where we're cycling forward but the MRU window
443 // is not active. This occurs when all windows are minimized. The starting
444 // window should be the first one rather than the second.
445 if (direction == WindowCycleController::FORWARD && !windows_[0]->IsActive())
446 current_index_ = -1;
447 }
448
449 // We're in a valid cycle, so step forward or backward.
450 current_index_ += direction == WindowCycleController::FORWARD ? 1 : -1;
451
452 // Wrap to window list size.
453 current_index_ = (current_index_ + windows_.size()) % windows_.size();
454 DCHECK(windows_[current_index_]);
455
456 if (ShouldShowUi()) {
457 if (current_index_ > 1)
458 InitWindowCycleView();
459
460 if (cycle_view_)
461 cycle_view_->SetTargetWindow(windows_[current_index_]);
462 }
463 }
464
465 // static
466 void WindowCycleList::DisableInitialDelayForTesting() {
467 g_disable_initial_delay = true;
468 }
469
470 void WindowCycleList::OnWindowDestroying(aura::Window* window) {
471 window->RemoveObserver(this);
472
473 WindowList::iterator i =
474 std::find(windows_.begin(), windows_.end(), WmWindow::Get(window));
475 // TODO(oshima): Change this back to DCHECK once crbug.com/483491 is fixed.
476 CHECK(i != windows_.end());
477 int removed_index = static_cast<int>(i - windows_.begin());
478 windows_.erase(i);
479 if (current_index_ > removed_index ||
480 current_index_ == static_cast<int>(windows_.size())) {
481 current_index_--;
482 }
483
484 if (cycle_view_) {
485 WmWindow* new_target_window =
486 windows_.empty() ? nullptr : windows_[current_index_];
487 cycle_view_->HandleWindowDestruction(WmWindow::Get(window),
488 new_target_window);
489 if (windows_.empty()) {
490 // This deletes us.
491 WmShell::Get()->window_cycle_controller()->CancelCycling();
492 return;
493 }
494 }
495 }
496
497 void WindowCycleList::OnDisplayAdded(const display::Display& new_display) {}
498
499 void WindowCycleList::OnDisplayRemoved(const display::Display& old_display) {}
500
501 void WindowCycleList::OnDisplayMetricsChanged(const display::Display& display,
502 uint32_t changed_metrics) {
503 if (cycle_ui_widget_ &&
504 display.id() ==
505 display::Screen::GetScreen()
506 ->GetDisplayNearestWindow(cycle_ui_widget_->GetNativeView())
507 .id() &&
508 (changed_metrics & (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION))) {
509 WmShell::Get()->window_cycle_controller()->CancelCycling();
510 // |this| is deleted.
511 return;
512 }
513 }
514
515 bool WindowCycleList::ShouldShowUi() {
516 return windows_.size() > 1;
517 }
518
519 void WindowCycleList::InitWindowCycleView() {
520 if (cycle_view_)
521 return;
522
523 cycle_view_ = new WindowCycleView(windows_);
524 cycle_view_->SetTargetWindow(windows_[current_index_]);
525
526 views::Widget* widget = new views::Widget;
527 views::Widget::InitParams params;
528 params.delegate = cycle_view_;
529 params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
530 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
531 params.accept_events = true;
532 params.name = "WindowCycleList (Alt+Tab)";
533 // TODO(estade): make sure nothing untoward happens when the lock screen
534 // or a system modal dialog is shown.
535 WmWindow* root_window = WmShell::Get()->GetRootWindowForNewWindows();
536 root_window->GetRootWindowController()->ConfigureWidgetInitParamsForContainer(
537 widget, kShellWindowId_OverlayContainer, &params);
538 gfx::Rect widget_rect = root_window->GetDisplayNearestWindow().bounds();
539 const int widget_height = cycle_view_->GetPreferredSize().height();
540 widget_rect.set_y(widget_rect.y() +
541 (widget_rect.height() - widget_height) / 2);
542 widget_rect.set_height(widget_height);
543 params.bounds = widget_rect;
544 widget->Init(params);
545
546 screen_observer_.Add(display::Screen::GetScreen());
547 widget->Show();
548 cycle_ui_widget_ = widget;
549 }
550
551 } // namespace ash
OLDNEW
« no previous file with comments | « ash/common/wm/window_cycle_list.h ('k') | ash/common/wm/window_dimmer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698