split_view_drag_indicators.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  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_drag_indicators.h"
  5. #include <utility>
  6. #include <vector>
  7. #include "ash/display/screen_orientation_controller.h"
  8. #include "ash/public/cpp/shell_window_ids.h"
  9. #include "ash/public/cpp/window_animation_types.h"
  10. #include "ash/screen_util.h"
  11. #include "ash/shelf/shelf.h"
  12. #include "ash/shell.h"
  13. #include "ash/strings/grit/ash_strings.h"
  14. #include "ash/style/ash_color_provider.h"
  15. #include "ash/style/default_color_constants.h"
  16. #include "ash/style/default_colors.h"
  17. #include "ash/utility/haptics_util.h"
  18. #include "ash/wm/overview/overview_controller.h"
  19. #include "ash/wm/overview/overview_window_drag_controller.h"
  20. #include "ash/wm/splitview/split_view_constants.h"
  21. #include "ash/wm/splitview/split_view_highlight_view.h"
  22. #include "ash/wm/splitview/split_view_utils.h"
  23. #include "ash/wm/window_animations.h"
  24. #include "ash/wm/window_util.h"
  25. #include "base/i18n/rtl.h"
  26. #include "base/strings/utf_string_conversions.h"
  27. #include "ui/aura/window.h"
  28. #include "ui/aura/window_observer.h"
  29. #include "ui/base/l10n/l10n_util.h"
  30. #include "ui/compositor/layer.h"
  31. #include "ui/compositor/layer_type.h"
  32. #include "ui/compositor/scoped_layer_animation_settings.h"
  33. #include "ui/display/display_observer.h"
  34. #include "ui/events/devices/haptic_touchpad_effects.h"
  35. #include "ui/views/background.h"
  36. #include "ui/views/controls/label.h"
  37. #include "ui/views/highlight_border.h"
  38. #include "ui/views/layout/box_layout.h"
  39. #include "ui/views/view.h"
  40. #include "ui/views/widget/widget.h"
  41. #include "ui/wm/core/coordinate_conversion.h"
  42. #include "ui/wm/core/window_animations.h"
  43. namespace ash {
  44. namespace {
  45. // When a preview is shown, the opposite highlight shall contract to this ratio
  46. // of the screen length.
  47. constexpr float kOtherHighlightScreenPrimaryAxisRatio = 0.03f;
  48. // Creates the widget responsible for displaying the indicators.
  49. std::unique_ptr<views::Widget> CreateWidget(aura::Window* root_window) {
  50. auto widget = std::make_unique<views::Widget>();
  51. views::Widget::InitParams params;
  52. params.type = views::Widget::InitParams::TYPE_POPUP;
  53. params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
  54. params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  55. params.accept_events = false;
  56. params.layer_type = ui::LAYER_NOT_DRAWN;
  57. params.parent =
  58. Shell::GetContainer(root_window, kShellWindowId_OverlayContainer);
  59. widget->set_focus_on_creation(false);
  60. widget->Init(std::move(params));
  61. return widget;
  62. }
  63. // Computes the transform which rotates the labels |angle| degrees. The point
  64. // of rotation is the relative center point of |bounds|.
  65. gfx::Transform ComputeRotateAroundCenterTransform(const gfx::Rect& bounds,
  66. double angle) {
  67. gfx::Transform transform;
  68. const gfx::Vector2dF center_point_vector =
  69. bounds.CenterPoint() - bounds.origin();
  70. transform.Translate(center_point_vector);
  71. transform.Rotate(angle);
  72. transform.Translate(-center_point_vector);
  73. return transform;
  74. }
  75. // Returns the work area bounds that has no overlap with shelf.
  76. gfx::Rect GetWorkAreaBoundsNoOverlapWithShelf(aura::Window* root_window) {
  77. aura::Window* window =
  78. root_window->GetChildById(kShellWindowId_OverlayContainer);
  79. gfx::Rect bounds = screen_util::GetDisplayWorkAreaBoundsInParent(window);
  80. ::wm::ConvertRectToScreen(root_window, &bounds);
  81. bounds.Subtract(Shelf::ForWindow(root_window)->GetIdealBounds());
  82. return bounds;
  83. }
  84. } // namespace
  85. // static
  86. SplitViewController::SnapPosition SplitViewDragIndicators::GetSnapPosition(
  87. WindowDraggingState window_dragging_state) {
  88. switch (window_dragging_state) {
  89. case WindowDraggingState::kToSnapLeft:
  90. return SplitViewController::LEFT;
  91. case WindowDraggingState::kToSnapRight:
  92. return SplitViewController::RIGHT;
  93. default:
  94. return SplitViewController::NONE;
  95. }
  96. }
  97. // static
  98. SplitViewDragIndicators::WindowDraggingState
  99. SplitViewDragIndicators::ComputeWindowDraggingState(
  100. bool is_dragging,
  101. WindowDraggingState non_snap_state,
  102. SplitViewController::SnapPosition snap_position) {
  103. if (!is_dragging || !ShouldAllowSplitView())
  104. return WindowDraggingState::kNoDrag;
  105. switch (snap_position) {
  106. case SplitViewController::NONE:
  107. return non_snap_state;
  108. case SplitViewController::LEFT:
  109. return WindowDraggingState::kToSnapLeft;
  110. case SplitViewController::RIGHT:
  111. return WindowDraggingState::kToSnapRight;
  112. }
  113. }
  114. // View which contains a label and can be rotated. Used by and rotated by
  115. // SplitViewDragIndicatorsView.
  116. class SplitViewDragIndicators::RotatedImageLabelView : public views::View {
  117. public:
  118. explicit RotatedImageLabelView(bool is_right_or_bottom)
  119. : is_right_or_bottom_(is_right_or_bottom) {
  120. // TODO(sammiequon): Remove this extra intermediate layer.
  121. SetPaintToLayer(ui::LAYER_NOT_DRAWN);
  122. layer()->SetFillsBoundsOpaquely(false);
  123. // Use |label_parent_| to add padding and rounded edges to the text. Create
  124. // this extra view so that we can rotate the label, while having a slide
  125. // animation at times on the whole thing.
  126. label_parent_ = AddChildView(std::make_unique<views::View>());
  127. label_parent_->SetPaintToLayer(ui::LAYER_TEXTURED);
  128. label_parent_->layer()->SetFillsBoundsOpaquely(false);
  129. label_parent_->SetLayoutManager(std::make_unique<views::BoxLayout>(
  130. views::BoxLayout::Orientation::kVertical,
  131. gfx::Insets::VH(kSplitviewLabelVerticalInsetDp,
  132. kSplitviewLabelHorizontalInsetDp)));
  133. label_ = label_parent_->AddChildView(std::make_unique<views::Label>(
  134. std::u16string(), views::style::CONTEXT_LABEL));
  135. label_->SetFontList(views::Label::GetDefaultFontList().Derive(
  136. 2, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
  137. }
  138. RotatedImageLabelView(const RotatedImageLabelView&) = delete;
  139. RotatedImageLabelView& operator=(const RotatedImageLabelView&) = delete;
  140. ~RotatedImageLabelView() override = default;
  141. void SetLabelText(const std::u16string& text) { label_->SetText(text); }
  142. // Called when the view's bounds are altered. Rotates the view by |angle|
  143. // degrees.
  144. void OnBoundsUpdated(const gfx::Rect& bounds, double angle) {
  145. SetBoundsRect(bounds);
  146. label_parent_->SetBoundsRect(gfx::Rect(bounds.size()));
  147. label_parent_->SetTransform(
  148. ComputeRotateAroundCenterTransform(bounds, angle));
  149. }
  150. // Called to update the opacity of the labels view on |indicator_state|.
  151. void OnWindowDraggingStateChanged(
  152. WindowDraggingState window_dragging_state,
  153. WindowDraggingState previous_window_dragging_state,
  154. bool can_dragged_window_be_snapped) {
  155. // No top label for dragging from the top in portrait orientation.
  156. if (window_dragging_state == WindowDraggingState::kFromTop &&
  157. !IsCurrentScreenOrientationLandscape() && !is_right_or_bottom_) {
  158. return;
  159. }
  160. // If there is no drag currently in this display, any label that is showing
  161. // shall fade out with the corresponding indicator.
  162. if (window_dragging_state == WindowDraggingState::kNoDrag ||
  163. window_dragging_state == WindowDraggingState::kOtherDisplay) {
  164. DoSplitviewOpacityAnimation(
  165. layer(), SPLITVIEW_ANIMATION_TEXT_FADE_OUT_WITH_HIGHLIGHT);
  166. return;
  167. }
  168. // When a snap preview is shown, any label that is showing shall fade out.
  169. if (GetSnapPosition(window_dragging_state) != SplitViewController::NONE) {
  170. DoSplitviewOpacityAnimation(layer(), SPLITVIEW_ANIMATION_TEXT_FADE_OUT);
  171. return;
  172. }
  173. // Set the text according to |can_dragged_window_be_snapped|.
  174. SetLabelText(l10n_util::GetStringUTF16(
  175. can_dragged_window_be_snapped ? IDS_ASH_SPLIT_VIEW_GUIDANCE
  176. : IDS_ASH_SPLIT_VIEW_CANNOT_SNAP));
  177. // When dragging begins in this display or comes in from another display, if
  178. // there is now no snap preview, fade in with an indicator.
  179. if (previous_window_dragging_state == WindowDraggingState::kNoDrag ||
  180. previous_window_dragging_state == WindowDraggingState::kOtherDisplay) {
  181. DoSplitviewOpacityAnimation(
  182. layer(), SPLITVIEW_ANIMATION_TEXT_FADE_IN_WITH_HIGHLIGHT);
  183. return;
  184. }
  185. // If a snap preview was shown, the labels shall now fade in.
  186. if (GetSnapPosition(previous_window_dragging_state) !=
  187. SplitViewController::NONE) {
  188. DoSplitviewOpacityAnimation(layer(), SPLITVIEW_ANIMATION_TEXT_FADE_IN);
  189. return;
  190. }
  191. }
  192. // views:View:
  193. void OnThemeChanged() override {
  194. views::View::OnThemeChanged();
  195. label_parent_->SetBackground(views::CreateRoundedRectBackground(
  196. AshColorProvider::Get()->GetBaseLayerColor(
  197. AshColorProvider::BaseLayerType::kTransparent80),
  198. kSplitviewLabelRoundRectRadiusDp));
  199. // TODO(crbug/1258983): Add blur background. This requires fixing a bug
  200. // that `SetRoundedCornerRadius()` does not work with transform or find a
  201. // solution to work around.
  202. label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
  203. AshColorProvider::ContentLayerType::kTextColorPrimary));
  204. label_->SetBackgroundColor(AshColorProvider::Get()->GetBaseLayerColor(
  205. AshColorProvider::BaseLayerType::kTransparent80));
  206. label_->SetFontList(views::Label::GetDefaultFontList().Derive(
  207. 2, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
  208. if (chromeos::features::IsDarkLightModeEnabled()) {
  209. label_parent_->SetBorder(std::make_unique<views::HighlightBorder>(
  210. /*corner_radius=*/kSplitviewLabelRoundRectRadiusDp,
  211. views::HighlightBorder::Type::kHighlightBorder1,
  212. /*use_light_colors=*/false));
  213. }
  214. }
  215. protected:
  216. gfx::Size CalculatePreferredSize() const override {
  217. return label_parent_->GetPreferredSize();
  218. }
  219. private:
  220. // True if the label view is the right/bottom side one, false if it is the
  221. // left/top one.
  222. const bool is_right_or_bottom_;
  223. views::View* label_parent_ = nullptr;
  224. views::Label* label_ = nullptr;
  225. };
  226. // View which contains two highlights on each side indicator where a user should
  227. // drag a selected window in order to initiate splitview. Each highlight has a
  228. // label with instructions to further guide users. The highlights are on the
  229. // left and right of the display in landscape mode, and on the top and bottom of
  230. // the display in landscape mode. The highlights can expand and shrink if a
  231. // window has entered a snap region to display the bounds of the window, if it
  232. // were to get snapped.
  233. class SplitViewDragIndicators::SplitViewDragIndicatorsView
  234. : public views::View,
  235. public aura::WindowObserver {
  236. public:
  237. SplitViewDragIndicatorsView() {
  238. left_highlight_view_ = AddChildView(
  239. std::make_unique<SplitViewHighlightView>(/*is_right_or_bottom=*/false));
  240. right_highlight_view_ = AddChildView(
  241. std::make_unique<SplitViewHighlightView>(/*is_right_or_bottom=*/true));
  242. left_rotated_view_ = AddChildView(
  243. std::make_unique<RotatedImageLabelView>(/*is_right_or_bottom=*/false));
  244. right_rotated_view_ = AddChildView(
  245. std::make_unique<RotatedImageLabelView>(/*is_right_or_bottom=*/true));
  246. // Nothing is shown initially.
  247. left_highlight_view_->layer()->SetOpacity(0.f);
  248. right_highlight_view_->layer()->SetOpacity(0.f);
  249. left_rotated_view_->layer()->SetOpacity(0.f);
  250. right_rotated_view_->layer()->SetOpacity(0.f);
  251. }
  252. SplitViewDragIndicatorsView(const SplitViewDragIndicatorsView&) = delete;
  253. SplitViewDragIndicatorsView& operator=(const SplitViewDragIndicatorsView&) =
  254. delete;
  255. ~SplitViewDragIndicatorsView() override {
  256. if (dragged_window_)
  257. dragged_window_->RemoveObserver(this);
  258. }
  259. SplitViewHighlightView* left_highlight_view() { return left_highlight_view_; }
  260. // Called by parent widget when the state machine changes. Handles setting the
  261. // opacity and bounds of the highlights and labels.
  262. void OnWindowDraggingStateChanged(WindowDraggingState window_dragging_state) {
  263. DCHECK_NE(window_dragging_state_, window_dragging_state);
  264. previous_window_dragging_state_ = window_dragging_state_;
  265. window_dragging_state_ = window_dragging_state;
  266. SplitViewController* split_view_controller =
  267. SplitViewController::Get(GetWidget()->GetNativeWindow());
  268. const bool previews_only =
  269. window_dragging_state == WindowDraggingState::kFromShelf ||
  270. window_dragging_state == WindowDraggingState::kFromTop ||
  271. split_view_controller->InSplitViewMode();
  272. const bool can_dragged_window_be_snapped =
  273. dragged_window_ &&
  274. split_view_controller->CanSnapWindow(dragged_window_);
  275. if (!previews_only) {
  276. left_rotated_view_->OnWindowDraggingStateChanged(
  277. window_dragging_state, previous_window_dragging_state_,
  278. can_dragged_window_be_snapped);
  279. right_rotated_view_->OnWindowDraggingStateChanged(
  280. window_dragging_state, previous_window_dragging_state_,
  281. can_dragged_window_be_snapped);
  282. }
  283. left_highlight_view_->OnWindowDraggingStateChanged(
  284. window_dragging_state, previous_window_dragging_state_, previews_only,
  285. can_dragged_window_be_snapped);
  286. right_highlight_view_->OnWindowDraggingStateChanged(
  287. window_dragging_state, previous_window_dragging_state_, previews_only,
  288. can_dragged_window_be_snapped);
  289. if (window_dragging_state != WindowDraggingState::kNoDrag ||
  290. GetSnapPosition(previous_window_dragging_state_) !=
  291. SplitViewController::NONE) {
  292. Layout(previous_window_dragging_state_ != WindowDraggingState::kNoDrag);
  293. }
  294. }
  295. views::View* GetViewForIndicatorType(IndicatorType type) {
  296. switch (type) {
  297. case IndicatorType::kLeftHighlight:
  298. return left_highlight_view_;
  299. case IndicatorType::kLeftText:
  300. return left_rotated_view_;
  301. case IndicatorType::kRightHighlight:
  302. return right_highlight_view_;
  303. case IndicatorType::kRightText:
  304. return right_rotated_view_;
  305. }
  306. NOTREACHED();
  307. return nullptr;
  308. }
  309. void SetDraggedWindow(aura::Window* dragged_window) {
  310. if (dragged_window_)
  311. dragged_window_->RemoveObserver(this);
  312. dragged_window_ = dragged_window;
  313. if (dragged_window)
  314. dragged_window->AddObserver(this);
  315. }
  316. // views::View:
  317. void Layout() override { Layout(/*animate=*/false); }
  318. // aura::WindowObserver:
  319. void OnWindowDestroyed(aura::Window* window) override {
  320. DCHECK_EQ(dragged_window_, window);
  321. dragged_window_ = nullptr;
  322. }
  323. private:
  324. // Layout the bounds of the highlight views and helper labels. One should
  325. // animate when changing states, but not when bounds or orientation is
  326. // changed.
  327. void Layout(bool animate) {
  328. // TODO(xdai|afakhry): Attempt to simplify this logic.
  329. const bool horizontal =
  330. SplitViewController::IsLayoutHorizontal(GetWidget()->GetNativeWindow());
  331. const int display_width = horizontal ? width() : height();
  332. const int display_height = horizontal ? height() : width();
  333. // Calculate the bounds of the two highlight regions.
  334. const int highlight_width =
  335. display_width * kHighlightScreenPrimaryAxisRatio;
  336. const int highlight_height =
  337. display_height - 2 * kHighlightScreenEdgePaddingDp;
  338. gfx::Size highlight_size(highlight_width, highlight_height);
  339. // When one highlight expands to become a preview area, the other highlight
  340. // contracts to this width.
  341. const int other_highlight_width =
  342. display_width * kOtherHighlightScreenPrimaryAxisRatio;
  343. // The origin of the right highlight view in horizontal split view layout,
  344. // or the bottom highlight view in vertical split view layout.
  345. gfx::Point right_bottom_origin(
  346. display_width - highlight_width - kHighlightScreenEdgePaddingDp,
  347. kHighlightScreenEdgePaddingDp);
  348. const gfx::Point highlight_padding_point(kHighlightScreenEdgePaddingDp,
  349. kHighlightScreenEdgePaddingDp);
  350. gfx::Rect left_highlight_bounds(highlight_padding_point, highlight_size);
  351. gfx::Rect right_highlight_bounds(right_bottom_origin, highlight_size);
  352. if (!horizontal) {
  353. left_highlight_bounds.Transpose();
  354. right_highlight_bounds.Transpose();
  355. }
  356. // True when the drag ends in a snap area, meaning that the dragged window
  357. // actually becomes snapped.
  358. const bool drag_ending_in_snap =
  359. window_dragging_state_ == WindowDraggingState::kNoDrag &&
  360. GetSnapPosition(previous_window_dragging_state_) !=
  361. SplitViewController::NONE;
  362. SplitViewController::SnapPosition snap_position =
  363. GetSnapPosition(window_dragging_state_);
  364. if (snap_position == SplitViewController::NONE)
  365. snap_position = GetSnapPosition(previous_window_dragging_state_);
  366. gfx::Rect preview_area_bounds;
  367. absl::optional<SplitviewAnimationType> left_highlight_animation_type;
  368. absl::optional<SplitviewAnimationType> right_highlight_animation_type;
  369. if (GetSnapPosition(window_dragging_state_) != SplitViewController::NONE ||
  370. drag_ending_in_snap) {
  371. // Get the preview area bounds from the split view controller.
  372. preview_area_bounds =
  373. SplitViewController::Get(GetWidget()->GetNativeWindow())
  374. ->GetSnappedWindowBoundsInScreen(snap_position, dragged_window_);
  375. aura::Window* root_window =
  376. GetWidget()->GetNativeWindow()->GetRootWindow();
  377. wm::ConvertRectFromScreen(root_window, &preview_area_bounds);
  378. // Preview area should have no overlap with the shelf.
  379. preview_area_bounds.Subtract(
  380. Shelf::ForWindow(root_window)->GetIdealBounds());
  381. gfx::Rect work_area_bounds =
  382. GetWorkAreaBoundsNoOverlapWithShelf(root_window);
  383. wm::ConvertRectFromScreen(root_window, &work_area_bounds);
  384. preview_area_bounds.set_y(preview_area_bounds.y() - work_area_bounds.y());
  385. if (!drag_ending_in_snap)
  386. preview_area_bounds.Inset(kHighlightScreenEdgePaddingDp);
  387. // Calculate the bounds of the other highlight, which is the one that
  388. // shrinks and fades away, while the other one, the preview area, expands
  389. // and takes up half the screen.
  390. gfx::Rect other_bounds(
  391. display_width - other_highlight_width - kHighlightScreenEdgePaddingDp,
  392. kHighlightScreenEdgePaddingDp, other_highlight_width,
  393. display_height - 2 * kHighlightScreenEdgePaddingDp);
  394. if (!horizontal)
  395. other_bounds.Transpose();
  396. if (SplitViewController::IsPhysicalLeftOrTop(snap_position,
  397. dragged_window_)) {
  398. left_highlight_bounds = preview_area_bounds;
  399. right_highlight_bounds = other_bounds;
  400. if (animate) {
  401. if (drag_ending_in_snap) {
  402. left_highlight_animation_type =
  403. SPLITVIEW_ANIMATION_PREVIEW_AREA_NIX_INSET;
  404. } else {
  405. left_highlight_animation_type =
  406. SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN;
  407. right_highlight_animation_type =
  408. SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_OUT;
  409. }
  410. }
  411. } else {
  412. other_bounds.set_origin(highlight_padding_point);
  413. left_highlight_bounds = other_bounds;
  414. right_highlight_bounds = preview_area_bounds;
  415. if (animate) {
  416. if (drag_ending_in_snap) {
  417. right_highlight_animation_type =
  418. SPLITVIEW_ANIMATION_PREVIEW_AREA_NIX_INSET;
  419. } else {
  420. left_highlight_animation_type =
  421. SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_OUT;
  422. right_highlight_animation_type =
  423. SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN;
  424. }
  425. }
  426. }
  427. } else if (GetSnapPosition(previous_window_dragging_state_) !=
  428. SplitViewController::NONE &&
  429. animate) {
  430. if (SplitViewController::IsPhysicalLeftOrTop(snap_position,
  431. dragged_window_)) {
  432. left_highlight_animation_type =
  433. SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_OUT;
  434. right_highlight_animation_type =
  435. SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_IN;
  436. } else {
  437. left_highlight_animation_type =
  438. SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_IN;
  439. right_highlight_animation_type =
  440. SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_OUT;
  441. }
  442. }
  443. left_highlight_view_->SetBounds(GetMirroredRect(left_highlight_bounds),
  444. left_highlight_animation_type);
  445. right_highlight_view_->SetBounds(GetMirroredRect(right_highlight_bounds),
  446. right_highlight_animation_type);
  447. // Calculate the bounds of the views which contain the guidance text and
  448. // icon. Rotate the two views in horizontal split view layout.
  449. const gfx::Size size(right_rotated_view_->GetPreferredSize().width(),
  450. kSplitviewLabelPreferredHeightDp);
  451. if (!horizontal)
  452. highlight_size.SetSize(highlight_size.height(), highlight_size.width());
  453. gfx::Rect left_rotated_bounds(
  454. highlight_size.width() / 2 - size.width() / 2,
  455. highlight_size.height() / 2 - size.height() / 2, size.width(),
  456. size.height());
  457. gfx::Rect right_rotated_bounds = left_rotated_bounds;
  458. left_rotated_bounds.Offset(highlight_padding_point.x(),
  459. highlight_padding_point.y());
  460. if (!horizontal) {
  461. right_bottom_origin.SetPoint(right_bottom_origin.y(),
  462. right_bottom_origin.x());
  463. }
  464. right_rotated_bounds.Offset(right_bottom_origin.x(),
  465. right_bottom_origin.y());
  466. // In portrait mode, there is no need to rotate the text and warning icon.
  467. // In horizontal split view layout, rotate the left text 90 degrees
  468. // clockwise in rtl and 90 degress anti clockwise in ltr. The right text is
  469. // rotated 90 degrees in the opposite direction of the left text.
  470. double left_rotation_angle = 0.0;
  471. if (horizontal)
  472. left_rotation_angle = 90.0 * (base::i18n::IsRTL() ? 1 : -1);
  473. left_rotated_view_->OnBoundsUpdated(left_rotated_bounds,
  474. /*angle=*/left_rotation_angle);
  475. right_rotated_view_->OnBoundsUpdated(right_rotated_bounds,
  476. /*angle=*/-left_rotation_angle);
  477. if (drag_ending_in_snap) {
  478. // Reset the label transforms, in preparation for the next drag (if any).
  479. left_rotated_view_->layer()->SetTransform(gfx::Transform());
  480. right_rotated_view_->layer()->SetTransform(gfx::Transform());
  481. return;
  482. }
  483. ui::Layer* preview_label_layer;
  484. ui::Layer* other_highlight_label_layer;
  485. if (snap_position == SplitViewController::NONE) {
  486. preview_label_layer = nullptr;
  487. other_highlight_label_layer = nullptr;
  488. } else if (SplitViewController::IsPhysicalLeftOrTop(snap_position,
  489. dragged_window_)) {
  490. preview_label_layer = left_rotated_view_->layer();
  491. other_highlight_label_layer = right_rotated_view_->layer();
  492. } else {
  493. preview_label_layer = right_rotated_view_->layer();
  494. other_highlight_label_layer = left_rotated_view_->layer();
  495. }
  496. // Slide out the labels when a snap preview appears. This code also adjusts
  497. // the label transforms for things like display rotation while there is a
  498. // snap preview.
  499. if (GetSnapPosition(window_dragging_state_) != SplitViewController::NONE) {
  500. // How far each label shall slide to stay centered in the corresponding
  501. // highlight as it expands/contracts. Include distance traveled with zero
  502. // opacity (whence a label still slides, not only for simplicity in
  503. // calculating the values below, but also to facilitate that the label
  504. // transform and the highlight transform have matching easing).
  505. const float preview_label_distance =
  506. 0.5f * (preview_area_bounds.width() - highlight_width);
  507. const float other_highlight_label_distance =
  508. 0.5f * (highlight_width - other_highlight_width);
  509. // Positive for right or down; negative for left or up.
  510. float preview_label_delta, other_highlight_label_delta;
  511. if (SplitViewController::IsPhysicalLeftOrTop(snap_position,
  512. dragged_window_)) {
  513. preview_label_delta = preview_label_distance;
  514. other_highlight_label_delta = other_highlight_label_distance;
  515. } else {
  516. preview_label_delta = -preview_label_distance;
  517. other_highlight_label_delta = -other_highlight_label_distance;
  518. }
  519. // x-axis if |horizontal|; else y-axis.
  520. gfx::Transform preview_label_transform, other_highlight_label_transform;
  521. if (horizontal) {
  522. preview_label_transform.Translate(preview_label_delta, 0.f);
  523. other_highlight_label_transform.Translate(other_highlight_label_delta,
  524. 0.f);
  525. } else {
  526. preview_label_transform.Translate(0.f, preview_label_delta);
  527. other_highlight_label_transform.Translate(0.f,
  528. other_highlight_label_delta);
  529. }
  530. if (animate) {
  531. // Animate the labels sliding out.
  532. DoSplitviewTransformAnimation(
  533. preview_label_layer,
  534. SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_OUT,
  535. preview_label_transform, /*animation_observers=*/{});
  536. DoSplitviewTransformAnimation(
  537. other_highlight_label_layer,
  538. SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_OUT,
  539. other_highlight_label_transform, /*animation_observers=*/{});
  540. } else {
  541. // Put the labels where they belong.
  542. preview_label_layer->SetTransform(preview_label_transform);
  543. other_highlight_label_layer->SetTransform(
  544. other_highlight_label_transform);
  545. }
  546. return;
  547. }
  548. // Slide in the labels when a snap preview disappears because you drag
  549. // inward. (Having reached this code, we know that the window is not
  550. // becoming snapped, because that case is handled earlier and we bail out.)
  551. if (GetSnapPosition(previous_window_dragging_state_) !=
  552. SplitViewController::NONE) {
  553. if (animate) {
  554. // Animate the labels sliding in.
  555. DoSplitviewTransformAnimation(
  556. preview_label_layer, SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_IN,
  557. gfx::Transform(), /*animation_observers=*/{});
  558. DoSplitviewTransformAnimation(
  559. other_highlight_label_layer,
  560. SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_IN, gfx::Transform(),
  561. /*animation_observers=*/{});
  562. } else {
  563. // Put the labels where they belong.
  564. preview_label_layer->SetTransform(gfx::Transform());
  565. other_highlight_label_layer->SetTransform(gfx::Transform());
  566. }
  567. return;
  568. }
  569. }
  570. SplitViewHighlightView* left_highlight_view_ = nullptr;
  571. SplitViewHighlightView* right_highlight_view_ = nullptr;
  572. RotatedImageLabelView* left_rotated_view_ = nullptr;
  573. RotatedImageLabelView* right_rotated_view_ = nullptr;
  574. WindowDraggingState window_dragging_state_ = WindowDraggingState::kNoDrag;
  575. WindowDraggingState previous_window_dragging_state_ =
  576. WindowDraggingState::kNoDrag;
  577. aura::Window* dragged_window_ = nullptr;
  578. };
  579. SplitViewDragIndicators::SplitViewDragIndicators(aura::Window* root_window) {
  580. widget_ = CreateWidget(root_window);
  581. widget_->SetBounds(GetWorkAreaBoundsNoOverlapWithShelf(root_window));
  582. indicators_view_ =
  583. widget_->SetContentsView(std::make_unique<SplitViewDragIndicatorsView>());
  584. widget_->Show();
  585. }
  586. SplitViewDragIndicators::~SplitViewDragIndicators() {
  587. // Allow some extra time for animations to finish.
  588. aura::Window* window = widget_->GetNativeWindow();
  589. if (window == nullptr)
  590. return;
  591. wm::SetWindowVisibilityAnimationType(
  592. window, WINDOW_VISIBILITY_ANIMATION_TYPE_STEP_END);
  593. AnimateOnChildWindowVisibilityChanged(window, /*visible=*/false);
  594. }
  595. void SplitViewDragIndicators::SetDraggedWindow(aura::Window* dragged_window) {
  596. DCHECK_EQ(WindowDraggingState::kNoDrag, current_window_dragging_state_);
  597. indicators_view_->SetDraggedWindow(dragged_window);
  598. }
  599. void SplitViewDragIndicators::SetWindowDraggingState(
  600. WindowDraggingState window_dragging_state) {
  601. if (window_dragging_state == current_window_dragging_state_)
  602. return;
  603. // Fire a haptic event if necessary.
  604. if (GetSnapPosition(window_dragging_state) != SplitViewController::NONE) {
  605. OverviewController* overview_controller =
  606. Shell::Get()->overview_controller();
  607. if (overview_controller->InOverviewSession() &&
  608. overview_controller->overview_session()->window_drag_controller() &&
  609. !overview_controller->overview_session()
  610. ->window_drag_controller()
  611. ->is_touch_dragging()) {
  612. haptics_util::PlayHapticTouchpadEffect(
  613. ui::HapticTouchpadEffect::kSnap,
  614. ui::HapticTouchpadEffectStrength::kMedium);
  615. }
  616. }
  617. current_window_dragging_state_ = window_dragging_state;
  618. indicators_view_->OnWindowDraggingStateChanged(window_dragging_state);
  619. }
  620. void SplitViewDragIndicators::OnDisplayBoundsChanged() {
  621. aura::Window* root_window = widget_->GetNativeView()->GetRootWindow();
  622. widget_->SetBounds(GetWorkAreaBoundsNoOverlapWithShelf(root_window));
  623. }
  624. bool SplitViewDragIndicators::GetIndicatorTypeVisibilityForTesting(
  625. IndicatorType type) const {
  626. return indicators_view_->GetViewForIndicatorType(type)->layer()->opacity() >
  627. 0.f;
  628. }
  629. gfx::Rect SplitViewDragIndicators::GetLeftHighlightViewBounds() const {
  630. return indicators_view_->left_highlight_view()->bounds();
  631. }
  632. } // namespace ash