blob: e0b085f768622b72e7cedaf90de7a693d0b116d7 [file] [log] [blame]
// Copyright (c) 2012 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/common/wm/system_modal_container_layout_manager.h"
#include <cmath>
#include "ash/common/session/session_state_delegate.h"
#include "ash/common/wm/window_dimmer.h"
#include "ash/common/wm_shell.h"
#include "ash/common/wm_window.h"
#include "ash/common/wm_window_property.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "ui/keyboard/keyboard_controller.h"
namespace ash {
namespace {
// The center point of the window can diverge this much from the center point
// of the container to be kept centered upon resizing operations.
const int kCenterPixelDelta = 32;
ui::ModalType GetModalType(WmWindow* window) {
return static_cast<ui::ModalType>(
window->GetIntProperty(WmWindowProperty::MODAL_TYPE));
}
bool HasTransientAncestor(const WmWindow* window, const WmWindow* ancestor) {
const WmWindow* transient_parent = window->GetTransientParent();
if (transient_parent == ancestor)
return true;
return transient_parent ? HasTransientAncestor(transient_parent, ancestor)
: false;
}
}
////////////////////////////////////////////////////////////////////////////////
// SystemModalContainerLayoutManager, public:
SystemModalContainerLayoutManager::SystemModalContainerLayoutManager(
WmWindow* container)
: container_(container) {}
SystemModalContainerLayoutManager::~SystemModalContainerLayoutManager() {
if (keyboard::KeyboardController::GetInstance())
keyboard::KeyboardController::GetInstance()->RemoveObserver(this);
}
////////////////////////////////////////////////////////////////////////////////
// SystemModalContainerLayoutManager, WmLayoutManager implementation:
void SystemModalContainerLayoutManager::OnChildWindowVisibilityChanged(
WmWindow* window,
bool visible) {
if (GetModalType(window) != ui::MODAL_TYPE_SYSTEM)
return;
if (window->IsVisible()) {
DCHECK(!base::ContainsValue(modal_windows_, window));
AddModalWindow(window);
} else {
if (RemoveModalWindow(window))
WmShell::Get()->OnModalWindowRemoved(window);
}
}
void SystemModalContainerLayoutManager::OnWindowResized() {
PositionDialogsAfterWorkAreaResize();
}
void SystemModalContainerLayoutManager::OnWindowAddedToLayout(WmWindow* child) {
DCHECK(child->GetType() == ui::wm::WINDOW_TYPE_NORMAL ||
child->GetType() == ui::wm::WINDOW_TYPE_POPUP);
// TODO(mash): IsUserSessionBlocked() depends on knowing the login state. We
// need a non-stub version of SessionStateDelegate. crbug.com/648964
if (!WmShell::Get()->IsRunningInMash()) {
DCHECK(container_->GetShellWindowId() !=
kShellWindowId_LockSystemModalContainer ||
WmShell::Get()->GetSessionStateDelegate()->IsUserSessionBlocked());
}
// Since this is for SystemModal, there is no good reason to add windows
// other than MODAL_TYPE_NONE or MODAL_TYPE_SYSTEM. DCHECK to avoid simple
// mistake.
DCHECK_NE(GetModalType(child), ui::MODAL_TYPE_CHILD);
DCHECK_NE(GetModalType(child), ui::MODAL_TYPE_WINDOW);
child->AddObserver(this);
if (GetModalType(child) == ui::MODAL_TYPE_SYSTEM && child->IsVisible())
AddModalWindow(child);
}
void SystemModalContainerLayoutManager::OnWillRemoveWindowFromLayout(
WmWindow* child) {
child->RemoveObserver(this);
windows_to_center_.erase(child);
if (GetModalType(child) == ui::MODAL_TYPE_SYSTEM)
RemoveModalWindow(child);
}
void SystemModalContainerLayoutManager::SetChildBounds(
WmWindow* child,
const gfx::Rect& requested_bounds) {
WmSnapToPixelLayoutManager::SetChildBounds(child, requested_bounds);
if (IsBoundsCentered(requested_bounds))
windows_to_center_.insert(child);
else
windows_to_center_.erase(child);
}
////////////////////////////////////////////////////////////////////////////////
// SystemModalContainerLayoutManager, WmWindowObserver implementation:
void SystemModalContainerLayoutManager::OnWindowPropertyChanged(
WmWindow* window,
WmWindowProperty property) {
if (property != WmWindowProperty::MODAL_TYPE || !window->IsVisible())
return;
if (GetModalType(window) == ui::MODAL_TYPE_SYSTEM) {
if (base::ContainsValue(modal_windows_, window))
return;
AddModalWindow(window);
} else {
if (RemoveModalWindow(window))
WmShell::Get()->OnModalWindowRemoved(window);
}
}
////////////////////////////////////////////////////////////////////////////////
// SystemModalContainerLayoutManager, Keyboard::KeybaordControllerObserver
// implementation:
void SystemModalContainerLayoutManager::OnKeyboardBoundsChanging(
const gfx::Rect& new_bounds) {
PositionDialogsAfterWorkAreaResize();
}
void SystemModalContainerLayoutManager::OnKeyboardClosed() {}
bool SystemModalContainerLayoutManager::IsPartOfActiveModalWindow(
WmWindow* window) {
return modal_window() &&
(modal_window()->Contains(window) ||
HasTransientAncestor(window->GetToplevelWindowForFocus(),
modal_window()));
}
bool SystemModalContainerLayoutManager::ActivateNextModalWindow() {
if (modal_windows_.empty())
return false;
modal_window()->Activate();
return true;
}
void SystemModalContainerLayoutManager::CreateModalBackground() {
if (!window_dimmer_) {
window_dimmer_ = base::MakeUnique<WindowDimmer>(container_);
window_dimmer_->window()->SetName(
"SystemModalContainerLayoutManager.ModalBackground");
// There isn't always a keyboard controller.
if (keyboard::KeyboardController::GetInstance())
keyboard::KeyboardController::GetInstance()->AddObserver(this);
}
window_dimmer_->window()->Show();
}
void SystemModalContainerLayoutManager::DestroyModalBackground() {
if (!window_dimmer_)
return;
if (keyboard::KeyboardController::GetInstance())
keyboard::KeyboardController::GetInstance()->RemoveObserver(this);
window_dimmer_.reset();
}
// static
bool SystemModalContainerLayoutManager::IsModalBackground(WmWindow* window) {
int id = window->GetParent()->GetShellWindowId();
if (id != kShellWindowId_SystemModalContainer &&
id != kShellWindowId_LockSystemModalContainer)
return false;
SystemModalContainerLayoutManager* layout_manager =
static_cast<SystemModalContainerLayoutManager*>(
window->GetParent()->GetLayoutManager());
return layout_manager->window_dimmer_ &&
layout_manager->window_dimmer_->window() == window;
}
////////////////////////////////////////////////////////////////////////////////
// SystemModalContainerLayoutManager, private:
void SystemModalContainerLayoutManager::AddModalWindow(WmWindow* window) {
if (modal_windows_.empty()) {
WmWindow* capture_window = WmShell::Get()->GetCaptureWindow();
if (capture_window)
capture_window->ReleaseCapture();
}
DCHECK(window->IsVisible());
DCHECK(!base::ContainsValue(modal_windows_, window));
modal_windows_.push_back(window);
WmShell::Get()->CreateModalBackground(window);
window->GetParent()->StackChildAtTop(window);
gfx::Rect target_bounds = window->GetBounds();
target_bounds.AdjustToFit(GetUsableDialogArea());
window->SetBounds(target_bounds);
}
bool SystemModalContainerLayoutManager::RemoveModalWindow(WmWindow* window) {
auto it = std::find(modal_windows_.begin(), modal_windows_.end(), window);
if (it == modal_windows_.end())
return false;
modal_windows_.erase(it);
return true;
}
void SystemModalContainerLayoutManager::PositionDialogsAfterWorkAreaResize() {
if (modal_windows_.empty())
return;
for (WmWindow* window : modal_windows_)
window->SetBounds(GetCenteredAndOrFittedBounds(window));
}
gfx::Rect SystemModalContainerLayoutManager::GetUsableDialogArea() const {
// Instead of resizing the system modal container, we move only the modal
// windows. This way we avoid flashing lines upon resize animation and if the
// keyboard will not fill left to right, the background is still covered.
gfx::Rect valid_bounds = container_->GetBounds();
keyboard::KeyboardController* keyboard_controller =
keyboard::KeyboardController::GetInstance();
if (keyboard_controller) {
gfx::Rect bounds = keyboard_controller->current_keyboard_bounds();
if (!bounds.IsEmpty()) {
valid_bounds.set_height(
std::max(0, valid_bounds.height() - bounds.height()));
}
}
return valid_bounds;
}
gfx::Rect SystemModalContainerLayoutManager::GetCenteredAndOrFittedBounds(
const WmWindow* window) {
gfx::Rect target_bounds;
gfx::Rect usable_area = GetUsableDialogArea();
if (windows_to_center_.count(window) > 0) {
// Keep the dialog centered if it was centered before.
target_bounds = usable_area;
target_bounds.ClampToCenteredSize(window->GetBounds().size());
} else {
// Keep the dialog within the usable area.
target_bounds = window->GetBounds();
target_bounds.AdjustToFit(usable_area);
}
if (usable_area != container_->GetBounds()) {
// Don't clamp the dialog for the keyboard. Keep the size as it is but make
// sure that the top remains visible.
// TODO(skuhne): M37 should add over scroll functionality to address this.
target_bounds.set_size(window->GetBounds().size());
}
return target_bounds;
}
bool SystemModalContainerLayoutManager::IsBoundsCentered(
const gfx::Rect& bounds) const {
gfx::Point window_center = bounds.CenterPoint();
gfx::Point container_center = GetUsableDialogArea().CenterPoint();
return std::abs(window_center.x() - container_center.x()) <
kCenterPixelDelta &&
std::abs(window_center.y() - container_center.y()) < kCenterPixelDelta;
}
} // namespace ash