blob: b32dd01ebf064a1610e0932956ab547549462a32 [file] [log] [blame]
// Copyright 2019 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/desks/desk_preview_view.h"
#include <memory>
#include <utility>
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/wallpaper/wallpaper_base_view.h"
#include "ash/wm/desks/desk_mini_view.h"
#include "ash/wm/desks/desks_bar_view.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_highlight_item_border.h"
#include "ash/wm/workspace/backdrop_controller.h"
#include "ash/wm/workspace/workspace_layout_manager.h"
#include "ash/wm/workspace_controller.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/numerics/ranges.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/layer_type.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/views/border.h"
namespace ash {
namespace {
// In non-compact layouts, the height of the preview is a percentage of the
// total display height, with a max of |kDeskPreviewMaxHeight| dips and a min of
// |kDeskPreviewMinHeight| dips.
constexpr int kRootHeightDivider = 12;
constexpr int kRootHeightDividerForSmallScreen = 8;
constexpr int kDeskPreviewMaxHeight = 140;
constexpr int kDeskPreviewMinHeight = 48;
constexpr int kUseSmallerHeightDividerWidthThreshold = 600;
// The corner radius of the border in dips.
constexpr int kBorderCornerRadius = 6;
// The rounded corner radii, also in dips.
constexpr int kCornerRadius = 4;
constexpr gfx::RoundedCornersF kCornerRadii(kCornerRadius);
constexpr int kShadowElevation = 4;
// Holds data about the original desk's layers to determine what we should do
// when we attempt to mirror those layers.
struct LayerData {
// If true, the layer won't be mirrored in the desk's mirrored contents. For
// example windows created by overview mode to hold the OverviewItemView,
// or minimized windows' layers, should all be skipped.
bool should_skip_layer = false;
// If true, we will force the mirror layers to be visible even if the source
// layers are not, and we will disable visibility change synchronization
// between the source and mirror layers.
// This is used, for example, for the desks container windows whose mirrors
// should always be visible (even for inactive desks) to be able to see their
// contents in the mini_views.
bool should_force_mirror_visible = false;
// If true, transformations will be cleared for this layer. This is used,
// for example, for visible on all desk windows to clear their overview
// transformation since they don't belong to inactive desks.
bool should_clear_transform = false;
};
// Returns true if |window| can be shown in the desk's preview according to its
// multi-profile ownership status (i.e. can only be shown if it belongs to the
// active user).
bool CanShowWindowForMultiProfile(aura::Window* window) {
aura::Window* window_to_check = window;
// If |window| is a backdrop, check the window which has this backdrop
// instead.
WorkspaceController* workspace_controller =
GetWorkspaceControllerForContext(window_to_check);
if (workspace_controller) {
BackdropController* backdrop_controller =
workspace_controller->layout_manager()->backdrop_controller();
if (backdrop_controller->backdrop_window() == window_to_check)
window_to_check = backdrop_controller->window_having_backdrop();
}
return window_util::ShouldShowForCurrentUser(window_to_check);
}
// Returns the LayerData entry for |target_layer| in |layer_data|. Returns an
// empty LayerData struct if not found.
const LayerData GetLayerDataEntry(
const base::flat_map<ui::Layer*, LayerData>& layers_data,
ui::Layer* target_layer) {
const auto iter = layers_data.find(target_layer);
return iter == layers_data.end() ? LayerData{} : iter->second;
}
// Appends clones of all the visible on all desks windows' layers to
// |out_desk_container_children|. Should only be called if
// |visible_on_all_desks_windows| is not empty.
void AppendVisibleOnAllDesksWindowsToDeskLayer(
const base::flat_set<aura::Window*>& visible_on_all_desks_windows,
const base::flat_map<ui::Layer*, LayerData>& layers_data,
std::vector<ui::Layer*>* out_desk_container_children) {
DCHECK(!visible_on_all_desks_windows.empty());
auto mru_windows =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
for (auto* window : visible_on_all_desks_windows) {
const LayerData layer_data =
GetLayerDataEntry(layers_data, window->layer());
if (layer_data.should_skip_layer)
continue;
auto window_iter =
std::find(mru_windows.begin(), mru_windows.end(), window);
if (window_iter == mru_windows.end())
continue;
auto closest_window_below_iter = std::next(window_iter);
while (closest_window_below_iter != mru_windows.end() &&
!base::Contains(*out_desk_container_children,
(*closest_window_below_iter)->layer())) {
// Find the closest window to |window| in the MRU tracker whose layer also
// is in |out_desk_container_children|. This window will be used to
// determine the stacking order of the visible on all desks window in the
// preview view.
closest_window_below_iter = std::next(closest_window_below_iter);
}
auto insertion_point_iter =
closest_window_below_iter == mru_windows.end()
? out_desk_container_children->begin()
: std::next(std::find(out_desk_container_children->begin(),
out_desk_container_children->end(),
(*closest_window_below_iter)->layer()));
out_desk_container_children->insert(insertion_point_iter, window->layer());
}
}
// Recursively mirrors |source_layer| and its children and adds them as children
// of |parent|, taking into account the given |layers_data|. If the layer data
// of |source_layer| has |should_clear_transform| set to true, the transforms of
// its mirror layers will be reset to identity.
void MirrorLayerTree(ui::Layer* source_layer,
ui::Layer* parent,
const base::flat_map<ui::Layer*, LayerData>& layers_data,
const base::flat_set<aura::Window*>&
visible_on_all_desks_windows_to_mirror) {
const LayerData layer_data = GetLayerDataEntry(layers_data, source_layer);
if (layer_data.should_skip_layer)
return;
auto* mirror = source_layer->Mirror().release();
parent->Add(mirror);
std::vector<ui::Layer*> children = source_layer->children();
if (!visible_on_all_desks_windows_to_mirror.empty()) {
// Windows that are visible on all desks should show up in each desk
// preview so for inactive desks, we need to append the layers of visible on
// all desks windows.
AppendVisibleOnAllDesksWindowsToDeskLayer(
visible_on_all_desks_windows_to_mirror, layers_data, &children);
}
for (auto* child : children) {
// Visible on all desks windows only needed to be added to the subtree once
// so use an empty set for subsequent calls.
MirrorLayerTree(child, mirror, layers_data,
base::flat_set<aura::Window*>());
}
mirror->set_sync_bounds_with_source(true);
if (layer_data.should_force_mirror_visible) {
mirror->SetVisible(true);
mirror->SetOpacity(1);
mirror->set_sync_visibility_with_source(false);
}
if (layer_data.should_clear_transform)
mirror->SetTransform(gfx::Transform());
}
// Gathers the needed data about the layers in the subtree rooted at the layer
// of the given |window|, and fills |out_layers_data|.
void GetLayersData(aura::Window* window,
base::flat_map<ui::Layer*, LayerData>* out_layers_data) {
auto& layer_data = (*out_layers_data)[window->layer()];
// Windows may be explicitly set to be skipped in mini_views such as those
// created for overview mode purposes.
// TODO(afakhry): Exclude exo's root surface, since it's a place holder and
// doesn't have any content. See `exo::SurfaceTreeHost::SetRootSurface()`.
if (window->GetProperty(kHideInDeskMiniViewKey)) {
layer_data.should_skip_layer = true;
return;
}
// Minimized windows should not show up in the mini_view.
auto* window_state = WindowState::Get(window);
if (window_state && window_state->IsMinimized()) {
layer_data.should_skip_layer = true;
return;
}
if (!CanShowWindowForMultiProfile(window)) {
layer_data.should_skip_layer = true;
return;
}
// Windows transformed into position in the overview mode grid should be
// mirrored and the transforms of the mirrored layers should be reset to
// identity.
if (window->GetProperty(kForceVisibleInMiniViewKey))
layer_data.should_force_mirror_visible = true;
// Visible on all desks windows aren't children of inactive desk's container
// so mark them explicitly to clear overview transforms. Additionally, windows
// in overview mode are transformed into their positions in the grid, but we
// want to show a preview of the windows in their untransformed state.
if (window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey) ||
desks_util::IsDeskContainer(window->parent())) {
layer_data.should_clear_transform = true;
}
for (auto* child : window->children())
GetLayersData(child, out_layers_data);
}
} // namespace
// -----------------------------------------------------------------------------
// DeskPreviewView::ShadowRenderer
// Layer delegate which handles drawing a shadow around DeskPreviewView.
class DeskPreviewView::ShadowRenderer : public ui::LayerDelegate {
public:
ShadowRenderer()
: shadow_values_(gfx::ShadowValue::MakeMdShadowValues(kShadowElevation)) {
}
~ShadowRenderer() override = default;
gfx::Rect GetPaintedBounds() const {
gfx::Rect total_rect(bounds_);
total_rect.Inset(gfx::ShadowValue::GetMargin(shadow_values_));
return total_rect;
}
void set_bounds(const gfx::Rect& bounds) { bounds_ = bounds; }
private:
// ui::LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override {
ui::PaintRecorder recorder(context, bounds_.size());
cc::PaintFlags shadow_flags;
shadow_flags.setAntiAlias(true);
shadow_flags.setLooper(gfx::CreateShadowDrawLooper(shadow_values_));
const gfx::Rect rrect_bounds =
bounds_ - GetPaintedBounds().OffsetFromOrigin();
const auto r_rect = SkRRect::MakeRectXY(gfx::RectToSkRect(rrect_bounds),
kCornerRadius, kCornerRadius);
recorder.canvas()->sk_canvas()->clipRRect(r_rect, SkClipOp::kDifference,
/*do_anti_alias=*/true);
recorder.canvas()->sk_canvas()->drawRRect(r_rect, shadow_flags);
}
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {}
gfx::Rect bounds_;
const gfx::ShadowValues shadow_values_;
DISALLOW_COPY_AND_ASSIGN(ShadowRenderer);
};
// -----------------------------------------------------------------------------
// DeskPreviewView
DeskPreviewView::DeskPreviewView(PressedCallback callback,
DeskMiniView* mini_view)
: views::Button(std::move(callback)),
mini_view_(mini_view),
wallpaper_preview_(new DeskWallpaperPreview),
desk_mirrored_contents_view_(new views::View),
force_occlusion_tracker_visible_(
std::make_unique<aura::WindowOcclusionTracker::ScopedForceVisible>(
mini_view->GetDeskContainer())),
shadow_delegate_(std::make_unique<ShadowRenderer>()) {
DCHECK(mini_view_);
SetFocusPainter(nullptr);
ink_drop()->SetMode(views::InkDropHost::InkDropMode::OFF);
SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
SetPaintToLayer(ui::LAYER_TEXTURED);
layer()->SetFillsBoundsOpaquely(false);
layer()->SetMasksToBounds(false);
shadow_layer_.SetFillsBoundsOpaquely(false);
layer()->Add(&shadow_layer_);
shadow_layer_.set_delegate(shadow_delegate_.get());
wallpaper_preview_->SetPaintToLayer();
auto* wallpaper_preview_layer = wallpaper_preview_->layer();
wallpaper_preview_layer->SetFillsBoundsOpaquely(false);
wallpaper_preview_layer->SetRoundedCornerRadius(kCornerRadii);
wallpaper_preview_layer->SetIsFastRoundedCorner(true);
AddChildView(wallpaper_preview_);
desk_mirrored_contents_view_->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
ui::Layer* contents_view_layer = desk_mirrored_contents_view_->layer();
contents_view_layer->SetMasksToBounds(true);
contents_view_layer->SetName("Desk mirrored contents view");
contents_view_layer->SetRoundedCornerRadius(kCornerRadii);
contents_view_layer->SetIsFastRoundedCorner(true);
AddChildView(desk_mirrored_contents_view_);
auto border = std::make_unique<WmHighlightItemBorder>(kBorderCornerRadius);
border_ptr_ = border.get();
SetBorder(std::move(border));
RecreateDeskContentsMirrorLayers();
}
DeskPreviewView::~DeskPreviewView() = default;
// static
int DeskPreviewView::GetHeight(aura::Window* root) {
DCHECK(root);
DCHECK(root->IsRootWindow());
const int height_divider =
root->bounds().width() <= kUseSmallerHeightDividerWidthThreshold
? kRootHeightDividerForSmallScreen
: kRootHeightDivider;
return base::ClampToRange(root->bounds().height() / height_divider,
kDeskPreviewMinHeight, kDeskPreviewMaxHeight);
}
void DeskPreviewView::SetBorderColor(SkColor color) {
border_ptr_->set_color(color);
SchedulePaint();
}
void DeskPreviewView::OnRemovingDesk() {
// Since the mini view has a remove animation, we don't want this desk preview
// to be pressed while it's animating. The desk will have already be removed
// after this.
SetCallback(views::Button::PressedCallback());
}
void DeskPreviewView::RecreateDeskContentsMirrorLayers() {
auto* desk_container = mini_view_->GetDeskContainer();
DCHECK(desk_container);
DCHECK(desk_container->layer());
// Mirror the layer tree of the desk container.
auto mirrored_content_root_layer =
std::make_unique<ui::Layer>(ui::LAYER_NOT_DRAWN);
mirrored_content_root_layer->SetName("mirrored contents root layer");
base::flat_map<ui::Layer*, LayerData> layers_data;
GetLayersData(desk_container, &layers_data);
base::flat_set<aura::Window*> visible_on_all_desks_windows_to_mirror;
if (!desks_util::IsActiveDeskContainer(desk_container)) {
// Since visible on all desks windows reside on the active desk, only mirror
// them in the layer tree if |this| is not the preview view for the active
// desk.
visible_on_all_desks_windows_to_mirror =
Shell::Get()->desks_controller()->GetVisibleOnAllDesksWindowsOnRoot(
mini_view_->root_window());
for (auto* window : visible_on_all_desks_windows_to_mirror)
GetLayersData(window, &layers_data);
}
auto* desk_container_layer = desk_container->layer();
MirrorLayerTree(desk_container_layer, mirrored_content_root_layer.get(),
layers_data, visible_on_all_desks_windows_to_mirror);
// Add the root of the mirrored layer tree as a child of the
// |desk_mirrored_contents_view_|'s layer.
ui::Layer* contents_view_layer = desk_mirrored_contents_view_->layer();
contents_view_layer->Add(mirrored_content_root_layer.get());
// Take ownership of the mirrored layer tree.
desk_mirrored_contents_layer_tree_owner_ =
std::make_unique<ui::LayerTreeOwner>(
std::move(mirrored_content_root_layer));
Layout();
}
const char* DeskPreviewView::GetClassName() const {
return "DeskPreviewView";
}
void DeskPreviewView::Layout() {
gfx::Rect bounds = GetContentsBounds();
shadow_delegate_->set_bounds(bounds);
shadow_layer_.SetBounds(shadow_delegate_->GetPaintedBounds());
wallpaper_preview_->SetBoundsRect(bounds);
desk_mirrored_contents_view_->SetBoundsRect(bounds);
// The desk's contents mirrored layer needs to be scaled down so that it fits
// exactly in the center of the view.
const auto root_size = mini_view_->root_window()->layer()->size();
const gfx::Vector2dF scale{
static_cast<float>(bounds.width()) / root_size.width(),
static_cast<float>(bounds.height()) / root_size.height()};
wallpaper_preview_->set_centered_layout_image_scale(scale);
gfx::Transform transform;
transform.Scale(scale.x(), scale.y());
ui::Layer* desk_mirrored_contents_layer =
desk_mirrored_contents_layer_tree_owner_->root();
DCHECK(desk_mirrored_contents_layer);
desk_mirrored_contents_layer->SetTransform(transform);
Button::Layout();
}
bool DeskPreviewView::OnMousePressed(const ui::MouseEvent& event) {
mini_view_->owner_bar()->HandlePressEvent(mini_view_, event);
return Button::OnMousePressed(event);
}
bool DeskPreviewView::OnMouseDragged(const ui::MouseEvent& event) {
mini_view_->owner_bar()->HandleDragEvent(mini_view_, event);
return Button::OnMouseDragged(event);
}
void DeskPreviewView::OnMouseReleased(const ui::MouseEvent& event) {
if (!mini_view_->owner_bar()->HandleReleaseEvent(mini_view_, event))
Button::OnMouseReleased(event);
}
void DeskPreviewView::OnGestureEvent(ui::GestureEvent* event) {
DesksBarView* owner_bar = mini_view_->owner_bar();
switch (event->type()) {
// Only long press can trigger drag & drop.
case ui::ET_GESTURE_LONG_PRESS:
owner_bar->HandleLongPressEvent(mini_view_, *event);
event->SetHandled();
break;
case ui::ET_GESTURE_SCROLL_BEGIN:
FALLTHROUGH;
case ui::ET_GESTURE_SCROLL_UPDATE:
owner_bar->HandleDragEvent(mini_view_, *event);
event->SetHandled();
break;
case ui::ET_GESTURE_END:
if (owner_bar->HandleReleaseEvent(mini_view_, *event))
event->SetHandled();
break;
default:
break;
}
if (!event->handled())
Button::OnGestureEvent(event);
}
} // namespace ash