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/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, ¶ms); | |
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 | |
OLD | NEW |