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

Unified Diff: ash/wm/splitview/split_view_controller.cc

Issue 2918403006: CrOS Tablet Window management - Split Screen part I (Closed)
Patch Set: Add unittests. Will split the CL into two CLs. Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ash/wm/splitview/split_view_controller.h ('k') | ash/wm/splitview/split_view_controller_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ash/wm/splitview/split_view_controller.cc
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
new file mode 100644
index 0000000000000000000000000000000000000000..82313a2f18b3b3114e9b86365535056e5e2dd671
--- /dev/null
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -0,0 +1,356 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/wm/splitview/split_view_controller.h"
+
+#include "ash/ash_switches.h"
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/screen_util.h"
+#include "ash/shell.h"
+#include "ash/wm/maximize_mode/maximize_mode_controller.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_state.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/wm_event.h"
+#include "base/command_line.h"
+#include "ui/aura/window.h"
+#include "ui/wm/core/coordinate_conversion.h"
+#include "ui/wm/public/activation_client.h"
+
+namespace ash {
+
+namespace {
+
+// Returns true if |window| can be activated and snapped.
+bool CanSnap(aura::Window* window) {
+ if (!wm::CanActivateWindow(window))
+ return false;
+ return wm::GetWindowState(window)->CanSnap();
+}
+
+} // namespace
+
+SplitViewController::SplitViewController() {
+ Shell::Get()->AddShellObserver(this);
+ Shell::Get()->activation_client()->AddObserver(this);
+}
+
+SplitViewController::~SplitViewController() {
+ Shell::Get()->RemoveShellObserver(this);
+ Shell::Get()->activation_client()->RemoveObserver(this);
+ EndSplitView();
+}
+
+// static
+bool SplitViewController::ShouldAllowSplitView() {
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableTabletSplitView)) {
+ return false;
+ }
+ if (!Shell::Get()
+ ->maximize_mode_controller()
+ ->IsMaximizeModeWindowManagerEnabled()) {
+ return false;
+ }
+ return true;
+}
+
+bool SplitViewController::IsSplitViewModeActive() const {
+ return state_ != NOSNAP;
+}
+
+void SplitViewController::SetLeftWindow(aura::Window* left_window) {
+ if (!left_window)
+ return;
+
+ if (state_ == NOSNAP)
+ Shell::Get()->NotifySplitViewModeStarting();
+
+ if (right_window_ == left_window) {
+ StopObserving(right_window_);
+ right_window_ = nullptr;
+ }
+ if (left_window_ != left_window) {
+ StopObserving(left_window_);
+ left_window_ = nullptr;
+ left_window_ = left_window;
+ left_window_->AddObserver(this);
+ wm::GetWindowState(left_window_)->AddObserver(this);
+ }
+
+ State previous_state = state_;
+ switch (state_) {
+ case NOSNAP:
+ default_snap_position_ = LEFT;
+ state_ = LEFT_SNAPPED;
+ break;
+ case LEFT_SNAPPED:
+ state_ = LEFT_SNAPPED;
+ break;
+ case RIGHT_SNAPPED:
+ case BOTH_SNAPPED:
+ state_ = BOTH_SNAPPED;
+ break;
+ }
+
+ const wm::WMEvent event(wm::WM_EVENT_SNAP_LEFT);
+ wm::GetWindowState(left_window_)->OnWMEvent(&event);
+ wm::ActivateWindow(left_window_);
+
+ NotifySplitViewStateChanged(previous_state, state_);
+}
+
+void SplitViewController::SetRightWindow(aura::Window* right_window) {
+ if (!right_window)
+ return;
+
+ if (state_ == NOSNAP)
+ Shell::Get()->NotifySplitViewModeStarting();
+
+ if (left_window_ == right_window) {
+ StopObserving(left_window_);
+ left_window_ = nullptr;
+ }
+ if (right_window_ != right_window) {
+ StopObserving(right_window_);
+ right_window_ = nullptr;
+ right_window_ = right_window;
+ right_window_->AddObserver(this);
+ wm::GetWindowState(right_window_)->AddObserver(this);
+ }
+
+ State previous_state = state_;
+ switch (state_) {
+ case NOSNAP:
+ default_snap_position_ = RIGHT;
+ state_ = RIGHT_SNAPPED;
+ break;
+ case RIGHT_SNAPPED:
+ state_ = RIGHT_SNAPPED;
+ break;
+ case LEFT_SNAPPED:
+ case BOTH_SNAPPED:
+ state_ = BOTH_SNAPPED;
+ break;
+ }
+
+ const wm::WMEvent event(wm::WM_EVENT_SNAP_RIGHT);
+ wm::GetWindowState(right_window_)->OnWMEvent(&event);
+ wm::ActivateWindow(right_window_);
+
+ NotifySplitViewStateChanged(previous_state, state_);
+}
+
+aura::Window* SplitViewController::GetDefaultSnappedWindow() {
+ if (default_snap_position_ == LEFT)
+ return left_window_;
+ if (default_snap_position_ == RIGHT)
+ return right_window_;
+ return nullptr;
+}
+
+gfx::Rect SplitViewController::GetSnappedWindowBoundsInParent(
+ aura::Window* window,
+ State snap_state) {
+ if (snap_state == LEFT_SNAPPED)
+ return GetLeftWindowBoundsInParent(window);
+ else if (snap_state == RIGHT_SNAPPED)
+ return GetRightWindowBoundsInParent(window);
+
+ NOTREACHED();
+ return gfx::Rect();
+}
+
+gfx::Rect SplitViewController::GetSnappedWindowBoundsInScreen(
+ aura::Window* window,
+ State snap_state) {
+ if (snap_state == LEFT_SNAPPED)
+ return GetLeftWindowBoundsInScreen(window);
+ else if (snap_state == RIGHT_SNAPPED)
+ return GetRightWindowBoundsInScreen(window);
+
+ NOTREACHED();
+ return gfx::Rect();
+}
+
+gfx::Rect SplitViewController::GetDisplayWorkAreaBoundsInParent(
+ aura::Window* window) {
+ aura::Window* root_window = window->GetRootWindow();
+ gfx::Rect bounds_in_parent = ScreenUtil::GetDisplayWorkAreaBoundsInParent(
+ root_window->GetChildById(kShellWindowId_DefaultContainer));
+ return bounds_in_parent;
+}
+
+gfx::Rect SplitViewController::GetDisplayWorkAreaBoundsInScreen(
+ aura::Window* window) {
+ gfx::Rect bounds = GetDisplayWorkAreaBoundsInParent(window);
+ ::wm::ConvertRectToScreen(window->GetRootWindow(), &bounds);
+ return bounds;
+}
+
+void SplitViewController::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void SplitViewController::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void SplitViewController::OnWindowDestroying(aura::Window* window) {
+ // If one of the snapped window gets closed, end the split view mode. The
+ // behavior might change in the future.
+ DCHECK(window == left_window_ || window == right_window_);
+ EndSplitView();
+}
+
+void SplitViewController::OnPostWindowStateTypeChange(
+ ash::wm::WindowState* window_state,
+ ash::wm::WindowStateType old_type) {
+ if (window_state->IsFullscreen() || window_state->IsMinimized() ||
+ window_state->IsMaximized()) {
+ // TODO(xdai): Decide what to do if one of the snapped windows gets
+ // minimized /maximized / full-screened. For now we end the split view mode
+ // for simplicity.
+ EndSplitView();
+ }
+}
+
+void SplitViewController::OnWindowActivated(ActivationReason reason,
+ aura::Window* gained_active,
+ aura::Window* lost_active) {
+ if (!IsSplitViewModeActive())
+ return;
+
+ // If |gained_active| was activated as a side effect of a window disposition
+ // change, do nothing. For example, when a snapped window is closed, another
+ // window will be activated before OnWindowDestroying() is called. We should
+ // not try to snap another window in this case.
+ if (reason == ActivationReason::WINDOW_DISPOSITION_CHANGED)
+ return;
+
+ // Only snap window that can be snapped but hasn't been snapped.
+ if (!gained_active || gained_active == left_window_ ||
+ gained_active == right_window_ || !CanSnap(gained_active)) {
+ return;
+ }
+
+ // Only window in MRU list can be snapped.
+ aura::Window::Windows windows =
+ Shell::Get()->mru_window_tracker()->BuildMruWindowList();
+ if (std::find(windows.begin(), windows.end(), gained_active) == windows.end())
+ return;
+
+ // Snap the window on the non-default side of the screen if split view mode
+ // is active.
+ if (default_snap_position_ == LEFT)
+ SetRightWindow(gained_active);
+ else
+ SetLeftWindow(gained_active);
+}
+
+void SplitViewController::OnOverviewModeStarting() {
+ // If split view mode is active, reset |state_| to make it be able to select
+ // another window from overview window grid.
+ if (IsSplitViewModeActive()) {
+ DCHECK(state_ == BOTH_SNAPPED);
+ if (default_snap_position_ == LEFT) {
+ StopObserving(right_window_);
+ state_ = LEFT_SNAPPED;
+ } else {
+ StopObserving(left_window_);
+ state_ = RIGHT_SNAPPED;
+ }
+ NotifySplitViewStateChanged(BOTH_SNAPPED, state_);
+ }
+}
+
+void SplitViewController::OnOverviewModeEnded() {
+ // If split view mode is active but only has one snapped window, use the MRU
+ // window list to auto select another window to snap.
+ if (IsSplitViewModeActive() && state_ != BOTH_SNAPPED) {
+ aura::Window::Windows windows =
+ Shell::Get()->mru_window_tracker()->BuildMruWindowList();
+ auto end = std::remove_if(windows.begin(), windows.end(),
+ std::not1(std::ptr_fun(&CanSnap)));
+ windows.resize(end - windows.begin());
+ for (auto* window : windows) {
+ if (window != GetDefaultSnappedWindow()) {
+ if (default_snap_position_ == LEFT)
+ SetRightWindow(window);
+ else
+ SetLeftWindow(window);
+ break;
+ }
+ }
+ }
+}
+
+void SplitViewController::EndSplitView() {
+ StopObserving(left_window_);
+ StopObserving(right_window_);
+ left_window_ = nullptr;
+ right_window_ = nullptr;
+ default_snap_position_ = LEFT;
+ separator_position_ = -1;
+
+ State previous_state = state_;
+ state_ = NOSNAP;
+ NotifySplitViewStateChanged(previous_state, state_);
+
+ Shell::Get()->NotifySplitViewModeEnded();
+}
+
+void SplitViewController::StopObserving(aura::Window* window) {
+ if (window) {
+ window->RemoveObserver(this);
+ if (wm::GetWindowState(window))
+ wm::GetWindowState(window)->RemoveObserver(this);
+ }
+}
+
+void SplitViewController::NotifySplitViewStateChanged(State previous_state,
+ State state) {
+ if (previous_state == state)
+ return;
+ for (Observer& observer : observers_)
+ observer.OnSplitViewStateChanged(previous_state, state);
+}
+
+gfx::Rect SplitViewController::GetLeftWindowBoundsInParent(
+ aura::Window* window) {
+ gfx::Rect bounds_in_parent = GetDisplayWorkAreaBoundsInParent(window);
+ if (separator_position_ < 0)
+ separator_position_ = bounds_in_parent.width() * 0.5f;
+ gfx::Rect bounds(bounds_in_parent.x(), bounds_in_parent.y(),
+ separator_position_, bounds_in_parent.height());
+ return bounds;
+}
+
+gfx::Rect SplitViewController::GetRightWindowBoundsInParent(
+ aura::Window* window) {
+ gfx::Rect bounds_in_parent = GetDisplayWorkAreaBoundsInParent(window);
+ if (separator_position_ < 0)
+ separator_position_ = bounds_in_parent.width() * 0.5f;
+ gfx::Rect bounds(bounds_in_parent.x() + separator_position_,
+ bounds_in_parent.y(),
+ bounds_in_parent.width() - separator_position_,
+ bounds_in_parent.height());
+ return bounds;
+}
+
+gfx::Rect SplitViewController::GetLeftWindowBoundsInScreen(
+ aura::Window* window) {
+ gfx::Rect bounds = GetLeftWindowBoundsInParent(window);
+ ::wm::ConvertRectToScreen(window->GetRootWindow(), &bounds);
+ return bounds;
+}
+
+gfx::Rect SplitViewController::GetRightWindowBoundsInScreen(
+ aura::Window* window) {
+ gfx::Rect bounds = GetRightWindowBoundsInParent(window);
+ ::wm::ConvertRectToScreen(window->GetRootWindow(), &bounds);
+ return bounds;
+}
+
+} // namespace ash
« no previous file with comments | « ash/wm/splitview/split_view_controller.h ('k') | ash/wm/splitview/split_view_controller_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698