| // 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 "ash/public/cpp/shell_window_ids.h" |
| #include "ash/shell.h" |
| #include "ash/wm/window_mirror_view.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_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/paint_context.h" |
| #include "ui/compositor_extra/shadow.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: |
| explicit DragWindowDetails(const display::Display& display) |
| : root_window_(Shell::GetRootWindowForDisplayId(display.id())) {} |
| DragWindowDetails(const DragWindowDetails&) = delete; |
| DragWindowDetails& operator=(const DragWindowDetails&) = delete; |
| ~DragWindowDetails() = default; |
| |
| void Update(aura::Window* original_window, |
| bool is_touch_dragging, |
| const base::Optional<gfx::Rect>& shadow_bounds) { |
| const float opacity = |
| GetDragWindowOpacity(root_window_, original_window, is_touch_dragging); |
| if (opacity == 0.f) { |
| shadow_.reset(); |
| widget_.reset(); |
| return; |
| } |
| |
| if (!widget_) |
| CreateDragWindow(original_window, shadow_bounds); |
| |
| gfx::Rect bounds = original_window->bounds(); |
| aura::Window* window = widget_->GetNativeWindow(); |
| aura::Window::ConvertRectToTarget(original_window->parent(), |
| window->parent(), &bounds); |
| window->SetBounds(bounds); |
| window->SetTransform(original_window->transform()); |
| widget_->SetOpacity(opacity); |
| } |
| |
| private: |
| friend class DragWindowController; |
| |
| void CreateDragWindow(aura::Window* original_window, |
| const base::Optional<gfx::Rect>& shadow_bounds) { |
| DCHECK(!widget_); |
| views::Widget::InitParams params; |
| params.type = views::Widget::InitParams::TYPE_POPUP; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; |
| params.layer_type = ui::LAYER_NOT_DRAWN; |
| params.name = "DragWindow"; |
| params.activatable = views::Widget::InitParams::Activatable::ACTIVATABLE_NO; |
| params.accept_events = false; |
| const int parent_id = original_window->parent()->id(); |
| params.parent = root_window_->GetChildById(parent_id); |
| |
| widget_ = std::make_unique<views::Widget>(); |
| widget_->set_focus_on_creation(false); |
| widget_->Init(std::move(params)); |
| |
| // TODO(crbug.com/1026746): Change this to WindowPreviewView. |
| // WindowPreviewView can show transient children, but currently does not |
| // show popups due to performance reasons. WindowPreviewView also needs to |
| // be modified so that it can optionally be clipped to the main window's |
| // bounds. |
| widget_->SetContentsView(std::make_unique<WindowMirrorView>( |
| original_window, /*trilinear_filtering_on_init=*/false, |
| /*show_non_client_view=*/true)); |
| |
| aura::Window* window = widget_->GetNativeWindow(); |
| window->set_id(kShellWindowId_PhantomWindow); |
| window->SetProperty(aura::client::kAnimationsDisabledKey, true); |
| gfx::Rect bounds = original_window->bounds(); |
| ::wm::ConvertRectToScreen(original_window->parent(), &bounds); |
| window->SetBounds(bounds); |
| |
| if (shadow_bounds) { |
| shadow_ = std::make_unique<ui::Shadow>(); |
| shadow_->Init(::wm::kShadowElevationActiveWindow); |
| shadow_->SetContentBounds(*shadow_bounds); |
| widget_->GetLayer()->Add(shadow_->layer()); |
| } else { |
| ::wm::SetShadowElevation(window, ::wm::kShadowElevationActiveWindow); |
| } |
| |
| // Show the widget the setup is done. |
| widget_->Show(); |
| } |
| |
| // The root window of |widget_|. |
| aura::Window* root_window_; |
| |
| // Contains a WindowMirrorView which is a copy of the original window. |
| std::unique_ptr<views::Widget> widget_; |
| |
| // Optional custom shadow if one is given. |
| std::unique_ptr<ui::Shadow> shadow_; |
| }; |
| |
| DragWindowController::DragWindowController( |
| aura::Window* window, |
| bool is_touch_dragging, |
| const base::Optional<gfx::Rect>& shadow_bounds) |
| : window_(window), |
| is_touch_dragging_(is_touch_dragging), |
| shadow_bounds_(shadow_bounds), |
| old_opacity_(window->layer()->opacity()) { |
| window->layer()->SetOpacity(1.f); |
| |
| 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)); |
| } |
| } |
| |
| 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_, shadow_bounds_); |
| } |
| |
| int DragWindowController::GetDragWindowsCountForTest() const { |
| int count = 0; |
| for (const std::unique_ptr<DragWindowDetails>& details : drag_windows_) { |
| if (details->widget_) |
| count++; |
| } |
| return count; |
| } |
| |
| const aura::Window* DragWindowController::GetDragWindowForTest( |
| size_t index) const { |
| for (const std::unique_ptr<DragWindowDetails>& details : drag_windows_) { |
| if (details->widget_) { |
| if (index == 0) |
| return details->widget_->GetNativeWindow(); |
| index--; |
| } |
| } |
| return nullptr; |
| } |
| |
| const ui::Shadow* DragWindowController::GetDragWindowShadowForTest( |
| size_t index) const { |
| for (const std::unique_ptr<DragWindowDetails>& details : drag_windows_) { |
| if (details->widget_) { |
| if (index == 0) |
| return details->shadow_.get(); |
| index--; |
| } |
| } |
| return nullptr; |
| } |
| |
| void DragWindowController::RequestLayerPaintForTest() { |
| auto list = base::MakeRefCounted<cc::DisplayItemList>(); |
| ui::PaintContext context(list.get(), 1.0f, gfx::Rect(), |
| window_->GetHost()->compositor()->is_pixel_canvas()); |
| for (auto& details : drag_windows_) { |
| std::vector<ui::Layer*> layers; |
| layers.push_back(details->widget_->GetLayer()); |
| 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 |