| 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/wm/window_cycle_list.h" | |
| 6 | |
| 7 #include <list> | |
| 8 #include <map> | |
| 9 | |
| 10 #include "ash/common/ash_switches.h" | |
| 11 #include "ash/common/shell_window_ids.h" | |
| 12 #include "ash/common/wm/forwarding_layer_delegate.h" | |
| 13 #include "ash/common/wm/mru_window_tracker.h" | |
| 14 #include "ash/common/wm/window_state.h" | |
| 15 #include "ash/common/wm_root_window_controller.h" | |
| 16 #include "ash/common/wm_shell.h" | |
| 17 #include "ash/common/wm_window.h" | |
| 18 #include "ash/shell.h" | |
| 19 #include "base/command_line.h" | |
| 20 #include "ui/compositor/layer_tree_owner.h" | |
| 21 #include "ui/views/background.h" | |
| 22 #include "ui/views/layout/box_layout.h" | |
| 23 #include "ui/views/painter.h" | |
| 24 #include "ui/views/view.h" | |
| 25 #include "ui/views/widget/widget.h" | |
| 26 #include "ui/wm/core/visibility_controller.h" | |
| 27 #include "ui/wm/core/window_util.h" | |
| 28 | |
| 29 namespace ash { | |
| 30 | |
| 31 void EnsureAllChildrenAreVisible(ui::Layer* layer) { | |
| 32 std::list<ui::Layer*> layers; | |
| 33 layers.push_back(layer); | |
| 34 while (!layers.empty()) { | |
| 35 for (auto child : layers.front()->children()) | |
| 36 layers.push_back(child); | |
| 37 layers.front()->SetVisible(true); | |
| 38 layers.pop_front(); | |
| 39 } | |
| 40 } | |
| 41 | |
| 42 // Returns the window immediately below |window| in the current container. | |
| 43 WmWindow* GetWindowBelow(WmWindow* window) { | |
| 44 WmWindow* parent = window->GetParent(); | |
| 45 if (!parent) | |
| 46 return nullptr; | |
| 47 const WmWindow::Windows children = parent->GetChildren(); | |
| 48 auto iter = std::find(children.begin(), children.end(), window); | |
| 49 CHECK(*iter == window); | |
| 50 return (iter != children.begin()) ? *(iter - 1) : nullptr; | |
| 51 } | |
| 52 | |
| 53 // This class restores and moves a window to the front of the stacking order for | |
| 54 // the duration of the class's scope. | |
| 55 class ScopedShowWindow : public WmWindowObserver { | |
| 56 public: | |
| 57 ScopedShowWindow(); | |
| 58 ~ScopedShowWindow() override; | |
| 59 | |
| 60 // Show |window| at the top of the stacking order. | |
| 61 void Show(WmWindow* window); | |
| 62 | |
| 63 // Cancel restoring the window on going out of scope. | |
| 64 void CancelRestore(); | |
| 65 | |
| 66 private: | |
| 67 // WmWindowObserver: | |
| 68 void OnWindowTreeChanging(WmWindow* window, | |
| 69 const TreeChangeParams& params) override; | |
| 70 | |
| 71 // The window being shown. | |
| 72 WmWindow* window_; | |
| 73 | |
| 74 // The window immediately below where window_ belongs. | |
| 75 WmWindow* stack_window_above_; | |
| 76 | |
| 77 // If true, minimize window_ on going out of scope. | |
| 78 bool minimized_; | |
| 79 | |
| 80 DISALLOW_COPY_AND_ASSIGN(ScopedShowWindow); | |
| 81 }; | |
| 82 | |
| 83 // A view that mirrors a single window. Layers are lifted from the underlying | |
| 84 // window (which gets new ones in their place). New paint calls, if any, are | |
| 85 // forwarded to the underlying window. | |
| 86 class WindowMirrorView : public views::View, public ::wm::LayerDelegateFactory { | |
| 87 public: | |
| 88 explicit WindowMirrorView(WmWindow* window) : target_(window) { | |
| 89 DCHECK(window); | |
| 90 } | |
| 91 ~WindowMirrorView() override {} | |
| 92 | |
| 93 void Init() { | |
| 94 SetPaintToLayer(true); | |
| 95 | |
| 96 layer_owner_ = ::wm::RecreateLayers( | |
| 97 target_->GetInternalWidget()->GetNativeView(), this); | |
| 98 | |
| 99 GetMirrorLayer()->parent()->Remove(GetMirrorLayer()); | |
| 100 layer()->Add(GetMirrorLayer()); | |
| 101 | |
| 102 // Some extra work is needed when the target window is minimized. | |
| 103 if (target_->GetWindowState()->IsMinimized()) { | |
| 104 GetMirrorLayer()->SetVisible(true); | |
| 105 GetMirrorLayer()->SetOpacity(1); | |
| 106 EnsureAllChildrenAreVisible(GetMirrorLayer()); | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 // views::View: | |
| 111 gfx::Size GetPreferredSize() const override { | |
| 112 const int kMaxWidth = 512; | |
| 113 const int kMaxHeight = 256; | |
| 114 | |
| 115 gfx::Size target_size = target_->GetBounds().size(); | |
| 116 if (target_size.width() <= kMaxWidth && | |
| 117 target_size.height() <= kMaxHeight) { | |
| 118 return target_size; | |
| 119 } | |
| 120 | |
| 121 float scale = | |
| 122 std::min(kMaxWidth / static_cast<float>(target_size.width()), | |
| 123 kMaxHeight / static_cast<float>(target_size.height())); | |
| 124 return gfx::ScaleToCeiledSize(target_size, scale, scale); | |
| 125 } | |
| 126 | |
| 127 void Layout() override { | |
| 128 // Position at 0, 0. | |
| 129 GetMirrorLayer()->SetBounds(gfx::Rect(GetMirrorLayer()->bounds().size())); | |
| 130 | |
| 131 // Scale down if necessary. | |
| 132 gfx::Transform mirror_transform; | |
| 133 if (size() != target_->GetBounds().size()) { | |
| 134 const float scale = | |
| 135 width() / static_cast<float>(target_->GetBounds().width()); | |
| 136 mirror_transform.Scale(scale, scale); | |
| 137 } | |
| 138 GetMirrorLayer()->SetTransform(mirror_transform); | |
| 139 } | |
| 140 | |
| 141 // ::wm::LayerDelegateFactory: | |
| 142 ui::LayerDelegate* CreateDelegate(ui::LayerDelegate* delegate) override { | |
| 143 if (!delegate) | |
| 144 return nullptr; | |
| 145 delegates_.push_back( | |
| 146 base::WrapUnique(new wm::ForwardingLayerDelegate(target_, delegate))); | |
| 147 | |
| 148 return delegates_.back().get(); | |
| 149 } | |
| 150 | |
| 151 private: | |
| 152 // Gets the root of the layer tree that was lifted from |target_| (and is now | |
| 153 // a child of |this->layer()|). | |
| 154 ui::Layer* GetMirrorLayer() { return layer_owner_->root(); } | |
| 155 | |
| 156 // The original window that is being represented by |this|. | |
| 157 WmWindow* target_; | |
| 158 | |
| 159 // Retains ownership of the mirror layer tree. | |
| 160 std::unique_ptr<ui::LayerTreeOwner> layer_owner_; | |
| 161 | |
| 162 std::vector<std::unique_ptr<wm::ForwardingLayerDelegate>> delegates_; | |
| 163 | |
| 164 DISALLOW_COPY_AND_ASSIGN(WindowMirrorView); | |
| 165 }; | |
| 166 | |
| 167 // A view that shows a collection of windows the user can tab through. | |
| 168 class WindowCycleView : public views::View { | |
| 169 public: | |
| 170 explicit WindowCycleView(const WindowCycleList::WindowList& windows) | |
| 171 : mirror_container_(new views::View()), | |
| 172 highlight_view_(new views::View()), | |
| 173 target_window_(nullptr) { | |
| 174 DCHECK(!windows.empty()); | |
| 175 SetPaintToLayer(true); | |
| 176 layer()->SetFillsBoundsOpaquely(false); | |
| 177 | |
| 178 set_background(views::Background::CreateSolidBackground( | |
| 179 SkColorSetA(SK_ColorBLACK, 0xCC))); | |
| 180 | |
| 181 const int kInsideBorderPaddingDip = 64; | |
| 182 const int kBetweenChildPaddingDip = 10; | |
| 183 views::BoxLayout* layout = new views::BoxLayout( | |
| 184 views::BoxLayout::kHorizontal, kInsideBorderPaddingDip, | |
| 185 kInsideBorderPaddingDip, kBetweenChildPaddingDip); | |
| 186 layout->set_cross_axis_alignment( | |
| 187 views::BoxLayout::CROSS_AXIS_ALIGNMENT_START); | |
| 188 mirror_container_->SetLayoutManager(layout); | |
| 189 mirror_container_->SetPaintToLayer(true); | |
| 190 mirror_container_->layer()->SetFillsBoundsOpaquely(false); | |
| 191 // The preview list animates bounds changes (other animatable properties | |
| 192 // never change). | |
| 193 mirror_container_->layer()->SetAnimator( | |
| 194 ui::LayerAnimator::CreateImplicitAnimator()); | |
| 195 | |
| 196 for (WmWindow* window : windows) { | |
| 197 WindowMirrorView* view = new WindowMirrorView(window); | |
| 198 view->Init(); | |
| 199 window_view_map_[window] = view; | |
| 200 mirror_container_->AddChildView(view); | |
| 201 } | |
| 202 | |
| 203 const float kHighlightCornerRadius = 4; | |
| 204 highlight_view_->set_background(views::Background::CreateBackgroundPainter( | |
| 205 true, views::Painter::CreateRoundRectWith1PxBorderPainter( | |
| 206 SkColorSetA(SK_ColorWHITE, 0x4D), | |
| 207 SkColorSetA(SK_ColorWHITE, 0x33), kHighlightCornerRadius))); | |
| 208 highlight_view_->SetPaintToLayer(true); | |
| 209 highlight_view_->layer()->SetFillsBoundsOpaquely(false); | |
| 210 // The selection highlight also animates all bounds changes and never | |
| 211 // changes other animatable properties. | |
| 212 highlight_view_->layer()->SetAnimator( | |
| 213 ui::LayerAnimator::CreateImplicitAnimator()); | |
| 214 | |
| 215 AddChildView(highlight_view_); | |
| 216 AddChildView(mirror_container_); | |
| 217 SetTargetWindow(windows.front()); | |
| 218 } | |
| 219 | |
| 220 ~WindowCycleView() override {} | |
| 221 | |
| 222 void SetTargetWindow(WmWindow* target) { | |
| 223 target_window_ = target; | |
| 224 if (GetWidget()) | |
| 225 Layout(); | |
| 226 } | |
| 227 | |
| 228 void HandleWindowDestruction(WmWindow* destroying_window, | |
| 229 WmWindow* new_target) { | |
| 230 auto view_iter = window_view_map_.find(destroying_window); | |
| 231 view_iter->second->parent()->RemoveChildView(view_iter->second); | |
| 232 window_view_map_.erase(view_iter); | |
| 233 SetTargetWindow(new_target); | |
| 234 } | |
| 235 | |
| 236 // views::View overrides: | |
| 237 gfx::Size GetPreferredSize() const override { | |
| 238 return mirror_container_->GetPreferredSize(); | |
| 239 } | |
| 240 | |
| 241 void Layout() override { | |
| 242 // Possible if the last window is deleted. | |
| 243 if (!target_window_) | |
| 244 return; | |
| 245 | |
| 246 // The preview list (|mirror_container_|) starts flush to the left of | |
| 247 // the screen but moves to the left (off the edge of the screen) as the use | |
| 248 // iterates over the previews. The list will move just enough to ensure the | |
| 249 // highlighted preview is at or to the left of the center of the workspace. | |
| 250 views::View* target_view = window_view_map_[target_window_]; | |
| 251 gfx::RectF target_bounds(target_view->GetLocalBounds()); | |
| 252 views::View::ConvertRectToTarget(target_view, mirror_container_, | |
| 253 &target_bounds); | |
| 254 gfx::Rect container_bounds(mirror_container_->GetPreferredSize()); | |
| 255 int x_offset = width() / 2 - target_bounds.CenterPoint().x(); | |
| 256 x_offset = std::min(x_offset, 0); | |
| 257 container_bounds.set_x(x_offset); | |
| 258 mirror_container_->SetBoundsRect(container_bounds); | |
| 259 | |
| 260 // Calculate the target preview's bounds relative to |this|. | |
| 261 views::View::ConvertRectToTarget(mirror_container_, this, &target_bounds); | |
| 262 const int kHighlightPaddingDip = 5; | |
| 263 target_bounds.Inset(gfx::InsetsF(-kHighlightPaddingDip)); | |
| 264 highlight_view_->SetBoundsRect(gfx::ToEnclosingRect(target_bounds)); | |
| 265 } | |
| 266 | |
| 267 WmWindow* target_window() { return target_window_; } | |
| 268 | |
| 269 private: | |
| 270 std::map<WmWindow*, WindowMirrorView*> window_view_map_; | |
| 271 views::View* mirror_container_; | |
| 272 views::View* highlight_view_; | |
| 273 WmWindow* target_window_; | |
| 274 | |
| 275 DISALLOW_COPY_AND_ASSIGN(WindowCycleView); | |
| 276 }; | |
| 277 | |
| 278 ScopedShowWindow::ScopedShowWindow() | |
| 279 : window_(nullptr), stack_window_above_(nullptr), minimized_(false) {} | |
| 280 | |
| 281 ScopedShowWindow::~ScopedShowWindow() { | |
| 282 if (window_) { | |
| 283 window_->GetParent()->RemoveObserver(this); | |
| 284 | |
| 285 // Restore window's stacking position. | |
| 286 if (stack_window_above_) | |
| 287 window_->GetParent()->StackChildAbove(window_, stack_window_above_); | |
| 288 else | |
| 289 window_->GetParent()->StackChildAtBottom(window_); | |
| 290 | |
| 291 // Restore minimized state. | |
| 292 if (minimized_) | |
| 293 window_->GetWindowState()->Minimize(); | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 void ScopedShowWindow::Show(WmWindow* window) { | |
| 298 DCHECK(!window_); | |
| 299 window_ = window; | |
| 300 stack_window_above_ = GetWindowBelow(window); | |
| 301 minimized_ = window->GetWindowState()->IsMinimized(); | |
| 302 window_->GetParent()->AddObserver(this); | |
| 303 window_->Show(); | |
| 304 window_->GetWindowState()->Activate(); | |
| 305 } | |
| 306 | |
| 307 void ScopedShowWindow::CancelRestore() { | |
| 308 if (!window_) | |
| 309 return; | |
| 310 window_->GetParent()->RemoveObserver(this); | |
| 311 window_ = stack_window_above_ = nullptr; | |
| 312 } | |
| 313 | |
| 314 void ScopedShowWindow::OnWindowTreeChanging(WmWindow* window, | |
| 315 const TreeChangeParams& params) { | |
| 316 // Only interested in removal. | |
| 317 if (params.new_parent != nullptr) | |
| 318 return; | |
| 319 | |
| 320 if (params.target == window_) { | |
| 321 CancelRestore(); | |
| 322 } else if (params.target == stack_window_above_) { | |
| 323 // If the window this window was above is removed, use the next window down | |
| 324 // as the restore marker. | |
| 325 stack_window_above_ = GetWindowBelow(stack_window_above_); | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 WindowCycleList::WindowCycleList(const WindowList& windows) | |
| 330 : windows_(windows), current_index_(0), cycle_view_(nullptr) { | |
| 331 WmShell::Get()->mru_window_tracker()->SetIgnoreActivations(true); | |
| 332 | |
| 333 for (WmWindow* window : windows_) | |
| 334 window->AddObserver(this); | |
| 335 | |
| 336 if (ShouldShowUi()) { | |
| 337 WmWindow* root_window = WmShell::Get()->GetRootWindowForNewWindows(); | |
| 338 views::Widget* widget = new views::Widget; | |
| 339 views::Widget::InitParams params; | |
| 340 params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; | |
| 341 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
| 342 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; | |
| 343 params.accept_events = true; | |
| 344 // TODO(estade): make sure nothing untoward happens when the lock screen | |
| 345 // or a system modal dialog is shown. | |
| 346 root_window->GetRootWindowController() | |
| 347 ->ConfigureWidgetInitParamsForContainer( | |
| 348 widget, kShellWindowId_OverlayContainer, ¶ms); | |
| 349 widget->Init(params); | |
| 350 | |
| 351 cycle_view_ = new WindowCycleView(windows_); | |
| 352 | |
| 353 widget->SetContentsView(cycle_view_); | |
| 354 // TODO(estade): right now this just extends past the edge of the screen if | |
| 355 // there are too many windows. Handle this more gracefully. Also, if | |
| 356 // the display metrics change, cancel the UI. | |
| 357 gfx::Rect widget_rect = widget->GetWorkAreaBoundsInScreen(); | |
| 358 int widget_height = cycle_view_->GetPreferredSize().height(); | |
| 359 widget_rect.set_y((widget_rect.height() - widget_height) / 2); | |
| 360 widget_rect.set_height(widget_height); | |
| 361 widget->SetBounds(widget_rect); | |
| 362 widget->Show(); | |
| 363 cycle_ui_widget_.reset(widget); | |
| 364 } | |
| 365 } | |
| 366 | |
| 367 WindowCycleList::~WindowCycleList() { | |
| 368 WmShell::Get()->mru_window_tracker()->SetIgnoreActivations(false); | |
| 369 for (WmWindow* window : windows_) | |
| 370 window->RemoveObserver(this); | |
| 371 | |
| 372 if (showing_window_) | |
| 373 showing_window_->CancelRestore(); | |
| 374 | |
| 375 if (cycle_view_ && cycle_view_->target_window()) { | |
| 376 cycle_view_->target_window()->Show(); | |
| 377 cycle_view_->target_window()->GetWindowState()->Activate(); | |
| 378 } | |
| 379 } | |
| 380 | |
| 381 void WindowCycleList::Step(WindowCycleController::Direction direction) { | |
| 382 if (windows_.empty()) | |
| 383 return; | |
| 384 | |
| 385 // When there is only one window, we should give feedback to the user. If the | |
| 386 // window is minimized, we should also show it. | |
| 387 if (windows_.size() == 1) { | |
| 388 windows_[0]->Animate(::wm::WINDOW_ANIMATION_TYPE_BOUNCE); | |
| 389 windows_[0]->Show(); | |
| 390 windows_[0]->GetWindowState()->Activate(); | |
| 391 return; | |
| 392 } | |
| 393 | |
| 394 DCHECK(static_cast<size_t>(current_index_) < windows_.size()); | |
| 395 | |
| 396 // We're in a valid cycle, so step forward or backward. | |
| 397 current_index_ += direction == WindowCycleController::FORWARD ? 1 : -1; | |
| 398 | |
| 399 // Wrap to window list size. | |
| 400 current_index_ = (current_index_ + windows_.size()) % windows_.size(); | |
| 401 DCHECK(windows_[current_index_]); | |
| 402 | |
| 403 if (cycle_view_) { | |
| 404 cycle_view_->SetTargetWindow(windows_[current_index_]); | |
| 405 return; | |
| 406 } | |
| 407 | |
| 408 // Make sure the next window is visible. | |
| 409 showing_window_.reset(new ScopedShowWindow); | |
| 410 showing_window_->Show(windows_[current_index_]); | |
| 411 } | |
| 412 | |
| 413 void WindowCycleList::OnWindowDestroying(WmWindow* window) { | |
| 414 window->RemoveObserver(this); | |
| 415 | |
| 416 WindowList::iterator i = std::find(windows_.begin(), windows_.end(), window); | |
| 417 // TODO(oshima): Change this back to DCHECK once crbug.com/483491 is fixed. | |
| 418 CHECK(i != windows_.end()); | |
| 419 int removed_index = static_cast<int>(i - windows_.begin()); | |
| 420 windows_.erase(i); | |
| 421 if (current_index_ > removed_index || | |
| 422 current_index_ == static_cast<int>(windows_.size())) { | |
| 423 current_index_--; | |
| 424 } | |
| 425 | |
| 426 if (cycle_view_) { | |
| 427 WmWindow* new_target_window = | |
| 428 windows_.empty() ? nullptr : windows_[current_index_]; | |
| 429 cycle_view_->HandleWindowDestruction(window, new_target_window); | |
| 430 if (windows_.empty()) { | |
| 431 // This deletes us. | |
| 432 Shell::GetInstance()->window_cycle_controller()->StopCycling(); | |
| 433 return; | |
| 434 } | |
| 435 } | |
| 436 } | |
| 437 | |
| 438 bool WindowCycleList::ShouldShowUi() { | |
| 439 return windows_.size() > 1 && | |
| 440 base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 441 switches::kAshEnableWindowCycleUi); | |
| 442 } | |
| 443 | |
| 444 } // namespace ash | |
| OLD | NEW |