blob: 65900bd410670b1e5f344c169d651108872c4d62 [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/wm/drag_window_controller.h"
#include <algorithm>
#include <memory>
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/wm/window_util.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/hit_test.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/paint_context.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/display.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/shadow_types.h"
#include "ui/wm/core/window_util.h"
namespace ash {
// This keeps track of the drag window's state. It creates/destroys/updates
// bounds and opacity based on the current bounds.
class DragWindowController::DragWindowDetails : public aura::WindowDelegate {
public:
DragWindowDetails(const display::Display& display,
aura::Window* original_window)
: root_window_(Shell::GetRootWindowForDisplayId(display.id())) {}
~DragWindowDetails() override {
delete drag_window_;
DCHECK(!drag_window_);
}
void Update(aura::Window* original_window,
const gfx::Rect& bounds_in_screen,
const gfx::Point& drag_location_in_screen) {
gfx::Rect root_bounds_in_screen = root_window_->GetBoundsInScreen();
if (!root_bounds_in_screen.Intersects(bounds_in_screen)) {
delete drag_window_;
// Make sure drag_window_ is reset so that new drag window will be created
// when it becomes necessary again.
DCHECK(!drag_window_);
layer_owner_.reset();
return;
}
if (!drag_window_)
CreateDragWindow(original_window, bounds_in_screen);
gfx::Rect bounds_in_root = bounds_in_screen;
::wm::ConvertRectFromScreen(drag_window_->parent(), &bounds_in_root);
drag_window_->SetBounds(bounds_in_root);
if (root_bounds_in_screen.Contains(drag_location_in_screen)) {
SetOpacity(original_window, 1.f);
} else {
drag_window_->SetBounds(bounds_in_root);
gfx::Rect visible_bounds = root_bounds_in_screen;
visible_bounds.Intersect(bounds_in_screen);
SetOpacity(original_window,
GetDragWindowOpacity(bounds_in_screen, visible_bounds));
}
}
private:
friend class DragWindowController;
void CreateDragWindow(aura::Window* original_window,
const gfx::Rect& bounds_in_screen) {
DCHECK(!drag_window_);
original_window_ = original_window;
drag_window_ = new aura::Window(this);
int parent_id = original_window->parent()->id();
aura::Window* container = root_window_->GetChildById(parent_id);
drag_window_->SetType(aura::client::WINDOW_TYPE_POPUP);
drag_window_->SetTransparent(true);
drag_window_->Init(ui::LAYER_TEXTURED);
drag_window_->SetName("DragWindow");
drag_window_->set_id(kShellWindowId_PhantomWindow);
drag_window_->SetProperty(aura::client::kAnimationsDisabledKey, true);
container->AddChild(drag_window_);
drag_window_->SetBounds(bounds_in_screen);
SetShadowElevation(drag_window_, ::wm::ShadowElevation::LARGE);
RecreateWindowLayers(original_window);
layer_owner_->root()->SetVisible(true);
drag_window_->layer()->Add(layer_owner_->root());
drag_window_->layer()->StackAtTop(layer_owner_->root());
// Show the widget after all the setups.
drag_window_->Show();
// Fade the window in.
ui::Layer* drag_layer = drag_window_->layer();
drag_layer->SetOpacity(0);
ui::ScopedLayerAnimationSettings scoped_setter(drag_layer->GetAnimator());
drag_layer->SetOpacity(1);
}
void RecreateWindowLayers(aura::Window* original_window) {
DCHECK(!layer_owner_.get());
layer_owner_ = ::wm::MirrorLayers(original_window, true /* sync_bounds */);
// Place the layer at (0, 0) of the DragWindowController's window.
gfx::Rect layer_bounds = layer_owner_->root()->bounds();
layer_bounds.set_origin(gfx::Point(0, 0));
layer_owner_->root()->SetBounds(layer_bounds);
layer_owner_->root()->SetVisible(false);
}
void SetOpacity(const aura::Window* original_window, float opacity) {
ui::Layer* layer = drag_window_->layer();
ui::ScopedLayerAnimationSettings scoped_setter(layer->GetAnimator());
layer->SetOpacity(opacity);
layer_owner_->root()->SetOpacity(1.0f);
}
// aura::WindowDelegate:
gfx::Size GetMinimumSize() const override { return gfx::Size(); }
gfx::Size GetMaximumSize() const override { return gfx::Size(); }
void OnBoundsChanged(const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) override {}
gfx::NativeCursor GetCursor(const gfx::Point& point) override {
return gfx::kNullCursor;
}
int GetNonClientComponent(const gfx::Point& point) const override {
return HTNOWHERE;
}
bool ShouldDescendIntoChildForEventHandling(
aura::Window* child,
const gfx::Point& location) override {
return false;
}
bool CanFocus() override { return false; }
void OnCaptureLost() override {}
void OnPaint(const ui::PaintContext& context) override {}
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {}
void OnWindowDestroyed(aura::Window* window) override {}
void OnWindowTargetVisibilityChanged(bool visible) override {}
bool HasHitTestMask() const override { return false; }
void GetHitTestMask(gfx::Path* mask) const override {}
void OnWindowDestroying(aura::Window* window) override {
DCHECK_EQ(drag_window_, window);
drag_window_ = nullptr;
}
aura::Window* root_window_;
aura::Window* drag_window_ = nullptr; // Owned by the container.
aura::Window* original_window_ = nullptr;
// The copy of window_->layer() and its descendants.
std::unique_ptr<ui::LayerTreeOwner> layer_owner_;
DISALLOW_COPY_AND_ASSIGN(DragWindowDetails);
};
// static
float DragWindowController::GetDragWindowOpacity(
const gfx::Rect& window_bounds,
const gfx::Rect& visible_bounds) {
// The maximum opacity of the drag phantom window.
static const float kMaxOpacity = 0.8f;
return kMaxOpacity * visible_bounds.size().GetArea() /
window_bounds.size().GetArea();
}
DragWindowController::DragWindowController(aura::Window* window)
: window_(window) {
DCHECK(drag_windows_.empty());
display::Screen* screen = display::Screen::GetScreen();
display::Display current = screen->GetDisplayNearestWindow(window_);
for (const display::Display& display : screen->GetAllDisplays()) {
if (current.id() == display.id())
continue;
drag_windows_.push_back(
std::make_unique<DragWindowDetails>(display, window_));
}
}
DragWindowController::~DragWindowController() = default;
void DragWindowController::Update(const gfx::Rect& bounds_in_screen,
const gfx::Point& drag_location_in_screen) {
for (std::unique_ptr<DragWindowDetails>& details : drag_windows_)
details->Update(window_, bounds_in_screen, drag_location_in_screen);
}
int DragWindowController::GetDragWindowsCountForTest() const {
int count = 0;
for (const std::unique_ptr<DragWindowDetails>& details : drag_windows_) {
if (details->drag_window_)
count++;
}
return count;
}
const aura::Window* DragWindowController::GetDragWindowForTest(
size_t index) const {
for (const std::unique_ptr<DragWindowDetails>& details : drag_windows_) {
if (details->drag_window_) {
if (index == 0)
return details->drag_window_;
index--;
}
}
return nullptr;
}
const ui::LayerTreeOwner* DragWindowController::GetDragLayerOwnerForTest(
size_t index) const {
for (const std::unique_ptr<DragWindowDetails>& details : drag_windows_) {
if (details->layer_owner_) {
if (index == 0)
return details->layer_owner_.get();
index--;
}
}
return nullptr;
}
void DragWindowController::RequestLayerPaintForTest() {
ui::PaintContext context(nullptr, 1.0f, gfx::Rect(),
window_->GetHost()->compositor()->is_pixel_canvas());
for (auto& details : drag_windows_) {
std::vector<ui::Layer*> layers;
layers.push_back(details->drag_window_->layer());
while (layers.size()) {
ui::Layer* layer = layers.back();
layers.pop_back();
if (layer->delegate())
layer->delegate()->OnPaintLayer(context);
for (auto* child : layer->children())
layers.push_back(child);
}
}
}
} // namespace ash