#include "ash/wm/float/float_controller.h"
#include <algorithm>
#include <cstddef>
#include <vector>
#include "ash/constants/app_types.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/rotator/screen_rotation_animator.h"
#include "ash/scoped_animation_disabler.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/float/scoped_window_tucker.h"
#include "ash/wm/float/tablet_mode_tuck_education.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_window_state.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_default_layout_manager.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/workspace/workspace_event_handler.h"
#include "ash/wm/workspace/workspace_layout_manager.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/wm/constants.h"
#include "chromeos/ui/wm/window_util.h"
#include "components/app_restore/window_properties.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_observer.h"
#include "ui/display/screen.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
constexpr float kFloatedWindowClamshellWidthRatio = 1.f / 3.f;
constexpr float kFloatedWindowClamshellHeightRatio = 0.7f;
constexpr char kFloatWindowCountsPerSessionHistogramName[] =
"Ash.Float.FloatWindowCountsPerSession";
constexpr char kFloatWindowDurationHistogramName[] =
"Ash.Float.FloatWindowDuration";
constexpr char kFloatWindowMoveToAnotherDeskCountsHistogramName[] =
"Ash.Float.FloatWindowMoveToAnotherDeskCounts";
bool DisableAndGetOriginalPositionAutoManaged(aura::Window* window) {
auto* window_state = WindowState::Get(window);
const bool original_position_auto_managed =
window_state->GetWindowPositionManaged();
if (original_position_auto_managed)
window_state->SetWindowPositionManaged(false);
return original_position_auto_managed;
}
void UpdateWindowBoundsForTablet(
aura::Window* window,
WindowState::BoundsChangeAnimationType animation_type) {
WindowState* window_state = WindowState::Get(window);
DCHECK(window_state);
if (window->GetProperty(aura::client::kAppType) ==
static_cast<int>(AppType::ARC_APP)) {
const SetBoundsWMEvent event(
TabletModeWindowState::GetBoundsInTabletMode(window_state),
animation_type !=
WindowState::BoundsChangeAnimationType::kNone);
window_state->OnWMEvent(&event);
return;
}
TabletModeWindowState::UpdateWindowPosition(window_state, animation_type);
}
void HideFloatedWindow(aura::Window* floated_window) {
DCHECK(floated_window);
ScopedAnimationDisabler disabler(floated_window);
floated_window->Hide();
}
void ShowFloatedWindow(aura::Window* floated_window) {
DCHECK(floated_window);
if (floated_window->IsVisible()) {
return;
}
ScopedAnimationDisabler disabler(floated_window);
floated_window->Show();
}
FloatController::MagnetismCorner GetMagnetismCornerForBounds(
const gfx::Rect& bounds_in_screen) {
const gfx::Point display_bounds_center =
display::Screen::GetScreen()
->GetDisplayMatching(bounds_in_screen)
.bounds()
.CenterPoint();
const int display_bounds_center_x = display_bounds_center.x();
const int display_bounds_center_y = display_bounds_center.y();
const gfx::Point center_point = bounds_in_screen.CenterPoint();
const int center_point_x = center_point.x();
const int center_point_y = center_point.y();
FloatController::MagnetismCorner magnetism_corner;
if (center_point_x < display_bounds_center_x &&
center_point_y < display_bounds_center_y) {
magnetism_corner = FloatController::MagnetismCorner::kTopLeft;
} else if (center_point_x >= display_bounds_center_x &&
center_point_y < display_bounds_center_y) {
magnetism_corner = FloatController::MagnetismCorner::kTopRight;
} else if (center_point_x < display_bounds_center_x &&
center_point_y >= display_bounds_center_y) {
magnetism_corner = FloatController::MagnetismCorner::kBottomLeft;
} else {
CHECK_GE(center_point_x, display_bounds_center_x);
CHECK_GE(center_point_y, display_bounds_center_y);
magnetism_corner = FloatController::MagnetismCorner::kBottomRight;
}
return magnetism_corner;
}
class FloatLayoutManager : public WmDefaultLayoutManager {
public:
FloatLayoutManager() = default;
FloatLayoutManager(const FloatLayoutManager&) = delete;
FloatLayoutManager& operator=(const FloatLayoutManager&) = delete;
~FloatLayoutManager() override = default;
void SetChildBounds(aura::Window* child,
const gfx::Rect& requested_bounds) override {
WindowState* window_state = WindowState::Get(child);
SetBoundsWMEvent event(requested_bounds);
window_state->OnWMEvent(&event);
}
};
}
class FloatController::FloatedWindowInfo : public aura::WindowObserver {
public:
FloatedWindowInfo(aura::Window* floated_window, const Desk* desk)
: floated_window_(floated_window),
was_position_auto_managed_(
DisableAndGetOriginalPositionAutoManaged(floated_window)),
desk_(desk) {
DCHECK(floated_window_);
floated_window_observation_.Observe(floated_window);
if (desk->is_active())
float_start_time_ = base::TimeTicks::Now();
if (Shell::Get()->tablet_mode_controller()->InTabletMode() &&
TabletModeTuckEducation::CanActivateTuckEducation()) {
tuck_education_ =
std::make_unique<TabletModeTuckEducation>(floated_window);
}
}
FloatedWindowInfo(const FloatedWindowInfo&) = delete;
FloatedWindowInfo& operator=(const FloatedWindowInfo&) = delete;
~FloatedWindowInfo() override {
if (was_position_auto_managed_)
WindowState::Get(floated_window_)->SetWindowPositionManaged(true);
MaybeRecordFloatWindowDuration();
}
const Desk* desk() const { return desk_; }
void set_desk(const Desk* desk) { desk_ = desk; }
bool is_tucked_for_tablet() const { return is_tucked_for_tablet_; }
MagnetismCorner magnetism_corner() const { return magnetism_corner_; }
void set_magnetism_corner(MagnetismCorner magnetism_corner) {
magnetism_corner_ = magnetism_corner;
}
void MaybeRecordFloatWindowDuration() {
if (!float_start_time_.is_null()) {
base::UmaHistogramCustomCounts(
kFloatWindowDurationHistogramName,
(base::TimeTicks::Now() - float_start_time_).InMinutes(), 1,
base::Days(7).InMinutes(), 50);
float_start_time_ = base::TimeTicks();
}
}
void MaybeTuckWindow(bool left) {
is_tucked_for_tablet_ = true;
scoped_window_tucker_ =
std::make_unique<ScopedWindowTucker>(floated_window_, left);
scoped_window_tucker_->AnimateTuck();
TabletModeTuckEducation::OnWindowTucked();
}
void OnUntuckAnimationEnded() {
scoped_window_tucker_.reset();
UpdateWindowBoundsForTablet(floated_window_,
WindowState::BoundsChangeAnimationType::kNone);
}
void MaybeUntuckWindow(bool animate) {
is_tucked_for_tablet_ = false;
if (!animate) {
scoped_window_tucker_.reset();
TabletModeWindowState::UpdateWindowPosition(
WindowState::Get(floated_window_),
WindowState::BoundsChangeAnimationType::kNone);
return;
}
if (scoped_window_tucker_) {
scoped_window_tucker_->AnimateUntuck(
base::BindOnce(&FloatedWindowInfo::OnUntuckAnimationEnded,
weak_ptr_factory_.GetWeakPtr()));
}
}
views::Widget* GetTuckHandleWidget() {
DCHECK(scoped_window_tucker_);
return scoped_window_tucker_->tuck_handle_widget();
}
void OnWindowDestroying(aura::Window* window) override {
DCHECK_EQ(floated_window_, window);
DCHECK(
floated_window_observation_.IsObservingSource(floated_window_.get()));
Shell::Get()->float_controller()->OnFloatedWindowDestroying(window);
}
void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
if (window != floated_window_)
return;
if (visible && desk_->is_active()) {
if (float_start_time_.is_null())
float_start_time_ = base::TimeTicks::Now();
return;
}
if (!visible && !desk_->is_active())
MaybeRecordFloatWindowDuration();
}
void OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) override {
CHECK_EQ(floated_window_, window);
if (key != aura::client::kResizeBehaviorKey) {
return;
}
if (!chromeos::wm::CanFloatWindow(floated_window_)) {
Shell::Get()->float_controller()->ResetFloatedWindow(floated_window_);
return;
}
if (Shell::Get()->IsInTabletMode()) {
UpdateWindowBoundsForTablet(
floated_window_, WindowState::BoundsChangeAnimationType::kNone);
}
}
private:
raw_ptr<aura::Window, ExperimentalAsh> floated_window_;
const bool was_position_auto_managed_;
std::unique_ptr<ScopedWindowTucker> scoped_window_tucker_;
std::unique_ptr<TabletModeTuckEducation> tuck_education_;
bool is_tucked_for_tablet_ = false;
raw_ptr<const Desk, ExperimentalAsh> desk_;
base::TimeTicks float_start_time_;
MagnetismCorner magnetism_corner_ = MagnetismCorner::kBottomRight;
base::ScopedObservation<aura::Window, aura::WindowObserver>
floated_window_observation_{this};
base::WeakPtrFactory<FloatedWindowInfo> weak_ptr_factory_{this};
};
FloatController::FloatController() {
shell_observation_.Observe(Shell::Get());
for (aura::Window* root : Shell::GetAllRootWindows())
OnRootWindowAdded(root);
}
FloatController::~FloatController() {
base::UmaHistogramCounts100(kFloatWindowCountsPerSessionHistogramName,
floated_window_counter_);
base::UmaHistogramCounts100(kFloatWindowMoveToAnotherDeskCountsHistogramName,
floated_window_move_to_another_desk_counter_);
}
gfx::Rect FloatController::GetPreferredFloatWindowClamshellBounds(
aura::Window* window) {
DCHECK(chromeos::wm::CanFloatWindow(window));
if (window->GetProperty(app_restore::kLaunchedFromAppRestoreKey)) {
return window->bounds();
}
const gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInParent(window);
const int padding_dp = chromeos::wm::kFloatedWindowPaddingDp;
if ((window->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanResize) == 0) {
const gfx::Size size = window->bounds().size();
return gfx::Rect(work_area.right() - size.width() - padding_dp,
work_area.bottom() - size.height() - padding_dp,
size.width(), size.height());
}
const gfx::Size minimum_size = window->delegate()->GetMinimumSize();
gfx::Rect preferred_bounds =
gfx::Rect(std::max(static_cast<int>(work_area.width() *
kFloatedWindowClamshellWidthRatio),
minimum_size.width()),
std::max(static_cast<int>(work_area.height() *
kFloatedWindowClamshellHeightRatio),
minimum_size.height()));
if (window->bounds().height() <= preferred_bounds.height() &&
window->bounds().width() <= preferred_bounds.width()) {
preferred_bounds = window->bounds();
}
const int preferred_width =
std::min(preferred_bounds.width(), work_area.width() - 2 * padding_dp);
const int preferred_height =
std::min(preferred_bounds.height(), work_area.height() - 2 * padding_dp);
return gfx::Rect(work_area.right() - preferred_width - padding_dp,
work_area.bottom() - preferred_height - padding_dp,
preferred_width, preferred_height);
}
gfx::Rect FloatController::GetPreferredFloatWindowTabletBounds(
aura::Window* window) {
const gfx::Size preferred_size =
chromeos::wm::GetFloatedWindowTabletSize(window);
const int width = preferred_size.width();
const int height = preferred_size.height();
auto* floated_window_info =
Shell::Get()->float_controller()->MaybeGetFloatedWindowInfo(window);
#if DCHECK_IS_ON()
if (window->GetProperty(aura::client::kAppType) !=
static_cast<int>(AppType::ARC_APP)) {
DCHECK(floated_window_info);
}
#endif
const gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInParent(window);
gfx::Point origin;
const MagnetismCorner magnetism_corner =
floated_window_info ? floated_window_info->magnetism_corner()
: MagnetismCorner::kBottomRight;
const int padding_dp = chromeos::wm::kFloatedWindowPaddingDp;
switch (magnetism_corner) {
case MagnetismCorner::kTopLeft:
origin =
gfx::Point(work_area.x() + padding_dp, work_area.y() + padding_dp);
break;
case MagnetismCorner::kTopRight:
origin = gfx::Point(work_area.right() - width - padding_dp,
work_area.y() + padding_dp);
break;
case MagnetismCorner::kBottomLeft:
origin = gfx::Point(work_area.x() + padding_dp,
work_area.bottom() - height - padding_dp);
break;
case MagnetismCorner::kBottomRight:
origin = gfx::Point(work_area.right() - width - padding_dp,
work_area.bottom() - height - padding_dp);
break;
}
if (floated_window_info && floated_window_info->is_tucked_for_tablet()) {
int x_offset;
switch (magnetism_corner) {
case MagnetismCorner::kTopLeft:
case MagnetismCorner::kBottomLeft:
x_offset = -width - padding_dp;
break;
case MagnetismCorner::kTopRight:
case MagnetismCorner::kBottomRight:
x_offset = width + padding_dp;
break;
}
origin.Offset(x_offset, 0);
}
return gfx::Rect(origin, gfx::Size(width, height));
}
void FloatController::MaybeUntuckFloatedWindowForTablet(
aura::Window* floated_window) {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
DCHECK(floated_window_info);
floated_window_info->MaybeUntuckWindow(true);
}
bool FloatController::IsFloatedWindowTuckedForTablet(
const aura::Window* floated_window) const {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
if (!floated_window_info) {
return false;
}
return floated_window_info->is_tucked_for_tablet();
}
bool FloatController::IsFloatedWindowAlignedWithShelf(
aura::Window* floated_window) const {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
DCHECK(floated_window_info);
if (floated_window_info->is_tucked_for_tablet()) {
return false;
}
MagnetismCorner magnetism_corner = floated_window_info->magnetism_corner();
return magnetism_corner == MagnetismCorner::kBottomLeft ||
magnetism_corner == MagnetismCorner::kBottomRight;
}
views::Widget* FloatController::GetTuckHandleWidget(
const aura::Window* floated_window) const {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
DCHECK(floated_window_info);
return floated_window_info->GetTuckHandleWidget();
}
void FloatController::OnDragCompletedForTablet(aura::Window* floated_window) {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
DCHECK(floated_window_info);
floated_window_info->set_magnetism_corner(
GetMagnetismCornerForBounds(floated_window->GetBoundsInScreen()));
UpdateWindowBoundsForTablet(floated_window,
WindowState::BoundsChangeAnimationType::kAnimate);
}
void FloatController::OnFlingOrSwipeForTablet(aura::Window* floated_window,
float velocity_x,
float velocity_y) {
auto* floated_window_info = MaybeGetFloatedWindowInfo(floated_window);
DCHECK(floated_window_info);
MagnetismCorner magnetism_corner = floated_window_info->magnetism_corner();
bool start_left = magnetism_corner == MagnetismCorner::kTopLeft ||
magnetism_corner == MagnetismCorner::kBottomLeft;
if (velocity_y < 0.f) {
floated_window_info->set_magnetism_corner(
start_left ? MagnetismCorner::kTopLeft : MagnetismCorner::kTopRight);
} else if (velocity_y > 0.f) {
floated_window_info->set_magnetism_corner(
start_left ? MagnetismCorner::kBottomLeft
: MagnetismCorner::kBottomRight);
}
magnetism_corner = floated_window_info->magnetism_corner();
bool start_top = magnetism_corner == MagnetismCorner::kTopLeft ||
magnetism_corner == MagnetismCorner::kTopRight;
if (velocity_x < 0.f) {
floated_window_info->set_magnetism_corner(
start_top ? MagnetismCorner::kTopLeft : MagnetismCorner::kBottomLeft);
} else if (velocity_x > 0.f) {
floated_window_info->set_magnetism_corner(
start_top ? MagnetismCorner::kTopRight : MagnetismCorner::kBottomRight);
}
if ((start_left && velocity_x < 0.f) || (!start_left && velocity_x > 0.f)) {
floated_window_info->MaybeTuckWindow(start_left);
return;
}
UpdateWindowBoundsForTablet(floated_window,
WindowState::BoundsChangeAnimationType::kAnimate);
}
const Desk* FloatController::FindDeskOfFloatedWindow(
const aura::Window* window) const {
if (auto* info = MaybeGetFloatedWindowInfo(window))
return info->desk();
return nullptr;
}
aura::Window* FloatController::FindFloatedWindowOfDesk(const Desk* desk) const {
DCHECK(desk);
for (const auto& [window, info] : floated_window_info_map_) {
if (info->desk() == desk)
return window;
}
return nullptr;
}
void FloatController::OnMovingAllWindowsOutToDesk(Desk* original_desk,
Desk* target_desk) {
auto* original_desk_floated_window = FindFloatedWindowOfDesk(original_desk);
if (!original_desk_floated_window)
return;
++floated_window_move_to_another_desk_counter_;
auto* target_desk_floated_window = FindFloatedWindowOfDesk(target_desk);
ShowFloatedWindow(original_desk_floated_window);
if (target_desk_floated_window) {
ResetFloatedWindow(original_desk_floated_window);
} else {
floated_window_info_map_[original_desk_floated_window]->set_desk(
target_desk);
Shell::Get()->mru_window_tracker()->OnWindowMovedOutFromRemovingDesk(
original_desk_floated_window);
}
}
void FloatController::OnMovingFloatedWindowToDesk(aura::Window* floated_window,
Desk* active_desk,
Desk* target_desk,
aura::Window* target_root) {
auto* target_desk_floated_window = FindFloatedWindowOfDesk(target_desk);
aura::Window* root = floated_window->GetRootWindow();
if (target_desk_floated_window) {
ResetFloatedWindow(target_desk_floated_window);
}
auto* float_info = MaybeGetFloatedWindowInfo(floated_window);
DCHECK(float_info);
DCHECK_EQ(float_info->desk(), active_desk);
float_info->set_desk(target_desk);
++floated_window_move_to_another_desk_counter_;
if (root != target_root) {
window_util::MoveWindowToDisplay(floated_window,
display::Screen::GetScreen()
->GetDisplayNearestWindow(target_root)
.id());
}
if (target_desk->is_active()) {
ShowFloatedWindow(floated_window);
} else {
HideFloatedWindow(floated_window);
}
active_desk->NotifyContentChanged();
target_desk->NotifyContentChanged();
}
void FloatController::ClearWorkspaceEventHandler(aura::Window* root) {
workspace_event_handlers_.erase(root);
}
void FloatController::OnTabletModeStarted() {
DCHECK(!floated_window_info_map_.empty());
for (auto& [window, info] : floated_window_info_map_) {
if (chromeos::wm::CanFloatWindow(window)) {
info->set_magnetism_corner(
GetMagnetismCornerForBounds(window->GetBoundsInScreen()));
UpdateWindowBoundsForTablet(
window, WindowState::BoundsChangeAnimationType::kCrossFade);
} else {
ResetFloatedWindow(window);
}
}
}
void FloatController::OnTabletModeEnding() {
for (auto& [window, info] : floated_window_info_map_)
info->MaybeUntuckWindow(true);
}
void FloatController::OnTabletControllerDestroyed() {
tablet_mode_observation_.Reset();
}
void FloatController::OnDeskActivationChanged(const Desk* activated,
const Desk* deactivated) {
auto deactivated_desk_floated_window_info_iter = base::ranges::find_if(
floated_window_info_map_, [deactivated](const auto& floated_window_info) {
return floated_window_info.second->desk() == deactivated;
});
if (deactivated_desk_floated_window_info_iter !=
floated_window_info_map_.end()) {
deactivated_desk_floated_window_info_iter->second->MaybeUntuckWindow(
false);
HideFloatedWindow(deactivated_desk_floated_window_info_iter->first);
}
if (auto* activated_desk_floated_window =
FindFloatedWindowOfDesk(activated)) {
ShowFloatedWindow(activated_desk_floated_window);
if (auto* top_window = window_util::GetTopWindow();
top_window == activated_desk_floated_window) {
wm::ActivateWindow(top_window);
}
}
}
void FloatController::OnDisplayMetricsChanged(const display::Display& display,
uint32_t metrics) {
display::TabletState tablet_state = chromeos::TabletState::Get()->state();
if (tablet_state == display::TabletState::kEnteringTabletMode ||
tablet_state == display::TabletState::kExitingTabletMode) {
return;
}
if ((display::DisplayObserver::DISPLAY_METRIC_WORK_AREA & metrics) == 0)
return;
DCHECK(!floated_window_info_map_.empty());
std::vector<aura::Window*> windows_need_reset;
for (auto& [window, info] : floated_window_info_map_) {
if (!chromeos::wm::CanFloatWindow(window)) {
windows_need_reset.push_back(window);
} else {
const WMEvent event(WM_EVENT_WORKAREA_BOUNDS_CHANGED);
WindowState::Get(window)->OnWMEvent(&event);
}
}
for (auto* window : windows_need_reset)
ResetFloatedWindow(window);
if (display::DisplayObserver::DISPLAY_METRIC_ROTATION & metrics) {
if (auto* const root_window = Shell::GetRootWindowForDisplayId(display.id())) {
auto* const animator =
ScreenRotationAnimator::GetForRootWindow(root_window);
if (!screen_rotation_observations_.IsObservingSource(animator)) {
screen_rotation_observations_.AddObservation(animator);
}
}
}
}
void FloatController::OnRootWindowAdded(aura::Window* root_window) {
workspace_event_handlers_[root_window] =
std::make_unique<WorkspaceEventHandler>(
root_window->GetChildById(kShellWindowId_FloatContainer));
root_window->GetChildById(kShellWindowId_FloatContainer)
->SetLayoutManager(std::make_unique<FloatLayoutManager>());
}
void FloatController::OnRootWindowWillShutdown(aura::Window* root_window) {
auto* const animator = ScreenRotationAnimator::GetForRootWindow(root_window);
if (screen_rotation_observations_.IsObservingSource(animator)) {
screen_rotation_observations_.RemoveObservation(animator);
}
}
void FloatController::OnScreenCopiedBeforeRotation() {}
void FloatController::OnScreenRotationAnimationFinished(
ScreenRotationAnimator* animator,
bool canceled) {
for (auto& [window, info] : floated_window_info_map_) {
if (window->GetProperty(aura::client::kAppType) ==
static_cast<int>(AppType::ARC_APP)) {
const gfx::Rect bounds =
Shell::Get()->tablet_mode_controller()->InTabletMode()
? GetPreferredFloatWindowTabletBounds(window)
: GetPreferredFloatWindowClamshellBounds(window);
const SetBoundsWMEvent event(bounds);
WindowState::Get(window)->OnWMEvent(&event);
}
}
}
void FloatController::OnPinnedStateChanged(aura::Window* pinned_window) {
if (aura::Window* floated_window =
window_util::GetFloatedWindowForActiveDesk()) {
if (aura::Window* to_be_pinned_window =
Shell::Get()->screen_pinning_controller()->pinned_window()) {
if (to_be_pinned_window != floated_window) {
HideFloatedWindow(floated_window);
}
} else {
ShowFloatedWindow(floated_window);
}
}
}
void FloatController::ToggleFloat(aura::Window* window) {
WindowState* window_state = WindowState::Get(window);
const WMEvent toggle_event(window_state->IsFloated() ? WM_EVENT_RESTORE
: WM_EVENT_FLOAT);
window_state->OnWMEvent(&toggle_event);
}
void FloatController::FloatForTablet(aura::Window* window,
chromeos::WindowStateType old_state_type) {
CHECK(Shell::Get()->IsInTabletMode());
FloatImpl(window);
absl::optional<MagnetismCorner> magnetism_corner;
if (chromeos::IsMinimizedWindowStateType(old_state_type)) {
magnetism_corner = GetMagnetismCornerForBounds(window->GetBoundsInScreen());
} else if (chromeos::IsSnappedWindowStateType(old_state_type)) {
const bool left_or_top =
old_state_type == chromeos::WindowStateType::kPrimarySnapped;
const bool landscape = IsCurrentScreenOrientationLandscape();
if (!left_or_top) {
magnetism_corner = MagnetismCorner::kBottomRight;
} else if (landscape) {
magnetism_corner = MagnetismCorner::kBottomLeft;
} else {
CHECK(left_or_top && !landscape);
magnetism_corner = MagnetismCorner::kTopRight;
}
}
if (!magnetism_corner) {
return;
}
auto* floated_window_info = MaybeGetFloatedWindowInfo(window);
CHECK(floated_window_info);
floated_window_info->set_magnetism_corner(*magnetism_corner);
}
void FloatController::FloatImpl(aura::Window* window) {
if (floated_window_info_map_.contains(window))
return;
auto* desk_controller = DesksController::Get();
const Desk* desk = desks_util::GetDeskForContext(window);
DCHECK(desk);
if (desks_util::IsWindowVisibleOnAllWorkspaces(window)) {
window->SetProperty(aura::client::kWindowWorkspaceKey,
aura::client::kWindowWorkspaceUnassignedWorkspace);
}
auto* previously_floated_window = FindFloatedWindowOfDesk(desk);
floated_window_info_map_.emplace(
window, std::make_unique<FloatedWindowInfo>(window, desk));
if (previously_floated_window)
ResetFloatedWindow(previously_floated_window);
aura::Window* floated_container =
window->GetRootWindow()->GetChildById(kShellWindowId_FloatContainer);
DCHECK_NE(window->parent(), floated_container);
floated_container->AddChild(window);
if (!desk->is_active())
HideFloatedWindow(window);
++floated_window_counter_;
if (!tablet_mode_observation_.IsObserving())
tablet_mode_observation_.Observe(Shell::Get()->tablet_mode_controller());
if (!desks_controller_observation_.IsObserving())
desks_controller_observation_.Observe(desk_controller);
if (!display_observer_)
display_observer_.emplace(this);
}
void FloatController::UnfloatImpl(aura::Window* window) {
auto* floated_window_info = MaybeGetFloatedWindowInfo(window);
if (!floated_window_info)
return;
WindowState::Get(window)->SetPreAddedToWorkspaceWindowBounds(
window->bounds());
ShowFloatedWindow(window);
floated_window_info->desk()
->GetDeskContainerForRoot(window->GetRootWindow())
->AddChild(window);
floated_window_info_map_.erase(window);
if (floated_window_info_map_.empty()) {
desks_controller_observation_.Reset();
tablet_mode_observation_.Reset();
display_observer_.reset();
}
}
void FloatController::ResetFloatedWindow(aura::Window* floated_window) {
DCHECK(floated_window);
DCHECK(WindowState::Get(floated_window)->IsFloated());
ToggleFloat(floated_window);
}
FloatController::FloatedWindowInfo* FloatController::MaybeGetFloatedWindowInfo(
const aura::Window* window) const {
const auto iter = floated_window_info_map_.find(window);
if (iter == floated_window_info_map_.end())
return nullptr;
return iter->second.get();
}
void FloatController::OnFloatedWindowDestroying(aura::Window* floated_window) {
floated_window_info_map_.erase(floated_window);
if (floated_window_info_map_.empty()) {
desks_controller_observation_.Reset();
tablet_mode_observation_.Reset();
display_observer_.reset();
}
}
}