Commit 57dccc17 authored by Avery Musbach's avatar Avery Musbach Committed by Commit Bot

split view: Implement the divider spawning animation

The present CL is for the animation of the divider and its white handle,
when split view is activated by dragging a window, if the window is not
minimized and the dragging is not tab dragging. The animation is based
on:
https://mccanny.users.x20web.corp.google.com/www/splitscreen-motion/index.html#window-drop

The present CL deviates from the specification in an edge case as shown
in the video linked in Comment 14 here:
https://bugs.chromium.org/p/chromium/issues/detail?id=934977#c14

See how the divider moves toward the side of the screen where the window
is snapped, whereas the mockup shows the divider moving away from that
side of the screen. The specification says that the white handle shall
spawn from "2dp off centerline of divider (in direction of drop)," which
in this case is 2dp to the left of where the divider spawns from. Then
the divider would pass through the point where the handle spawns.
Because that seems wrong, where the specification says "in direction of
drop," I instead use the direction from which the divider spawns, as you
see in the video.

chrome://flags/#ui-slow-animations affects the divider but not the white
handle, because the white handle uses SlideAnimation. The flag therefore
desynchronizes the spawning animations, but fortunately, the video
referenced above opens in a player where playback speed can be adjusted.

Test: manual
Bug: 934977
Change-Id: Ide325c36c939fbf4af07b878811207c79afc206c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1601368Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarXiaoqian Dai <xdai@chromium.org>
Commit-Queue: Avery Musbach <amusbach@chromium.org>
Cr-Commit-Position: refs/heads/master@{#659279}
parent 3e9d5d0c
...@@ -1174,6 +1174,8 @@ component("ash") { ...@@ -1174,6 +1174,8 @@ component("ash") {
"wm/splitview/split_view_controller.cc", "wm/splitview/split_view_controller.cc",
"wm/splitview/split_view_divider.cc", "wm/splitview/split_view_divider.cc",
"wm/splitview/split_view_divider.h", "wm/splitview/split_view_divider.h",
"wm/splitview/split_view_divider_handler_view.cc",
"wm/splitview/split_view_divider_handler_view.h",
"wm/splitview/split_view_drag_indicators.cc", "wm/splitview/split_view_drag_indicators.cc",
"wm/splitview/split_view_drag_indicators.h", "wm/splitview/split_view_drag_indicators.h",
"wm/splitview/split_view_highlight_view.cc", "wm/splitview/split_view_highlight_view.cc",
......
...@@ -482,7 +482,8 @@ void OverviewWindowDragController::SnapWindow( ...@@ -482,7 +482,8 @@ void OverviewWindowDragController::SnapWindow(
DCHECK_NE(snap_position, SplitViewController::NONE); DCHECK_NE(snap_position, SplitViewController::NONE);
// |item_| will be deleted after SplitViewController::SnapWindow(). // |item_| will be deleted after SplitViewController::SnapWindow().
split_view_controller_->SnapWindow(item_->GetWindow(), snap_position); split_view_controller_->SnapWindow(item_->GetWindow(), snap_position,
/*use_divider_spawn_animation=*/true);
item_ = nullptr; item_ = nullptr;
} }
......
...@@ -35,6 +35,48 @@ constexpr SkColor kSplitviewLabelEnabledColor = SK_ColorWHITE; ...@@ -35,6 +35,48 @@ constexpr SkColor kSplitviewLabelEnabledColor = SK_ColorWHITE;
constexpr SkColor kSplitviewLabelBackgroundColor = constexpr SkColor kSplitviewLabelBackgroundColor =
SkColorSetA(SK_ColorBLACK, 0xDE); SkColorSetA(SK_ColorBLACK, 0xDE);
// The color of the divider.
constexpr SkColor kSplitviewDividerColor = SK_ColorBLACK;
// The color of the divider's handler.
constexpr SkColor kSplitviewWhiteBarColor = SK_ColorWHITE;
// The thickness of the divider when it is not being dragged.
constexpr int kSplitviewDividerShortSideLength = 8;
// The thickness of the divider during dragging.
constexpr int kSplitviewDividerEnlargedShortSideLength = 16;
// The time duration for the window transformation animations.
constexpr int kSplitviewWindowTransformMs = 250;
// The time duration for the divider animations when dragging starts and ends.
constexpr int kSplitviewDividerSelectionStatusChangeDurationMs = 250;
// The time duration for the divider spawning animation.
constexpr int kSplitviewDividerSpawnDurationMs = 100;
// The delay before the divider spawning animation.
constexpr int kSplitviewDividerSpawnDelayMs = 183;
// The thickness of the divider's handler.
constexpr int kSplitviewWhiteBarShortSideLength = 2;
// The length of the divider's handler.
constexpr int kSplitviewWhiteBarLongSideLength = 16;
// The corner radius of the divider's handler.
constexpr int kSplitviewWhiteBarCornerRadius = 1;
// The radius of the circular handler when the divider is being dragged.
constexpr int kSplitviewWhiteBarRadius = 4;
// The length of the divider's handler when it spawns.
constexpr int kSplitviewWhiteBarSpawnLongSideLength = 2;
// The distance from the divider to where its handler spawns.
constexpr int kSplitviewWhiteBarSpawnUnsignedOffset = 2;
} // namespace ash } // namespace ash
#endif // ASH_WM_SPLITVIEW_SPLIT_VIEW_CONSTANTS_H_ #endif // ASH_WM_SPLITVIEW_SPLIT_VIEW_CONSTANTS_H_
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "ash/wm/overview/overview_grid.h" #include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h" #include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_utils.h" #include "ash/wm/overview/overview_utils.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_divider.h" #include "ash/wm/splitview/split_view_divider.h"
#include "ash/wm/splitview/split_view_utils.h" #include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "ash/wm/tablet_mode/tablet_mode_controller.h"
...@@ -308,7 +309,8 @@ bool SplitViewController::InTabletSplitViewMode() const { ...@@ -308,7 +309,8 @@ bool SplitViewController::InTabletSplitViewMode() const {
} }
void SplitViewController::SnapWindow(aura::Window* window, void SplitViewController::SnapWindow(aura::Window* window,
SnapPosition snap_position) { SnapPosition snap_position,
bool use_divider_spawn_animation) {
DCHECK(window && CanSnapInSplitview(window)); DCHECK(window && CanSnapInSplitview(window));
DCHECK_NE(snap_position, NONE); DCHECK_NE(snap_position, NONE);
DCHECK(!is_resizing_); DCHECK(!is_resizing_);
...@@ -323,6 +325,7 @@ void SplitViewController::SnapWindow(aura::Window* window, ...@@ -323,6 +325,7 @@ void SplitViewController::SnapWindow(aura::Window* window,
UpdateSnappingWindowTransformedBounds(window); UpdateSnappingWindowTransformedBounds(window);
RemoveWindowFromOverviewIfApplicable(window); RemoveWindowFromOverviewIfApplicable(window);
bool do_divider_spawn_animation = false;
if (state_ == SplitViewState::kNoSnap) { if (state_ == SplitViewState::kNoSnap) {
// Add observers when the split view mode starts. // Add observers when the split view mode starts.
Shell::Get()->AddShellObserver(this); Shell::Get()->AddShellObserver(this);
...@@ -337,6 +340,20 @@ void SplitViewController::SnapWindow(aura::Window* window, ...@@ -337,6 +340,20 @@ void SplitViewController::SnapWindow(aura::Window* window,
if (split_view_type_ == SplitViewType::kTabletType) { if (split_view_type_ == SplitViewType::kTabletType) {
split_view_divider_ = split_view_divider_ =
std::make_unique<SplitViewDivider>(this, window->GetRootWindow()); std::make_unique<SplitViewDivider>(this, window->GetRootWindow());
// The divider spawn animation adds a finishing touch to the |window|
// animation that generally accommodates snapping by dragging, but if
// |window| is currently minimized then it will undergo the unminimizing
// animation instead. Therefore skip the divider spawn animation if
// |window| is minimized.
if (use_divider_spawn_animation &&
!wm::GetWindowState(window)->IsMinimized()) {
// For the divider spawn animation, at the end of the delay, the divider
// shall be visually aligned with an edge of |window|. This effect will
// be more easily achieved after |window| has been snapped and the
// corresponding transform animation has begun. So for now, just set a
// flag to indicate that the divider spawn animation should be done.
do_divider_spawn_animation = true;
}
} }
splitview_start_time_ = base::Time::Now(); splitview_start_time_ = base::Time::Now();
} }
...@@ -408,6 +425,28 @@ void SplitViewController::SnapWindow(aura::Window* window, ...@@ -408,6 +425,28 @@ void SplitViewController::SnapWindow(aura::Window* window,
wm::GetWindowState(window)->OnWMEvent(&event); wm::GetWindowState(window)->OnWMEvent(&event);
} }
if (do_divider_spawn_animation) {
DCHECK(window->layer()->GetAnimator()->GetTargetTransform().IsIdentity());
const gfx::Rect bounds =
GetSnappedWindowBoundsInScreen(window, snap_position);
// Get one of the two corners of |window| that meet the divider.
gfx::Point p = IsPhysicalLeftOrTop(snap_position) ? bounds.bottom_right()
: bounds.origin();
// Apply the transform that |window| will undergo when the divider spawns.
static const double value = gfx::Tween::CalculateValue(
gfx::Tween::FAST_OUT_SLOW_IN,
static_cast<double>(kSplitviewDividerSpawnDelayMs) /
static_cast<double>(kSplitviewWindowTransformMs));
gfx::TransformAboutPivot(bounds.origin(),
gfx::Tween::TransformValueBetween(
value, window->transform(), gfx::Transform()))
.TransformPoint(&p);
// Use a coordinate of the transformed |window| corner for spawn_position.
split_view_divider_->DoSpawningAnimation(
IsCurrentScreenOrientationLandscape() ? p.x() : p.y());
}
base::RecordAction(base::UserMetricsAction("SplitView_SnapWindow")); base::RecordAction(base::UserMetricsAction("SplitView_SnapWindow"));
} }
...@@ -1851,9 +1890,12 @@ void SplitViewController::EndWindowDragImpl( ...@@ -1851,9 +1890,12 @@ void SplitViewController::EndWindowDragImpl(
/*animate=*/true); /*animate=*/true);
} }
} else { } else {
aura::Window* initiator_window =
window->GetProperty(ash::kTabDraggingSourceWindowKey);
// Note SnapWindow() might put the previous window that was snapped at the // Note SnapWindow() might put the previous window that was snapped at the
// |desired_snap_position| in overview. // |desired_snap_position| in overview.
SnapWindow(window, desired_snap_position); SnapWindow(window, desired_snap_position,
/*use_divider_spawn_animation=*/!initiator_window);
// Reapply the bounds update because the bounds might have been // Reapply the bounds update because the bounds might have been
// modified by dragging operation. // modified by dragging operation.
// TODO(oshima): WindowState already gets notified when drag ends. Refactor // TODO(oshima): WindowState already gets notified when drag ends. Refactor
...@@ -1868,8 +1910,6 @@ void SplitViewController::EndWindowDragImpl( ...@@ -1868,8 +1910,6 @@ void SplitViewController::EndWindowDragImpl(
// If splitview mode was not active before snapping the dragged // If splitview mode was not active before snapping the dragged
// window, snap the initiator window to the other side of the screen // window, snap the initiator window to the other side of the screen
// if it's not the same window as the dragged window. // if it's not the same window as the dragged window.
aura::Window* initiator_window =
window->GetProperty(ash::kTabDraggingSourceWindowKey);
if (initiator_window && initiator_window != window) { if (initiator_window && initiator_window != window) {
SnapWindow(initiator_window, SnapWindow(initiator_window,
(desired_snap_position == SplitViewController::LEFT) (desired_snap_position == SplitViewController::LEFT)
......
...@@ -98,8 +98,14 @@ class ASH_EXPORT SplitViewController : public SplitViewNotifier, ...@@ -98,8 +98,14 @@ class ASH_EXPORT SplitViewController : public SplitViewNotifier,
// Snaps window to left/right. It will try to remove |window| from the // Snaps window to left/right. It will try to remove |window| from the
// overview window grid first before snapping it if |window| is currently // overview window grid first before snapping it if |window| is currently
// showing in the overview window grid. // showing in the overview window grid. If split view mode is not already
void SnapWindow(aura::Window* window, SnapPosition snap_position); // active, and if |window| is not minimized, |use_divider_spawn_animation|
// causes the divider to show up with an animation that adds a finishing touch
// to the snap animation of |window|. Use true when |window| is snapped by
// dragging, except for tab dragging.
void SnapWindow(aura::Window* window,
SnapPosition snap_position,
bool use_divider_spawn_animation = false);
// Swaps the left and right windows. This will do nothing if one of the // Swaps the left and right windows. This will do nothing if one of the
// windows is not snapped. // windows is not snapped.
......
This diff is collapsed.
...@@ -56,6 +56,10 @@ class ASH_EXPORT SplitViewDivider : public aura::WindowObserver, ...@@ -56,6 +56,10 @@ class ASH_EXPORT SplitViewDivider : public aura::WindowObserver,
int divider_position, int divider_position,
bool is_dragging); bool is_dragging);
// Do the divider spawning animation that adds a finishing touch to the
// snapping animation of a window.
void DoSpawningAnimation(int spawn_position);
// Updates |divider_widget_|'s bounds. // Updates |divider_widget_|'s bounds.
void UpdateDividerBounds(); void UpdateDividerBounds();
......
// Copyright 2019 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_divider_handler_view.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/shell.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "base/timer/timer.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/slide_animation.h"
namespace ash {
namespace {
constexpr base::TimeDelta kWhiteBarSpawnDelay =
base::TimeDelta::FromMilliseconds(kSplitviewDividerSpawnDelayMs);
} // namespace
class SplitViewDividerHandlerView::SelectionAnimation
: public gfx::SlideAnimation,
public gfx::AnimationDelegate {
public:
SelectionAnimation(SplitViewDividerHandlerView* white_handler_view)
: gfx::SlideAnimation(this), white_handler_view_(white_handler_view) {
SetSlideDuration(kSplitviewDividerSelectionStatusChangeDurationMs);
SetTweenType(gfx::Tween::EASE_IN);
}
~SelectionAnimation() override = default;
void UpdateWhiteHandlerBounds() {
white_handler_view_->SetBounds(
CurrentValueBetween(kSplitviewWhiteBarShortSideLength,
kSplitviewWhiteBarRadius * 2),
CurrentValueBetween(kSplitviewWhiteBarLongSideLength,
kSplitviewWhiteBarRadius * 2),
/*signed_offset=*/0);
}
private:
// gfx::AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override {
UpdateWhiteHandlerBounds();
white_handler_view_->SetCornerRadius(CurrentValueBetween(
kSplitviewWhiteBarCornerRadius, kSplitviewWhiteBarRadius));
}
SplitViewDividerHandlerView* white_handler_view_;
DISALLOW_COPY_AND_ASSIGN(SelectionAnimation);
};
class SplitViewDividerHandlerView::SpawningAnimation
: public gfx::SlideAnimation,
public gfx::AnimationDelegate {
public:
SpawningAnimation(SplitViewDividerHandlerView* white_handler_view,
int divider_signed_offset)
: gfx::SlideAnimation(this),
white_handler_view_(white_handler_view),
spawn_signed_offset_(divider_signed_offset +
(divider_signed_offset > 0
? kSplitviewWhiteBarSpawnUnsignedOffset
: -kSplitviewWhiteBarSpawnUnsignedOffset)) {
SetSlideDuration(kSplitviewDividerSpawnDurationMs);
SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
}
~SpawningAnimation() override = default;
void Activate() {
white_handler_view_->SetVisible(false);
delay_timer_.Start(FROM_HERE, kWhiteBarSpawnDelay, this,
&SpawningAnimation::StartAnimation);
}
bool IsActive() const { return delay_timer_.IsRunning() || is_animating(); }
void UpdateWhiteHandlerBounds() {
DCHECK(IsActive());
white_handler_view_->SetBounds(
kSplitviewWhiteBarShortSideLength,
CurrentValueBetween(kSplitviewWhiteBarSpawnLongSideLength,
kSplitviewWhiteBarLongSideLength),
CurrentValueBetween(spawn_signed_offset_, 0));
}
private:
void StartAnimation() {
DCHECK(!white_handler_view_->visible());
white_handler_view_->SetVisible(true);
DCHECK(!is_animating());
Show();
DCHECK_EQ(0.0, GetCurrentValue());
UpdateWhiteHandlerBounds();
}
// gfx::AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override {
UpdateWhiteHandlerBounds();
}
SplitViewDividerHandlerView* white_handler_view_;
int spawn_signed_offset_;
base::OneShotTimer delay_timer_;
DISALLOW_COPY_AND_ASSIGN(SpawningAnimation);
};
SplitViewDividerHandlerView::SplitViewDividerHandlerView()
: RoundedRectView(kSplitviewWhiteBarCornerRadius, kSplitviewWhiteBarColor),
selection_animation_(std::make_unique<SelectionAnimation>(this)) {
SetPaintToLayer();
}
SplitViewDividerHandlerView::~SplitViewDividerHandlerView() = default;
void SplitViewDividerHandlerView::DoSpawningAnimation(
int divider_signed_offset) {
spawning_animation_ =
std::make_unique<SpawningAnimation>(this, divider_signed_offset);
spawning_animation_->Activate();
}
void SplitViewDividerHandlerView::Refresh() {
spawning_animation_.reset();
SetVisible(true);
selection_animation_->UpdateWhiteHandlerBounds();
if (Shell::Get()->split_view_controller()->is_resizing())
selection_animation_->Show();
else
selection_animation_->Hide();
}
void SplitViewDividerHandlerView::SetBounds(int short_length,
int long_length,
int signed_offset) {
const bool landscape = IsCurrentScreenOrientationLandscape();
gfx::Rect bounds = landscape ? gfx::Rect(short_length, long_length)
: gfx::Rect(long_length, short_length);
bounds.Offset(parent()->GetLocalBounds().CenterPoint() -
bounds.CenterPoint() +
(landscape ? gfx::Vector2d(signed_offset, 0)
: gfx::Vector2d(0, signed_offset)));
SetBoundsRect(bounds);
}
void SplitViewDividerHandlerView::OnPaint(gfx::Canvas* canvas) {
views::View::OnPaint(canvas);
// It's needed to avoid artifacts when tapping on the divider quickly.
canvas->DrawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc);
RoundedRectView::OnPaint(canvas);
}
} // namespace ash
// Copyright 2019 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.
#ifndef ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_HANDLER_VIEW_H_
#define ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_HANDLER_VIEW_H_
#include <memory>
#include "ash/ash_export.h"
#include "ash/public/cpp/ash_constants.h"
#include "ash/wm/overview/rounded_rect_view.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "base/macros.h"
#include "ui/gfx/canvas.h"
#include "ui/views/view.h"
namespace ash {
// The white handler bar in the middle of the divider.
class ASH_EXPORT SplitViewDividerHandlerView : public RoundedRectView {
public:
SplitViewDividerHandlerView();
~SplitViewDividerHandlerView() override;
// Play the white handler's part in the divider spawn animation.
// |divider_signed_offset| represents the motion of the center of the divider
// during the spawning animation. This parameter is used to make the white
// handler move with the center of the divider, as the two views are siblings
// because if the white handler view were a child of the divider view, then
// the transform that enlarges the divider during dragging would distort the
// white handler.
void DoSpawningAnimation(int divider_signed_offset);
// If the spawning animation is running, stop it and show the white handler.
// Update bounds. Do the enlarge/shrink animation when starting/ending
// dragging.
void Refresh();
private:
class SelectionAnimation;
class SpawningAnimation;
void SetBounds(int short_length, int long_length, int signed_offset);
// views::View:
void OnPaint(gfx::Canvas* canvas) override;
// Handles the animations for starting and ending dragging.
std::unique_ptr<SelectionAnimation> selection_animation_;
// Handles the spawning animation.
std::unique_ptr<SpawningAnimation> spawning_animation_;
DISALLOW_COPY_AND_ASSIGN(SplitViewDividerHandlerView);
};
} // namespace ash
#endif // ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_HANDLER_VIEW_H_
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "ash/wm/splitview/split_view_utils.h" #include "ash/wm/splitview/split_view_utils.h"
#include "ash/accessibility/accessibility_controller.h" #include "ash/accessibility/accessibility_controller.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/public/cpp/ash_features.h" #include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/ash_switches.h" #include "ash/public/cpp/ash_switches.h"
#include "ash/screen_util.h" #include "ash/screen_util.h"
...@@ -46,7 +47,7 @@ constexpr base::TimeDelta kLabelAnimationDelay = ...@@ -46,7 +47,7 @@ constexpr base::TimeDelta kLabelAnimationDelay =
base::TimeDelta::FromMilliseconds(167); base::TimeDelta::FromMilliseconds(167);
// The time duration for the window transformation animations. // The time duration for the window transformation animations.
constexpr base::TimeDelta kWindowTransform = constexpr base::TimeDelta kWindowTransform =
base::TimeDelta::FromMilliseconds(250); base::TimeDelta::FromMilliseconds(kSplitviewWindowTransformMs);
constexpr float kHighlightOpacity = 0.3f; constexpr float kHighlightOpacity = 0.3f;
constexpr float kPreviewAreaHighlightOpacity = 0.18f; constexpr float kPreviewAreaHighlightOpacity = 0.18f;
...@@ -267,4 +268,11 @@ bool CanSnapInSplitview(aura::Window* window) { ...@@ -267,4 +268,11 @@ bool CanSnapInSplitview(aura::Window* window) {
return true; return true;
} }
bool IsPhysicalLeftOrTop(SplitViewController::SnapPosition position) {
DCHECK_NE(SplitViewController::NONE, position);
return position == (IsCurrentScreenOrientationPrimary()
? SplitViewController::LEFT
: SplitViewController::RIGHT);
}
} // namespace ash } // namespace ash
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "ash/display/screen_orientation_controller.h" #include "ash/display/screen_orientation_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
#include "ui/gfx/transform.h" #include "ui/gfx/transform.h"
...@@ -90,6 +91,8 @@ ASH_EXPORT bool ShouldAllowSplitView(); ...@@ -90,6 +91,8 @@ ASH_EXPORT bool ShouldAllowSplitView();
// tablet mode. // tablet mode.
ASH_EXPORT bool CanSnapInSplitview(aura::Window* window); ASH_EXPORT bool CanSnapInSplitview(aura::Window* window);
ASH_EXPORT bool IsPhysicalLeftOrTop(SplitViewController::SnapPosition position);
} // namespace ash } // namespace ash
#endif // ASH_WM_SPLITVIEW_SPLIT_VIEW_UTILS_H_ #endif // ASH_WM_SPLITVIEW_SPLIT_VIEW_UTILS_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment