#include "ash/wm/splitview/split_view_divider_view.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/system/screen_layout_observer.h"
#include "ash/wm/snap_group/snap_group.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/snap_group/snap_group_expanded_menu_view.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_divider.h"
#include "ash/wm/splitview/split_view_divider_handler_view.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/background.h"
#include "ui/views/highlight_border.h"
namespace ash {
namespace {
constexpr int kKebabButtonDistanceFromBottom = 24;
constexpr gfx::Size kKebabButtonSize{4, 24};
constexpr int kDistanceBetweenKebabButtonAndExpandedMenu = 8;
constexpr int kExpandedMenuHeight = 150;
bool IsInTabletMode() {
TabletModeController* tablet_mode_controller =
Shell::Get()->tablet_mode_controller();
return tablet_mode_controller && tablet_mode_controller->InTabletMode();
}
}
SplitViewDividerView::SplitViewDividerView(SplitViewController* controller,
SplitViewDivider* divider)
: split_view_controller_(controller),
divider_handler_view_(
AddChildView(std::make_unique<SplitViewDividerHandlerView>())),
divider_(divider) {
SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
SetPaintToLayer(ui::LAYER_TEXTURED);
layer()->SetFillsBoundsOpaquely(false);
const bool is_jellyroll_enabled = chromeos::features::IsJellyrollEnabled();
SetBackground(views::CreateThemedSolidBackground(
is_jellyroll_enabled
? static_cast<ui::ColorId>(cros_tokens::kCrosSysSystemBaseElevated)
: kColorAshShieldAndBaseOpaque));
SetBorder(std::make_unique<views::HighlightBorder>(
0,
is_jellyroll_enabled
? views::HighlightBorder::Type::kHighlightBorderNoShadow
: views::HighlightBorder::Type::kHighlightBorder1));
if (IsSnapGroupEnabledInClamshellMode()) {
kebab_button_ = AddChildView(std::make_unique<IconButton>(
base::BindRepeating(&SplitViewDividerView::OnKebabButtonPressed,
base::Unretained(this)),
IconButton::Type::kMediumFloating, &kSnapGroupKebabIcon,
IDS_ASH_SNAP_GROUP_MORE_OPTIONS,
false,
false));
kebab_button_->SetPaintToLayer();
kebab_button_->layer()->SetFillsBoundsOpaquely(false);
kebab_button_->SetPreferredSize(kKebabButtonSize);
kebab_button_->SetVisible(true);
}
}
void SplitViewDividerView::DoSpawningAnimation(int spawn_position) {
const gfx::Rect bounds = GetBoundsInScreen();
int divider_signed_offset;
if (IsCurrentScreenOrientationLandscape()) {
SetBounds(spawn_position - bounds.x(), 0, 0, bounds.height());
divider_signed_offset = spawn_position - bounds.CenterPoint().x();
} else {
SetBounds(0, spawn_position - bounds.y(), bounds.width(), 0);
divider_signed_offset = spawn_position - bounds.CenterPoint().y();
}
ui::LayerAnimator* divider_animator = layer()->GetAnimator();
ui::ScopedLayerAnimationSettings settings(divider_animator);
settings.SetTransitionDuration(kSplitviewDividerSpawnDuration);
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
divider_animator->SchedulePauseForProperties(
kSplitviewDividerSpawnDelay, ui::LayerAnimationElement::BOUNDS);
SetBounds(0, 0, bounds.width(), bounds.height());
divider_handler_view_->DoSpawningAnimation(divider_signed_offset);
}
void SplitViewDividerView::SetDividerBarVisible(bool visible) {
divider_handler_view_->SetVisible(visible);
}
void SplitViewDividerView::Layout() {
if (!IsInTabletMode() && !IsSnapGroupEnabledInClamshellMode()) {
return;
}
SetBoundsRect(GetLocalBounds());
divider_handler_view_->Refresh(
split_view_controller_->is_resizing_with_divider());
if (IsSnapGroupEnabledInClamshellMode()) {
const gfx::Size kebab_button_size = kebab_button_->GetPreferredSize();
const gfx::Rect kebab_button_bounds(
(width() - kebab_button_size.width()) / 2.f,
height() - kebab_button_size.height() - kKebabButtonDistanceFromBottom,
kebab_button_size.width(), kebab_button_size.height());
kebab_button_->SetBoundsRect(kebab_button_bounds);
}
}
bool SplitViewDividerView::OnMousePressed(const ui::MouseEvent& event) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
split_view_controller_->StartResizeWithDivider(location);
OnResizeStatusChanged();
return true;
}
bool SplitViewDividerView::OnMouseDragged(const ui::MouseEvent& event) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
split_view_controller_->ResizeWithDivider(location);
return true;
}
void SplitViewDividerView::OnMouseReleased(const ui::MouseEvent& event) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
split_view_controller_->EndResizeWithDivider(location);
OnResizeStatusChanged();
if (event.GetClickCount() == 2) {
split_view_controller_->SwapWindows(
SplitViewController::SwapWindowsSource::kDoubleTap);
}
}
void SplitViewDividerView::OnGestureEvent(ui::GestureEvent* event) {
gfx::Point location(event->location());
views::View::ConvertPointToScreen(this, &location);
switch (event->type()) {
case ui::ET_GESTURE_TAP:
if (event->details().tap_count() == 2) {
split_view_controller_->SwapWindows(
SplitViewController::SwapWindowsSource::kDoubleTap);
}
break;
case ui::ET_GESTURE_TAP_DOWN:
case ui::ET_GESTURE_SCROLL_BEGIN:
split_view_controller_->StartResizeWithDivider(location);
OnResizeStatusChanged();
break;
case ui::ET_GESTURE_SCROLL_UPDATE:
split_view_controller_->ResizeWithDivider(location);
break;
case ui::ET_GESTURE_END:
split_view_controller_->EndResizeWithDivider(location);
OnResizeStatusChanged();
break;
default:
break;
}
event->SetHandled();
}
bool SplitViewDividerView::DoesIntersectRect(const views::View* target,
const gfx::Rect& rect) const {
DCHECK_EQ(target, this);
return true;
}
void SplitViewDividerView::OnResizeStatusChanged() {
if (!split_view_controller_->InSplitViewMode()) {
return;
}
ui::LayerAnimator* divider_animator = layer()->GetAnimator();
divider_animator->StopAnimatingProperty(ui::LayerAnimationElement::BOUNDS);
SetBoundsRect(GetLocalBounds());
const gfx::Rect old_bounds =
divider_->GetDividerBoundsInScreen(false);
const gfx::Rect new_bounds = divider_->GetDividerBoundsInScreen(
split_view_controller_->is_resizing_with_divider());
gfx::Transform transform;
transform.Translate(new_bounds.x() - old_bounds.x(),
new_bounds.y() - old_bounds.y());
transform.Scale(
static_cast<float>(new_bounds.width()) / old_bounds.width(),
static_cast<float>(new_bounds.height()) / old_bounds.height());
ui::ScopedLayerAnimationSettings settings(divider_animator);
settings.SetTransitionDuration(
kSplitviewDividerSelectionStatusChangeDuration);
settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
SetTransform(transform);
divider_handler_view_->Refresh(
split_view_controller_->is_resizing_with_divider());
}
void SplitViewDividerView::OnKebabButtonPressed() {
should_show_expanded_menu_ = !should_show_expanded_menu_;
if (!should_show_expanded_menu_) {
snap_group_expanded_menu_widget_.reset();
snap_group_expanded_menu_view_ = nullptr;
return;
}
if (!snap_group_expanded_menu_widget_) {
snap_group_expanded_menu_widget_ = std::make_unique<views::Widget>();
snap_group_expanded_menu_widget_->Init(CreateWidgetInitParams(
split_view_controller_->root_window(), "SnapGroupExpandedMenuWidget"));
SnapGroupController* snap_group_controller =
Shell::Get()->snap_group_controller();
SnapGroup* snap_group = snap_group_controller->GetSnapGroupForGivenWindow(
split_view_controller_->primary_window());
CHECK(snap_group);
snap_group_expanded_menu_view_ =
snap_group_expanded_menu_widget_->SetContentsView(
std::make_unique<SnapGroupExpandedMenuView>(snap_group));
}
snap_group_expanded_menu_widget_->Show();
MaybeUpdateExpandedMenuWidgetBounds();
}
void SplitViewDividerView::MaybeUpdateExpandedMenuWidgetBounds() {
CHECK(snap_group_expanded_menu_widget_);
const auto kebab_button_bounds = kebab_button_->GetBoundsInScreen();
gfx::Rect divider_bounds_in_screen =
split_view_controller_->split_view_divider()->GetDividerBoundsInScreen(
false);
const gfx::Rect expanded_menu_bounds(
divider_bounds_in_screen.x() + kSplitviewDividerShortSideLength / 2 -
kExpandedMenuRoundedCornerRadius,
kebab_button_bounds.y() - kExpandedMenuHeight -
kDistanceBetweenKebabButtonAndExpandedMenu,
kExpandedMenuRoundedCornerRadius * 2, kExpandedMenuHeight);
divider_bounds_in_screen.ClampToCenteredSize(
gfx::Size(kExpandedMenuRoundedCornerRadius * 2, kExpandedMenuHeight));
snap_group_expanded_menu_widget_->SetBounds(expanded_menu_bounds);
}
BEGIN_METADATA(SplitViewDividerView, views::View)
END_METADATA
}