#include "ash/wm/splitview/auto_snap_controller.h"
#include <optional>
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_metrics.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_overview_session.h"
#include "ash/wm/splitview/split_view_types.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/window_util.h"
#include "ui/wm/public/activation_client.h"
namespace ash {
namespace {
std::optional<float> CalculateAutoSnapRatio(
SplitViewController* split_view_controller,
aura::Window* window) {
if (display::Screen::Get()->InTabletMode()) {
return split_view_controller->ComputeAutoSnapRatio(window);
}
auto* window_state = WindowState::Get(window);
if (auto* split_view_overview_session =
RootWindowController::ForWindow(window)
->split_view_overview_session();
split_view_overview_session &&
split_view_overview_session->window() != window) {
if (!window_state->CanSnap()) {
return std::nullopt;
}
const float snap_ratio =
1.f - WindowState::Get(split_view_overview_session->window())
->snap_ratio()
.value_or(chromeos::kDefaultSnapRatio);
return std::make_optional<float>(snap_ratio);
}
return window_state->snap_ratio();
}
}
AutoSnapController::AutoSnapController(aura::Window* root_window)
: root_window_(root_window) {
Shell::Get()->activation_client()->AddObserver(this);
AddWindow(root_window);
for (aura::Window* window :
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
AddWindow(window);
}
}
AutoSnapController::~AutoSnapController() {
window_observations_.RemoveAllObservations();
Shell::Get()->activation_client()->RemoveObserver(this);
}
void AutoSnapController::OnWindowActivating(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (!gained_active ||
reason == ActivationReason::WINDOW_DISPOSITION_CHANGED) {
return;
}
AutoSnapWindowIfNeeded(gained_active);
}
void AutoSnapController::OnWindowVisibilityChanging(aura::Window* window,
bool visible) {
if (!visible) {
return;
}
if (auto* window_state = WindowState::Get(window);
!window_state || !window_state->IsMinimized()) {
return;
}
if (window->GetProperty(kHideDuringWindowDragging)) {
return;
}
AutoSnapWindowIfNeeded(window);
}
void AutoSnapController::OnWindowAddedToRootWindow(aura::Window* window) {
AddWindow(window);
}
void AutoSnapController::OnWindowRemovingFromRootWindow(
aura::Window* window,
aura::Window* new_root) {
RemoveWindow(window);
}
void AutoSnapController::OnWindowDestroying(aura::Window* window) {
RemoveWindow(window);
if (window == root_window_) {
root_window_ = nullptr;
}
}
bool AutoSnapController::AutoSnapWindowIfNeeded(aura::Window* window) {
CHECK(window);
if (window->GetRootWindow() != root_window_) {
return false;
}
OverviewController* overview_controller = OverviewController::Get();
if (auto* overview_session = overview_controller->overview_session();
overview_session && overview_session->is_shutting_down()) {
return false;
}
auto* split_view_controller = SplitViewController::Get(window);
if (!split_view_controller->InSplitViewMode()) {
return false;
}
WindowState* window_state = WindowState::Get(window);
const SplitViewController::State state = split_view_controller->state();
if (window_state->IsFloated() &&
state == SplitViewController::State::kBothSnapped) {
return false;
}
if (DesksController::Get()->AreDesksBeingModified()) {
return false;
}
if (split_view_controller->IsWindowInSplitView(window) ||
!base::Contains(
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk),
window)) {
return false;
}
if (split_view_controller->IsWindowInTransitionalState(window)) {
return false;
}
if (split_view_controller->InClamshellSplitViewMode() &&
overview_controller->InOverviewSession() &&
!overview_controller->overview_session()->IsWindowInOverview(window) &&
!window_util::IsInFasterSplitScreenSetupSession(window)) {
overview_controller->EndOverview(OverviewEndAction::kSplitView);
return false;
}
if (window_state->is_dragged() || window->GetProperty(kIsDraggingTabsKey)) {
return false;
}
if (split_view_controller->IsDividerAnimating()) {
if (window_state->IsUserPositionable()) {
split_view_controller->EndSplitView(
SplitViewController::EndReason::kUnsnappableWindowActivated);
}
return false;
}
std::optional<float> auto_snap_ratio =
CalculateAutoSnapRatio(split_view_controller, window);
if (!auto_snap_ratio) {
if (window_state->IsUserPositionable()) {
split_view_controller->EndSplitView(
SplitViewController::EndReason::kUnsnappableWindowActivated);
ShowAppCannotSnapToast();
}
return false;
}
if (!split_view_controller->CanSnapWindow(window, *auto_snap_ratio)) {
auto_snap_ratio.emplace(static_cast<float>(GetMinimumWindowLength(
window, IsLayoutHorizontal(window))) /
static_cast<float>(GetDividerPositionUpperLimit(
window->GetRootWindow())));
}
split_view_controller->SnapWindow(
window,
(split_view_controller->default_snap_position() == SnapPosition::kPrimary)
? SnapPosition::kSecondary
: SnapPosition::kPrimary,
WindowSnapActionSource::kAutoSnapInSplitView,
false, *auto_snap_ratio);
return true;
}
void AutoSnapController::AddWindow(aura::Window* window) {
if (window->GetRootWindow() != root_window_) {
return;
}
if (!window_observations_.IsObservingSource(window)) {
window_observations_.AddObservation(window);
}
}
void AutoSnapController::RemoveWindow(aura::Window* window) {
window_observations_.RemoveObservation(window);
}
}