blob: 0d3a85f1056ca66ed0b833cf5c65334da610d060 [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/window_factory.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/gfx/transform_util.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/cursor_manager.h"
#include "ui/wm/core/shadow_types.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
// The maximum opacity of the drag phantom window.
constexpr float kDragPhantomMaxOpacity = 0.8f;
// Computes an opacity value for |dragged_window| or for a drag window that
// represents |dragged_window| on |root_window|.
float GetDragWindowOpacity(aura::Window* root_window,
aura::Window* dragged_window,
bool is_touch_dragging) {
// For touch dragging, the present function should only need to be used for
// the drag windows; the opacity of the original window can simply be set to 1
// in the constructor and reverted in the destructor.
DCHECK(!is_touch_dragging || dragged_window->GetRootWindow() != root_window);
// For mouse dragging, if the mouse is in |root_window|, then return 1.
if (!is_touch_dragging && Shell::Get()->cursor_manager()->GetDisplay().id() ==
display::Screen::GetScreen()
->GetDisplayNearestWindow(root_window)
.id()) {
return 1.f;
}
// Return an opacity value based on what fraction of |dragged_window| is
// contained in |root_window|.
gfx::Rect dragged_window_bounds = dragged_window->bounds();
::wm::ConvertRectToScreen(dragged_window->parent(), &dragged_window_bounds);
gfx::RectF transformed_dragged_window_bounds(dragged_window_bounds);
gfx::TransformAboutPivot(dragged_window_bounds.origin(),
dragged_window->transform())
.TransformRect(&transformed_dragged_window_bounds);
gfx::RectF visible_bounds(root_window->GetBoundsInScreen());
visible_bounds.Intersect(transformed_dragged_window_bounds);
return kDragPhantomMaxOpacity * visible_bounds.size().GetArea() /
transformed_dragged_window_bounds.size().GetArea();
}
} // namespace
// 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, bool is_touch_dragging) {
const float opacity =
GetDragWindowOpacity(root_window_, original_window, is_touch_dragging);
if (opacity == 0.f) {
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);
gfx::Rect bounds = original_window->bounds();
aura::Window::ConvertRectToTarget(original_window->parent(),
drag_window_->parent(), &bounds);
drag_window_->SetBounds(bounds);
drag_window_->SetTransform(original_window->transform());
SetOpacity(opacity);
}
private:
friend class DragWindowController;
void CreateDragWindow(aura::Window* original_window) {
DCHECK(!drag_window_);
original_window_ = original_window;
drag_window_ = window_factory::NewWindow(this).release();
int parent_id = original_window->parent()->id();
aura::Window* container = root_window_->GetChildById(parent_id);
gfx::Rect bounds = original_window->bounds();
::wm::ConvertRectToScreen(original_window->parent(), &bounds);
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);
::wm::SetShadowElevation(drag_window_, ::wm::kShadowElevationActiveWindow);
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()->SetTransform(gfx::Transform());
layer_owner_->root()->SetVisible(false);
}
void SetOpacity(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(SkPath* 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);
};
DragWindowController::DragWindowController(aura::Window* window,
bool is_touch_dragging)
: window_(window),
is_touch_dragging_(is_touch_dragging),
old_opacity_(window->layer()->opacity()) {
DCHECK(drag_windows_.empty());
display::Screen* screen = display::Screen::GetScreen();
display::Display current = screen->GetDisplayNearestWindow(window_);
window->layer()->SetOpacity(1.f);
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() {
window_->layer()->SetOpacity(old_opacity_);
}
void DragWindowController::Update() {
// For mouse dragging, update the opacity of the original window. For touch
// dragging, just leave that opacity at 1.
if (!is_touch_dragging_) {
window_->layer()->SetOpacity(GetDragWindowOpacity(
window_->GetRootWindow(), window_, /*is_touch_dragging=*/false));
}
for (std::unique_ptr<DragWindowDetails>& details : drag_windows_)
details->Update(window_, is_touch_dragging_);
}
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