split_view_divider.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. // Copyright 2017 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. #include "ash/wm/splitview/split_view_divider.h"
  5. #include <memory>
  6. #include "ash/display/screen_orientation_controller.h"
  7. #include "ash/public/cpp/shell_window_ids.h"
  8. #include "ash/public/cpp/window_properties.h"
  9. #include "ash/screen_util.h"
  10. #include "ash/shell.h"
  11. #include "ash/style/ash_color_provider.h"
  12. #include "ash/wm/desks/desks_util.h"
  13. #include "ash/wm/splitview/split_view_constants.h"
  14. #include "ash/wm/splitview/split_view_controller.h"
  15. #include "ash/wm/splitview/split_view_divider_handler_view.h"
  16. #include "ash/wm/splitview/split_view_utils.h"
  17. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  18. #include "ash/wm/window_properties.h"
  19. #include "ash/wm/window_util.h"
  20. #include "base/containers/contains.h"
  21. #include "base/task/sequenced_task_runner.h"
  22. #include "chromeos/constants/chromeos_features.h"
  23. #include "ui/aura/scoped_window_targeter.h"
  24. #include "ui/aura/window_targeter.h"
  25. #include "ui/compositor/layer.h"
  26. #include "ui/compositor/layer_type.h"
  27. #include "ui/compositor/scoped_layer_animation_settings.h"
  28. #include "ui/views/background.h"
  29. #include "ui/views/highlight_border.h"
  30. #include "ui/views/view.h"
  31. #include "ui/views/view_targeter_delegate.h"
  32. #include "ui/views/widget/widget.h"
  33. #include "ui/views/widget/widget_delegate.h"
  34. #include "ui/wm/core/coordinate_conversion.h"
  35. #include "ui/wm/core/transient_window_manager.h"
  36. #include "ui/wm/core/window_util.h"
  37. #include "ui/wm/public/activation_client.h"
  38. namespace ash {
  39. namespace {
  40. // The window targeter that is installed on the always on top container window
  41. // when the split view mode is active.
  42. class AlwaysOnTopWindowTargeter : public aura::WindowTargeter {
  43. public:
  44. explicit AlwaysOnTopWindowTargeter(aura::Window* divider_window)
  45. : divider_window_(divider_window) {}
  46. AlwaysOnTopWindowTargeter(const AlwaysOnTopWindowTargeter&) = delete;
  47. AlwaysOnTopWindowTargeter& operator=(const AlwaysOnTopWindowTargeter&) =
  48. delete;
  49. ~AlwaysOnTopWindowTargeter() override = default;
  50. private:
  51. bool GetHitTestRects(aura::Window* target,
  52. gfx::Rect* hit_test_rect_mouse,
  53. gfx::Rect* hit_test_rect_touch) const override {
  54. if (target == divider_window_) {
  55. *hit_test_rect_mouse = *hit_test_rect_touch = gfx::Rect(target->bounds());
  56. hit_test_rect_touch->Inset(
  57. gfx::Insets::VH(-SplitViewDivider::kDividerEdgeInsetForTouch,
  58. -SplitViewDivider::kDividerEdgeInsetForTouch));
  59. return true;
  60. }
  61. return aura::WindowTargeter::GetHitTestRects(target, hit_test_rect_mouse,
  62. hit_test_rect_touch);
  63. }
  64. aura::Window* divider_window_;
  65. };
  66. // The divider view class. Passes the mouse/gesture events to the controller.
  67. // Has two child views, one for the divider and one for its white handler. The
  68. // bounds and transforms of these two child views can be affected by the
  69. // spawning animation and by dragging, but regardless, the controller receives
  70. // mouse/gesture events in the bounds of the |DividerView| object itself.
  71. class DividerView : public views::View, public views::ViewTargeterDelegate {
  72. public:
  73. explicit DividerView(SplitViewController* controller,
  74. SplitViewDivider* divider)
  75. : controller_(controller), divider_(divider) {
  76. divider_view_ = AddChildView(std::make_unique<views::View>());
  77. divider_view_->SetPaintToLayer(ui::LAYER_TEXTURED);
  78. divider_view_->layer()->SetFillsBoundsOpaquely(false);
  79. divider_view_->SetBackground(
  80. views::CreateSolidBackground(AshColorProvider::Get()->GetBaseLayerColor(
  81. AshColorProvider::BaseLayerType::kOpaque)));
  82. divider_handler_view_ =
  83. AddChildView(std::make_unique<SplitViewDividerHandlerView>());
  84. SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
  85. }
  86. DividerView(const DividerView&) = delete;
  87. DividerView& operator=(const DividerView&) = delete;
  88. ~DividerView() override = default;
  89. void DoSpawningAnimation(int spawn_position) {
  90. const gfx::Rect bounds = GetBoundsInScreen();
  91. int divider_signed_offset;
  92. // To animate the divider scaling up from nothing, animate its bounds rather
  93. // than its transform, mostly because a transform that scales by zero would
  94. // be singular. For that bounds animation, express |spawn_position| in local
  95. // coordinates by subtracting a coordinate of the origin. Compute
  96. // |divider_signed_offset| as described in the comment for
  97. // |SplitViewDividerHandlerView::DoSpawningAnimation|.
  98. if (IsCurrentScreenOrientationLandscape()) {
  99. divider_view_->SetBounds(spawn_position - bounds.x(), 0, 0,
  100. bounds.height());
  101. divider_signed_offset = spawn_position - bounds.CenterPoint().x();
  102. } else {
  103. divider_view_->SetBounds(0, spawn_position - bounds.y(), bounds.width(),
  104. 0);
  105. divider_signed_offset = spawn_position - bounds.CenterPoint().y();
  106. }
  107. ui::LayerAnimator* divider_animator = divider_view_->layer()->GetAnimator();
  108. ui::ScopedLayerAnimationSettings settings(divider_animator);
  109. settings.SetTransitionDuration(kSplitviewDividerSpawnDuration);
  110. settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
  111. settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
  112. divider_animator->SchedulePauseForProperties(
  113. kSplitviewDividerSpawnDelay, ui::LayerAnimationElement::BOUNDS);
  114. divider_view_->SetBounds(0, 0, bounds.width(), bounds.height());
  115. divider_handler_view_->DoSpawningAnimation(divider_signed_offset);
  116. }
  117. void SetDividerBarVisible(bool visible) {
  118. divider_handler_view_->SetVisible(visible);
  119. }
  120. // views::View:
  121. void Layout() override {
  122. // There is no divider in clamshell split view. If we are in clamshell mode,
  123. // then we must be transitioning from tablet mode, and the divider will be
  124. // destroyed, meaning there is no need to update it.
  125. if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
  126. return;
  127. divider_view_->SetBoundsRect(GetLocalBounds());
  128. divider_handler_view_->Refresh(controller_->is_resizing());
  129. }
  130. bool OnMousePressed(const ui::MouseEvent& event) override {
  131. gfx::Point location(event.location());
  132. views::View::ConvertPointToScreen(this, &location);
  133. controller_->StartResize(location);
  134. OnResizeStatusChanged();
  135. return true;
  136. }
  137. bool OnMouseDragged(const ui::MouseEvent& event) override {
  138. gfx::Point location(event.location());
  139. views::View::ConvertPointToScreen(this, &location);
  140. controller_->Resize(location);
  141. return true;
  142. }
  143. void OnMouseReleased(const ui::MouseEvent& event) override {
  144. gfx::Point location(event.location());
  145. views::View::ConvertPointToScreen(this, &location);
  146. controller_->EndResize(location);
  147. OnResizeStatusChanged();
  148. if (event.GetClickCount() == 2)
  149. controller_->SwapWindows();
  150. }
  151. void OnGestureEvent(ui::GestureEvent* event) override {
  152. gfx::Point location(event->location());
  153. views::View::ConvertPointToScreen(this, &location);
  154. switch (event->type()) {
  155. case ui::ET_GESTURE_TAP:
  156. if (event->details().tap_count() == 2)
  157. controller_->SwapWindows();
  158. break;
  159. case ui::ET_GESTURE_TAP_DOWN:
  160. case ui::ET_GESTURE_SCROLL_BEGIN:
  161. controller_->StartResize(location);
  162. OnResizeStatusChanged();
  163. break;
  164. case ui::ET_GESTURE_SCROLL_UPDATE:
  165. controller_->Resize(location);
  166. break;
  167. case ui::ET_GESTURE_END:
  168. controller_->EndResize(location);
  169. OnResizeStatusChanged();
  170. break;
  171. default:
  172. break;
  173. }
  174. event->SetHandled();
  175. }
  176. void OnThemeChanged() override {
  177. views::View::OnThemeChanged();
  178. divider_view_->background()->SetNativeControlColor(
  179. AshColorProvider::Get()->GetBaseLayerColor(
  180. AshColorProvider::BaseLayerType::kOpaque));
  181. if (chromeos::features::IsDarkLightModeEnabled()) {
  182. divider_view_->SetBorder(std::make_unique<views::HighlightBorder>(
  183. /*corner_radius=*/0, views::HighlightBorder::Type::kHighlightBorder1,
  184. /*use_light_colors=*/false));
  185. }
  186. }
  187. // views::ViewTargeterDelegate:
  188. bool DoesIntersectRect(const views::View* target,
  189. const gfx::Rect& rect) const override {
  190. DCHECK_EQ(target, this);
  191. return true;
  192. }
  193. private:
  194. void OnResizeStatusChanged() {
  195. // It's possible that when this function is called, split view mode has
  196. // been ended, and the divider widget is to be deleted soon. In this case
  197. // no need to update the divider layout and do the animation.
  198. if (!controller_->InSplitViewMode())
  199. return;
  200. // If |divider_view_|'s bounds are animating, it is for the divider spawning
  201. // animation. Stop that before animating |divider_view_|'s transform.
  202. ui::LayerAnimator* divider_animator = divider_view_->layer()->GetAnimator();
  203. divider_animator->StopAnimatingProperty(ui::LayerAnimationElement::BOUNDS);
  204. // Do the divider enlarge/shrink animation when starting/ending dragging.
  205. divider_view_->SetBoundsRect(GetLocalBounds());
  206. const gfx::Rect old_bounds =
  207. divider_->GetDividerBoundsInScreen(/*is_dragging=*/false);
  208. const gfx::Rect new_bounds =
  209. divider_->GetDividerBoundsInScreen(controller_->is_resizing());
  210. gfx::Transform transform;
  211. transform.Translate(new_bounds.x() - old_bounds.x(),
  212. new_bounds.y() - old_bounds.y());
  213. transform.Scale(
  214. static_cast<float>(new_bounds.width()) / old_bounds.width(),
  215. static_cast<float>(new_bounds.height()) / old_bounds.height());
  216. ui::ScopedLayerAnimationSettings settings(divider_animator);
  217. settings.SetTransitionDuration(
  218. kSplitviewDividerSelectionStatusChangeDuration);
  219. settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
  220. settings.SetPreemptionStrategy(
  221. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  222. divider_view_->SetTransform(transform);
  223. divider_handler_view_->Refresh(controller_->is_resizing());
  224. }
  225. views::View* divider_view_ = nullptr;
  226. SplitViewDividerHandlerView* divider_handler_view_ = nullptr;
  227. SplitViewController* controller_;
  228. SplitViewDivider* divider_;
  229. };
  230. } // namespace
  231. SplitViewDivider::SplitViewDivider(SplitViewController* controller)
  232. : controller_(controller) {
  233. Shell::Get()->activation_client()->AddObserver(this);
  234. CreateDividerWidget(controller);
  235. aura::Window* always_on_top_container = Shell::GetContainer(
  236. controller->root_window(), kShellWindowId_AlwaysOnTopContainer);
  237. split_view_window_targeter_ = std::make_unique<aura::ScopedWindowTargeter>(
  238. always_on_top_container, std::make_unique<AlwaysOnTopWindowTargeter>(
  239. divider_widget_->GetNativeWindow()));
  240. // Observe currently snapped windows.
  241. for (auto snap_pos : {SplitViewController::SnapPosition::LEFT,
  242. SplitViewController::SnapPosition::RIGHT}) {
  243. auto* window = controller_->GetSnappedWindow(snap_pos);
  244. if (window)
  245. AddObservedWindow(window);
  246. }
  247. }
  248. SplitViewDivider::~SplitViewDivider() {
  249. Shell::Get()->activation_client()->RemoveObserver(this);
  250. divider_widget_->Close();
  251. split_view_window_targeter_.reset();
  252. for (auto* window : observed_windows_) {
  253. window->RemoveObserver(this);
  254. ::wm::TransientWindowManager::GetOrCreate(window)->RemoveObserver(this);
  255. }
  256. observed_windows_.clear();
  257. }
  258. // static
  259. gfx::Rect SplitViewDivider::GetDividerBoundsInScreen(
  260. const gfx::Rect& work_area_bounds_in_screen,
  261. bool landscape,
  262. int divider_position,
  263. bool is_dragging) {
  264. const int dragging_diff = (kSplitviewDividerEnlargedShortSideLength -
  265. kSplitviewDividerShortSideLength) /
  266. 2;
  267. if (landscape) {
  268. return is_dragging
  269. ? gfx::Rect(work_area_bounds_in_screen.x() + divider_position -
  270. dragging_diff,
  271. work_area_bounds_in_screen.y(),
  272. kSplitviewDividerEnlargedShortSideLength,
  273. work_area_bounds_in_screen.height())
  274. : gfx::Rect(work_area_bounds_in_screen.x() + divider_position,
  275. work_area_bounds_in_screen.y(),
  276. kSplitviewDividerShortSideLength,
  277. work_area_bounds_in_screen.height());
  278. } else {
  279. return is_dragging
  280. ? gfx::Rect(work_area_bounds_in_screen.x(),
  281. work_area_bounds_in_screen.y() + divider_position -
  282. dragging_diff,
  283. work_area_bounds_in_screen.width(),
  284. kSplitviewDividerEnlargedShortSideLength)
  285. : gfx::Rect(work_area_bounds_in_screen.x(),
  286. work_area_bounds_in_screen.y() + divider_position,
  287. work_area_bounds_in_screen.width(),
  288. kSplitviewDividerShortSideLength);
  289. }
  290. }
  291. void SplitViewDivider::DoSpawningAnimation(int spawning_position) {
  292. static_cast<DividerView*>(divider_widget_->GetContentsView())
  293. ->DoSpawningAnimation(spawning_position);
  294. }
  295. void SplitViewDivider::UpdateDividerBounds() {
  296. divider_widget_->SetBounds(GetDividerBoundsInScreen(/*is_dragging=*/false));
  297. }
  298. gfx::Rect SplitViewDivider::GetDividerBoundsInScreen(bool is_dragging) {
  299. const gfx::Rect work_area_bounds_in_screen =
  300. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  301. controller_->root_window()->GetChildById(
  302. desks_util::GetActiveDeskContainerId()));
  303. const int divider_position = controller_->divider_position();
  304. const bool landscape = IsCurrentScreenOrientationLandscape();
  305. return GetDividerBoundsInScreen(work_area_bounds_in_screen, landscape,
  306. divider_position, is_dragging);
  307. }
  308. void SplitViewDivider::SetAlwaysOnTop(bool on_top) {
  309. if (on_top) {
  310. divider_widget_->SetZOrderLevel(ui::ZOrderLevel::kFloatingUIElement);
  311. // Special handling when put divider into always_on_top container. We want
  312. // to put it at the bottom so it won't block other always_on_top windows.
  313. aura::Window* always_on_top_container =
  314. Shell::GetContainer(divider_widget_->GetNativeWindow()->GetRootWindow(),
  315. kShellWindowId_AlwaysOnTopContainer);
  316. always_on_top_container->StackChildAtBottom(
  317. divider_widget_->GetNativeWindow());
  318. } else {
  319. divider_widget_->SetZOrderLevel(ui::ZOrderLevel::kNormal);
  320. }
  321. }
  322. void SplitViewDivider::SetAdjustable(bool adjustable) {
  323. if (adjustable == IsAdjustable())
  324. return;
  325. divider_widget_->GetNativeWindow()->SetEventTargetingPolicy(
  326. adjustable ? aura::EventTargetingPolicy::kTargetAndDescendants
  327. : aura::EventTargetingPolicy::kNone);
  328. static_cast<DividerView*>(divider_view_)->SetDividerBarVisible(adjustable);
  329. }
  330. bool SplitViewDivider::IsAdjustable() const {
  331. return divider_widget_->GetNativeWindow()->event_targeting_policy() !=
  332. aura::EventTargetingPolicy::kNone;
  333. }
  334. void SplitViewDivider::AddObservedWindow(aura::Window* window) {
  335. if (!base::Contains(observed_windows_, window)) {
  336. window->AddObserver(this);
  337. observed_windows_.push_back(window);
  338. ::wm::TransientWindowManager* transient_manager =
  339. ::wm::TransientWindowManager::GetOrCreate(window);
  340. transient_manager->AddObserver(this);
  341. for (auto* transient_window : transient_manager->transient_children())
  342. StartObservingTransientChild(transient_window);
  343. }
  344. }
  345. void SplitViewDivider::RemoveObservedWindow(aura::Window* window) {
  346. auto iter =
  347. std::find(observed_windows_.begin(), observed_windows_.end(), window);
  348. if (iter != observed_windows_.end()) {
  349. window->RemoveObserver(this);
  350. observed_windows_.erase(iter);
  351. ::wm::TransientWindowManager* transient_manager =
  352. ::wm::TransientWindowManager::GetOrCreate(window);
  353. transient_manager->RemoveObserver(this);
  354. for (auto* transient_window : transient_manager->transient_children())
  355. StopObservingTransientChild(transient_window);
  356. }
  357. }
  358. void SplitViewDivider::OnWindowDragStarted() {
  359. is_dragging_window_ = true;
  360. SetAlwaysOnTop(false);
  361. aura::Window* divider_window = divider_widget_->GetNativeWindow();
  362. divider_window->parent()->StackChildAtBottom(divider_window);
  363. }
  364. void SplitViewDivider::OnWindowDragEnded() {
  365. is_dragging_window_ = false;
  366. SetAlwaysOnTop(true);
  367. }
  368. void SplitViewDivider::OnWindowDestroying(aura::Window* window) {
  369. RemoveObservedWindow(window);
  370. }
  371. void SplitViewDivider::OnWindowBoundsChanged(aura::Window* window,
  372. const gfx::Rect& old_bounds,
  373. const gfx::Rect& new_bounds,
  374. ui::PropertyChangeReason reason) {
  375. if (!controller_->InSplitViewMode())
  376. return;
  377. // We only care about the bounds change of windows in
  378. // |transient_windows_observations_|.
  379. if (!transient_windows_observations_.IsObservingSource(window))
  380. return;
  381. // |window|'s transient parent must be one of the windows in
  382. // |observed_windows_|.
  383. aura::Window* transient_parent = nullptr;
  384. for (auto* observed_window : observed_windows_) {
  385. if (::wm::HasTransientAncestor(window, observed_window)) {
  386. transient_parent = observed_window;
  387. break;
  388. }
  389. }
  390. DCHECK(transient_parent);
  391. gfx::Rect transient_bounds = window->GetBoundsInScreen();
  392. transient_bounds.AdjustToFit(transient_parent->GetBoundsInScreen());
  393. window->SetBoundsInScreen(
  394. transient_bounds,
  395. display::Screen::GetScreen()->GetDisplayNearestWindow(window));
  396. }
  397. void SplitViewDivider::OnWindowActivated(ActivationReason reason,
  398. aura::Window* gained_active,
  399. aura::Window* lost_active) {
  400. if (!is_dragging_window_ &&
  401. (!gained_active || base::Contains(observed_windows_, gained_active))) {
  402. SetAlwaysOnTop(true);
  403. } else {
  404. // If |gained_active| is not one of the observed windows, or there is one
  405. // window that is currently being dragged, |divider_widget_| should not
  406. // be placed on top.
  407. SetAlwaysOnTop(false);
  408. }
  409. }
  410. void SplitViewDivider::OnTransientChildAdded(aura::Window* window,
  411. aura::Window* transient) {
  412. StartObservingTransientChild(transient);
  413. }
  414. void SplitViewDivider::OnTransientChildRemoved(aura::Window* window,
  415. aura::Window* transient) {
  416. StopObservingTransientChild(transient);
  417. }
  418. bool SplitViewDivider::IsWindowObserved(const aura::Window* window) const {
  419. return base::Contains(observed_windows_, window);
  420. }
  421. void SplitViewDivider::CreateDividerWidget(SplitViewController* controller) {
  422. DCHECK(!divider_widget_);
  423. // Native widget owns this widget.
  424. divider_widget_ = new views::Widget;
  425. views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
  426. params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
  427. params.activatable = views::Widget::InitParams::Activatable::kNo;
  428. params.parent = Shell::GetContainer(controller->root_window(),
  429. kShellWindowId_AlwaysOnTopContainer);
  430. params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);
  431. params.name = "SplitViewDivider";
  432. divider_widget_->set_focus_on_creation(false);
  433. divider_widget_->Init(std::move(params));
  434. divider_widget_->SetVisibilityAnimationTransition(
  435. views::Widget::ANIMATE_NONE);
  436. divider_view_ = divider_widget_->SetContentsView(
  437. std::make_unique<DividerView>(controller, this));
  438. divider_widget_->SetBounds(GetDividerBoundsInScreen(false /* is_dragging */));
  439. divider_widget_->GetNativeWindow()->SetProperty(kLockedToRootKey, true);
  440. divider_widget_->Show();
  441. }
  442. void SplitViewDivider::StartObservingTransientChild(aura::Window* transient) {
  443. // For now, we only care about dialog bubbles type transient child. We may
  444. // observe other types transient child window as well if need arises in the
  445. // future.
  446. views::Widget* widget = views::Widget::GetWidgetForNativeWindow(transient);
  447. if (!widget || !widget->widget_delegate()->AsBubbleDialogDelegate())
  448. return;
  449. // At this moment, the transient window may not have the valid bounds yet.
  450. // Start observe the transient window.
  451. transient_windows_observations_.AddObservation(transient);
  452. }
  453. void SplitViewDivider::StopObservingTransientChild(aura::Window* transient) {
  454. if (transient_windows_observations_.IsObservingSource(transient))
  455. transient_windows_observations_.RemoveObservation(transient);
  456. }
  457. } // namespace ash