blob: d81cbe5b38af2f308bd2005b7ddbf35756d0f0e2 [file] [log] [blame]
// Copyright 2013 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/overview/window_selector_item.h"
#include <algorithm>
#include <vector>
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/overview/overview_animation_type.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/overview_window_drag_controller.h"
#include "ash/wm/overview/rounded_rect_view.h"
#include "ash/wm/overview/scoped_overview_animation_settings.h"
#include "ash/wm/overview/scoped_transform_overview_window.h"
#include "ash/wm/overview/window_grid.h"
#include "ash/wm/overview/window_selector_controller.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_state.h"
#include "base/auto_reset.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor_extra/shadow.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/window/non_client_view.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/shadow_types.h"
namespace ash {
namespace {
// In the conceptual overview table, the window margin is the space reserved
// around the window within the cell. This margin does not overlap so the
// closest distance between adjacent windows will be twice this amount.
constexpr int kWindowMargin = 5;
// Cover the transformed window including the gaps between the windows with a
// transparent shield to block the input events from reaching the transformed
// window while in overview.
constexpr int kWindowSelectorMargin = kWindowMargin * 2;
// Foreground label color.
constexpr SkColor kLabelColor = SK_ColorWHITE;
// Horizontal padding for the label, on both sides.
constexpr int kHorizontalLabelPaddingDp = 12;
// Height of an item header.
constexpr int kHeaderHeightDp = 40;
// Opacity for dimmed items.
constexpr float kDimmedItemOpacity = 0.3f;
// Opacity for fading out during closing a window.
constexpr float kClosingItemOpacity = 0.8f;
// Opacity for the item header.
constexpr float kHeaderOpacity = 0.1f;
// Duration of the header and close button fade in/out when a drag is
// started/finished on a window selector item;
constexpr int kDragAnimationMs = 167;
// Before closing a window animate both the window and the caption to shrink by
// this fraction of size.
constexpr float kPreCloseScale = 0.02f;
// The size in dp of the window icon shown on the overview window next to the
// title.
constexpr gfx::Size kIconSize{24, 24};
constexpr int kCloseButtonInkDropInsetDp = 2;
// Close button color.
constexpr SkColor kCloseButtonColor = SK_ColorWHITE;
// The colors of the close button ripple.
constexpr SkColor kCloseButtonInkDropRippleColor =
SkColorSetA(kCloseButtonColor, 0x0F);
constexpr SkColor kCloseButtonInkDropRippleHighlightColor =
SkColorSetA(kCloseButtonColor, 0x14);
// The font delta of the overview window title.
constexpr int kLabelFontDelta = 2;
constexpr int kShadowElevation = 16;
// Values of the backdrop.
constexpr int kBackdropRoundingDp = 4;
constexpr SkColor kBackdropColor = SkColorSetARGB(0x24, 0xFF, 0xFF, 0xFF);
// The amount of translation an item animates by when it is closed by using
// swipe to close.
constexpr int kSwipeToCloseCloseTranslationDp = 96;
// Before dragging an overview window, the window will be scaled up
// |kPreDragScale| to indicate its selection.
constexpr float kDragWindowScale = 0.04f;
std::unique_ptr<views::Widget> CreateBackdropWidget(aura::Window* parent) {
auto widget = CreateBackgroundWidget(
/*root_window=*/nullptr, ui::LAYER_TEXTURED, kBackdropColor,
/*border_thickness=*/0, kBackdropRoundingDp, kBackdropColor,
/*initial_opacity=*/1.f, parent,
/*stack_on_top=*/false);
return widget;
}
// A Button that has a listener and listens to mouse / gesture events on the
// visible part of an overview window. Note that the drag events are only
// handled in maximized mode.
class ShieldButton : public views::Button {
public:
ShieldButton(views::ButtonListener* listener, const base::string16& name)
: views::Button(listener) {
SetAccessibleName(name);
}
~ShieldButton() override = default;
// When WindowSelectorItem (which is a ButtonListener) is destroyed, its
// |item_widget_| is allowed to stay around to complete any animations.
// Resetting the listener in all views that are targeted by events is
// necessary to prevent a crash when a user clicks on the fading out widget
// after the WindowSelectorItem has been destroyed.
void ResetListener() { listener_ = nullptr; }
// views::Button:
bool OnMousePressed(const ui::MouseEvent& event) override {
if (listener()) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
listener()->HandlePressEvent(location);
return true;
}
return views::Button::OnMousePressed(event);
}
void OnMouseReleased(const ui::MouseEvent& event) override {
if (listener()) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
listener()->HandleReleaseEvent(location);
return;
}
views::Button::OnMouseReleased(event);
}
bool OnMouseDragged(const ui::MouseEvent& event) override {
if (listener()) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
listener()->HandleDragEvent(location);
return true;
}
return views::Button::OnMouseDragged(event);
}
void OnGestureEvent(ui::GestureEvent* event) override {
if (IsSlidingOutOverviewFromShelf()) {
event->SetHandled();
return;
}
if (listener()) {
gfx::Point location(event->location());
views::View::ConvertPointToScreen(this, &location);
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN:
listener()->HandlePressEvent(location);
break;
case ui::ET_GESTURE_SCROLL_UPDATE:
listener()->HandleDragEvent(location);
break;
case ui::ET_SCROLL_FLING_START:
listener()->HandleFlingStartEvent(location,
event->details().velocity_x(),
event->details().velocity_y());
break;
case ui::ET_GESTURE_SCROLL_END:
listener()->HandleReleaseEvent(location);
break;
case ui::ET_GESTURE_LONG_PRESS:
listener()->HandleLongPressEvent(location);
break;
case ui::ET_GESTURE_TAP:
listener()->ActivateDraggedWindow();
break;
case ui::ET_GESTURE_END:
listener()->ResetDraggedWindowGesture();
break;
default:
break;
}
event->SetHandled();
return;
}
views::Button::OnGestureEvent(event);
}
WindowSelectorItem* listener() {
return static_cast<WindowSelectorItem*>(listener_);
}
protected:
// views::View:
const char* GetClassName() const override { return "ShieldButton"; }
private:
DISALLOW_COPY_AND_ASSIGN(ShieldButton);
};
} // namespace
WindowSelectorItem::OverviewCloseButton::OverviewCloseButton(
views::ButtonListener* listener)
: views::ImageButton(listener) {
SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER);
SetImage(views::Button::STATE_NORMAL,
gfx::CreateVectorIcon(kOverviewWindowCloseIcon, kCloseButtonColor));
SetImageAlignment(views::ImageButton::ALIGN_CENTER,
views::ImageButton::ALIGN_MIDDLE);
SetMinimumImageSize(gfx::Size(kHeaderHeightDp, kHeaderHeightDp));
SetAccessibleName(l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
}
WindowSelectorItem::OverviewCloseButton::~OverviewCloseButton() = default;
std::unique_ptr<views::InkDrop>
WindowSelectorItem::OverviewCloseButton::CreateInkDrop() {
std::unique_ptr<views::InkDropImpl> ink_drop =
std::make_unique<views::InkDropImpl>(this, size());
ink_drop->SetAutoHighlightMode(
views::InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE);
ink_drop->SetShowHighlightOnHover(true);
return ink_drop;
}
std::unique_ptr<views::InkDropRipple>
WindowSelectorItem::OverviewCloseButton::CreateInkDropRipple() const {
return std::make_unique<views::FloodFillInkDropRipple>(
size(), gfx::Insets(), GetInkDropCenterBasedOnLastEvent(),
kCloseButtonInkDropRippleColor, /*visible_opacity=*/1.f);
}
std::unique_ptr<views::InkDropHighlight>
WindowSelectorItem::OverviewCloseButton::CreateInkDropHighlight() const {
return std::make_unique<views::InkDropHighlight>(
gfx::PointF(GetLocalBounds().CenterPoint()),
std::make_unique<views::CircleLayerDelegate>(
kCloseButtonInkDropRippleHighlightColor, GetInkDropRadius()));
}
std::unique_ptr<views::InkDropMask>
WindowSelectorItem::OverviewCloseButton::CreateInkDropMask() const {
return std::make_unique<views::CircleInkDropMask>(
size(), GetLocalBounds().CenterPoint(), GetInkDropRadius());
}
int WindowSelectorItem::OverviewCloseButton::GetInkDropRadius() const {
return std::min(size().width(), size().height()) / 2 -
kCloseButtonInkDropInsetDp;
}
// A Container View that has a ShieldButton to listen to events. The
// ShieldButton covers most of the View except for the transparent gap between
// the windows and is visually transparent. The text label does not receive
// events, however the close button is higher in Z-order than its parent and
// receives events forwarding them to the same |listener| (i.e.
// WindowSelectorItem::ButtonPressed()).
class WindowSelectorItem::CaptionContainerView : public views::View {
public:
enum class HeaderVisibility {
kInvisible,
kCloseButtonInvisibleOnly,
kVisible,
};
CaptionContainerView(ButtonListener* listener,
views::ImageView* image_view,
views::Label* title_label,
views::Label* cannot_snap_label,
views::ImageButton* close_button)
: listener_button_(new ShieldButton(listener, title_label->text())),
image_view_(image_view),
title_label_(title_label),
cannot_snap_label_(cannot_snap_label),
close_button_(close_button) {
AddChildView(listener_button_);
header_view_ = new views::View();
views::BoxLayout* layout =
header_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(),
kHorizontalLabelPaddingDp));
if (image_view_)
header_view_->AddChildView(image_view_);
header_view_->AddChildView(title_label_);
AddChildWithLayer(header_view_, close_button_);
AddChildWithLayer(this, header_view_);
layout->SetFlexForView(title_label_, 1);
}
~CaptionContainerView() override {
// If the cannot snap container was never created, delete cannot_snap_label_
// manually.
if (!cannot_snap_container_)
delete cannot_snap_label_;
}
RoundedRectView* GetCannotSnapContainer() {
if (!cannot_snap_container_) {
// Use |cannot_snap_container_| to specify the padding surrounding
// |cannot_snap_label_| and to give the label rounded corners.
cannot_snap_container_ = new RoundedRectView(
kSplitviewLabelRoundRectRadiusDp, kSplitviewLabelBackgroundColor);
cannot_snap_container_->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical,
gfx::Insets(kSplitviewLabelVerticalInsetDp,
kSplitviewLabelHorizontalInsetDp)));
cannot_snap_container_->AddChildView(cannot_snap_label_);
cannot_snap_container_->set_can_process_events_within_subtree(false);
AddChildWithLayer(this, cannot_snap_container_);
cannot_snap_container_->layer()->SetOpacity(0.f);
Layout();
}
return cannot_snap_container_;
}
ShieldButton* listener_button() { return listener_button_; }
gfx::Rect backdrop_bounds() const { return backdrop_bounds_; }
void SetHeaderVisibility(HeaderVisibility visibility) {
DCHECK(close_button_->layer());
DCHECK(header_view_->layer());
// Make the close button invisible if the rest of the header is to be shown.
// If the rest of the header is to be hidden, make the close button visible
// as |header_view_|'s opacity will be 0.f, hiding the close button. Modify
// |close_button_|'s opacity instead of visibilty so the flex from its
// sibling views do not mess up its layout.
close_button_->layer()->SetOpacity(
visibility == HeaderVisibility::kCloseButtonInvisibleOnly ? 0.f : 1.f);
const bool visible = visibility != HeaderVisibility::kInvisible;
AnimateLayerOpacity(header_view_->layer(), visible);
}
void SetCannotSnapLabelVisibility(bool visible) {
if (!cannot_snap_container_ && !visible)
return;
DoSplitviewOpacityAnimation(
GetCannotSnapContainer()->layer(),
visible ? SPLITVIEW_ANIMATION_SELECTOR_ITEM_FADE_IN
: SPLITVIEW_ANIMATION_SELECTOR_ITEM_FADE_OUT);
}
views::View* header_view() { return header_view_; }
protected:
// views::View:
void Layout() override {
// |listener_button_| serves as a shield to prevent input events from
// reaching the transformed window in overview.
gfx::Rect bounds(GetLocalBounds());
bounds.Inset(kWindowSelectorMargin, kWindowSelectorMargin);
listener_button_->SetBoundsRect(bounds);
// Position the cannot snap label.
gfx::Size label_size = cannot_snap_label_->CalculatePreferredSize();
label_size.set_width(
std::min(label_size.width() + 2 * kSplitviewLabelHorizontalInsetDp,
bounds.width() - 2 * kSplitviewLabelHorizontalInsetDp));
label_size.set_height(
std::max(label_size.height(), kSplitviewLabelPreferredHeightDp));
const int visible_height = close_button_->GetPreferredSize().height();
backdrop_bounds_ = bounds;
backdrop_bounds_.Inset(0, visible_height, 0, 0);
if (cannot_snap_container_) {
// Position the cannot snap label in the middle of the item, minus the
// title.
gfx::Rect cannot_snap_bounds = GetLocalBounds();
cannot_snap_bounds.Inset(0, visible_height, 0, 0);
cannot_snap_bounds.ClampToCenteredSize(label_size);
cannot_snap_container_->SetBoundsRect(cannot_snap_bounds);
}
// Position the header at the top. The left should be indented to match
// the transformed window, but not the right because the close button hit
// radius should extend past the transformed window's rightmost bounds.
gfx::Rect header_bounds = GetLocalBounds();
header_bounds.Inset(kWindowSelectorMargin, kWindowSelectorMargin, 0, 0);
header_bounds.set_height(visible_height);
header_view_->SetBoundsRect(header_bounds);
}
const char* GetClassName() const override { return "CaptionContainerView"; }
private:
// Animates |layer| from 0 -> 1 opacity if |visible| and 1 -> 0 opacity
// otherwise. The tween type differs for |visible| and if |visible| is true
// there is a slight delay before the animation begins. Does not animate if
// opacity matches |visible|.
void AnimateLayerOpacity(ui::Layer* layer, bool visible) {
float target_opacity = visible ? 1.f : 0.f;
if (layer->GetTargetOpacity() == target_opacity)
return;
layer->SetOpacity(1.f - target_opacity);
{
ui::LayerAnimator* animator = layer->GetAnimator();
ui::ScopedLayerAnimationSettings settings(animator);
settings.SetPreemptionStrategy(
ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
if (visible) {
animator->SchedulePauseForProperties(
base::TimeDelta::FromMilliseconds(kDragAnimationMs),
ui::LayerAnimationElement::OPACITY);
}
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kDragAnimationMs));
settings.SetTweenType(visible ? gfx::Tween::LINEAR_OUT_SLOW_IN
: gfx::Tween::FAST_OUT_LINEAR_IN);
layer->SetOpacity(target_opacity);
}
}
// Helper function to add a child view to a parent view and make it paint to
// layer.
static void AddChildWithLayer(views::View* parent, views::View* child) {
child->SetPaintToLayer();
child->layer()->SetFillsBoundsOpaquely(false);
parent->AddChildView(child);
};
ShieldButton* listener_button_;
views::ImageView* image_view_;
views::Label* title_label_;
views::Label* cannot_snap_label_;
RoundedRectView* cannot_snap_container_ = nullptr;
views::ImageButton* close_button_;
// View which contains the icon, title and close button.
views::View* header_view_ = nullptr;
gfx::Rect backdrop_bounds_;
DISALLOW_COPY_AND_ASSIGN(CaptionContainerView);
};
WindowSelectorItem::WindowSelectorItem(aura::Window* window,
WindowSelector* window_selector,
WindowGrid* window_grid)
: root_window_(window->GetRootWindow()),
transform_window_(this, window),
close_button_(new OverviewCloseButton(this)),
window_selector_(window_selector),
window_grid_(window_grid) {
CreateWindowLabel(window->GetTitle());
GetWindow()->AddObserver(this);
if (GetWindowDimensionsType() !=
ScopedTransformOverviewWindow::GridWindowFillMode::kNormal) {
backdrop_widget_ = CreateBackdropWidget(window->parent());
}
GetWindow()->SetProperty(ash::kIsShowingInOverviewKey, true);
}
WindowSelectorItem::~WindowSelectorItem() {
GetWindow()->RemoveObserver(this);
GetWindow()->ClearProperty(ash::kIsShowingInOverviewKey);
}
aura::Window* WindowSelectorItem::GetWindow() {
return transform_window_.window();
}
aura::Window* WindowSelectorItem::GetWindowForStacking() {
// If the window is minimized, stack |widget_window| above the minimized
// window, otherwise the minimized window will cover |widget_window|. The
// minimized is created with the same parent as the original window, just
// like |item_widget_|, so there is no need to reparent.
return transform_window_.minimized_widget()
? transform_window_.minimized_widget()->GetNativeWindow()
: GetWindow();
}
bool WindowSelectorItem::Contains(const aura::Window* target) const {
return transform_window_.Contains(target);
}
void WindowSelectorItem::RestoreWindow(bool reset_transform) {
caption_container_view_->listener_button()->ResetListener();
close_button_->ResetListener();
transform_window_.RestoreWindow(
reset_transform,
window_selector_->enter_exit_overview_type() ==
WindowSelector::EnterExitOverviewType::kWindowsMinimized);
}
void WindowSelectorItem::EnsureVisible() {
transform_window_.EnsureVisible();
}
void WindowSelectorItem::Shutdown() {
if (transform_window_.GetTopInset()) {
// Activating a window (even when it is the window that was active before
// overview) results in stacking it at the top. Maintain the label window
// stacking position above the item to make the header transformation more
// gradual upon exiting the overview mode.
aura::Window* widget_window = item_widget_->GetNativeWindow();
// |widget_window| was originally created in the same container as the
// |transform_window_| but when closing overview the |transform_window_|
// could have been reparented if a drag was active. Only change stacking
// if the windows still belong to the same container.
if (widget_window->parent() == transform_window_.window()->parent()) {
widget_window->parent()->StackChildAbove(widget_window,
transform_window_.window());
}
}
// On swiping from the shelf, the caller handles the animation via calls to
// UpdateYAndOpacity, so do not additional fade out or slide animation to the
// window.
if (window_selector_->enter_exit_overview_type() ==
WindowSelector::EnterExitOverviewType::kSwipeFromShelf) {
return;
}
// Fade out the item widget. This animation continues past the lifetime
// of |this|.
const bool slide = window_selector_->enter_exit_overview_type() ==
WindowSelector::EnterExitOverviewType::kWindowsMinimized;
FadeOutWidgetAndMaybeSlideOnExit(
std::move(item_widget_),
slide ? OVERVIEW_ANIMATION_EXIT_TO_HOME_LAUNCHER
: OVERVIEW_ANIMATION_EXIT_OVERVIEW_MODE_FADE_OUT,
slide);
}
void WindowSelectorItem::PrepareForOverview() {
transform_window_.PrepareForOverview();
RestackItemWidget();
UpdateHeaderLayout(HeaderFadeInMode::kEnter, OVERVIEW_ANIMATION_NONE);
}
void WindowSelectorItem::SlideWindowIn() {
// |transform_window_|'s |minimized_widget| is non null because this only gets
// called if we see the home launcher on enter (all windows are minimized).
DCHECK(item_widget_);
DCHECK(transform_window_.minimized_widget());
FadeInWidgetAndMaybeSlideOnEnter(item_widget_.get(),
OVERVIEW_ANIMATION_ENTER_FROM_HOME_LAUNCHER,
/*slide=*/true);
FadeInWidgetAndMaybeSlideOnEnter(transform_window_.minimized_widget(),
OVERVIEW_ANIMATION_ENTER_FROM_HOME_LAUNCHER,
/*slide=*/true);
}
void WindowSelectorItem::UpdateYPositionAndOpacity(
int new_grid_y,
float opacity,
WindowSelector::UpdateAnimationSettingsCallback callback) {
// Animate the window selector widget and the window itself.
// TODO(sammiequon): Investigate if we can combine with
// FadeInWidgetAndMaybeSlideOnEnter and animate the transient children too.
// Also when animating we should remove shadow and rounded corners.
std::vector<ui::Layer*> animation_layers = {
GetWindowForStacking()->layer(),
item_widget_->GetNativeWindow()->layer()};
for (auto* layer : animation_layers) {
layer->GetAnimator()->StopAnimating();
std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
if (!callback.is_null()) {
settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
layer->GetAnimator());
callback.Run(settings.get(), /*observe=*/false);
}
layer->SetOpacity(opacity);
// Alter the y-translation. Offset by the window location relative to the
// grid.
const int offset = target_bounds_.y() + kHeaderHeightDp + kWindowMargin;
gfx::Transform transform = layer->transform();
transform.matrix().setFloat(1, 3, static_cast<float>(offset + new_grid_y));
layer->SetTransform(transform);
}
}
float WindowSelectorItem::GetItemScale(const gfx::Size& size) {
gfx::Size inset_size(size.width(), size.height() - 2 * kWindowMargin);
return ScopedTransformOverviewWindow::GetItemScale(
GetTargetBoundsInScreen().size(), inset_size,
transform_window_.GetTopInset(),
close_button_->GetPreferredSize().height());
}
gfx::Rect WindowSelectorItem::GetTargetBoundsInScreen() const {
return ::ash::GetTargetBoundsInScreen(transform_window_.GetOverviewWindow());
}
gfx::Rect WindowSelectorItem::GetTransformedBounds() const {
return transform_window_.GetTransformedBounds();
}
void WindowSelectorItem::SetBounds(const gfx::Rect& target_bounds,
OverviewAnimationType animation_type) {
if (in_bounds_update_)
return;
// Do not animate if the resulting bounds does not change. The original
// window may change bounds so we still need to call SetItemBounds to update
// the window transform.
OverviewAnimationType new_animation_type = animation_type;
if (target_bounds == target_bounds_)
new_animation_type = OVERVIEW_ANIMATION_NONE;
base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true);
// If |target_bounds_| is empty, this is the first update. Let
// UpdateHeaderLayout know, as we do not want |item_widget_| to be animated
// with the window.
HeaderFadeInMode mode = target_bounds_.IsEmpty()
? HeaderFadeInMode::kFirstUpdate
: HeaderFadeInMode::kUpdate;
target_bounds_ = target_bounds;
gfx::Rect inset_bounds(target_bounds);
inset_bounds.Inset(kWindowMargin, kWindowMargin);
// Do not animate if entering when the window is minimized, as it will be
// faded in. We still want to animate if the position is changed after
// entering.
if (wm::GetWindowState(GetWindow())->IsMinimized() &&
mode == HeaderFadeInMode::kFirstUpdate) {
new_animation_type = OVERVIEW_ANIMATION_NONE;
}
SetItemBounds(inset_bounds, new_animation_type);
// SetItemBounds is called before UpdateHeaderLayout so the header can
// properly use the updated windows bounds.
UpdateHeaderLayout(mode, new_animation_type);
// Shadow is normally set after an animation is finished. In the case of no
// animations, manually set the shadow. Shadow relies on both the window
// transform and |item_widget_|'s new bounds so set it after SetItemBounds
// and UpdateHeaderLayout. Do not apply the shadow for drop target.
if (new_animation_type == OVERVIEW_ANIMATION_NONE) {
SetShadowBounds(
window_grid_->IsDropTargetWindow(GetWindow())
? base::nullopt
: base::make_optional(transform_window_.GetTransformedBounds()));
}
UpdateBackdropBounds();
}
void WindowSelectorItem::SendAccessibleSelectionEvent() {
caption_container_view_->listener_button()->NotifyAccessibilityEvent(
ax::mojom::Event::kSelection, true);
}
void WindowSelectorItem::AnimateAndCloseWindow(bool up) {
base::RecordAction(base::UserMetricsAction("WindowSelector_SwipeToClose"));
animating_to_close_ = true;
window_selector_->PositionWindows(/*animate=*/true);
caption_container_view_->listener_button()->ResetListener();
close_button_->ResetListener();
int translation_y = kSwipeToCloseCloseTranslationDp * (up ? -1 : 1);
gfx::Transform transform;
transform.Translate(gfx::Vector2d(0, translation_y));
auto animate_window = [this](aura::Window* window,
const gfx::Transform& transform, bool observe) {
ScopedOverviewAnimationSettings settings(
OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM, window);
gfx::Transform original_transform = window->transform();
original_transform.ConcatTransform(transform);
window->SetTransform(original_transform);
if (observe)
settings.AddObserver(this);
};
AnimateOpacity(0.0, OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM);
animate_window(item_widget_->GetNativeWindow(), transform, false);
animate_window(GetWindowForStacking(), transform, true);
}
void WindowSelectorItem::CloseWindow() {
gfx::Rect inset_bounds(target_bounds_);
inset_bounds.Inset(target_bounds_.width() * kPreCloseScale,
target_bounds_.height() * kPreCloseScale);
// Scale down both the window and label.
SetBounds(inset_bounds, OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM);
// First animate opacity to an intermediate value concurrently with the
// scaling animation.
AnimateOpacity(kClosingItemOpacity, OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM);
// Fade out the window and the label, effectively hiding them.
AnimateOpacity(0.0, OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM);
transform_window_.Close();
}
void WindowSelectorItem::OnMinimizedStateChanged() {
transform_window_.UpdateMirrorWindowForMinimizedState();
}
void WindowSelectorItem::UpdateCannotSnapWarningVisibility() {
// Windows which can snap will never show this warning. Or if the window is
// the drop target window, also do not show this warning.
if (Shell::Get()->split_view_controller()->CanSnap(GetWindow()) ||
window_grid_->IsDropTargetWindow(GetWindow())) {
caption_container_view_->SetCannotSnapLabelVisibility(false);
return;
}
const SplitViewController::State state =
Shell::Get()->split_view_controller()->state();
const bool visible = state == SplitViewController::LEFT_SNAPPED ||
state == SplitViewController::RIGHT_SNAPPED;
caption_container_view_->SetCannotSnapLabelVisibility(visible);
}
void WindowSelectorItem::OnSelectorItemDragStarted(WindowSelectorItem* item) {
caption_container_view_->SetHeaderVisibility(
item == this
? CaptionContainerView::HeaderVisibility::kInvisible
: CaptionContainerView::HeaderVisibility::kCloseButtonInvisibleOnly);
}
void WindowSelectorItem::OnSelectorItemDragEnded() {
caption_container_view_->SetHeaderVisibility(
CaptionContainerView::HeaderVisibility::kVisible);
}
ScopedTransformOverviewWindow::GridWindowFillMode
WindowSelectorItem::GetWindowDimensionsType() const {
return transform_window_.type();
}
void WindowSelectorItem::UpdateWindowDimensionsType() {
transform_window_.UpdateWindowDimensionsType();
if (GetWindowDimensionsType() ==
ScopedTransformOverviewWindow::GridWindowFillMode::kNormal) {
// Delete the backdrop widget, if it exists for normal windows.
if (backdrop_widget_)
backdrop_widget_.reset();
} else {
// Create the backdrop widget if needed.
if (!backdrop_widget_) {
backdrop_widget_ =
CreateBackdropWidget(transform_window_.window()->parent());
}
}
}
void WindowSelectorItem::EnableBackdropIfNeeded() {
if (GetWindowDimensionsType() ==
ScopedTransformOverviewWindow::GridWindowFillMode::kNormal) {
DisableBackdrop();
return;
}
UpdateBackdropBounds();
}
void WindowSelectorItem::DisableBackdrop() {
if (backdrop_widget_)
backdrop_widget_->Hide();
}
void WindowSelectorItem::UpdateBackdropBounds() {
if (!backdrop_widget_)
return;
gfx::Rect backdrop_bounds = caption_container_view_->backdrop_bounds();
::wm::ConvertRectToScreen(item_widget_->GetNativeWindow(), &backdrop_bounds);
backdrop_widget_->SetBounds(backdrop_bounds);
backdrop_widget_->Show();
}
gfx::Rect WindowSelectorItem::GetBoundsOfSelectedItem() {
gfx::Rect original_bounds = target_bounds();
ScaleUpSelectedItem(OVERVIEW_ANIMATION_NONE);
gfx::Rect selected_bounds = transform_window_.GetTransformedBounds();
SetBounds(original_bounds, OVERVIEW_ANIMATION_NONE);
return selected_bounds;
}
void WindowSelectorItem::ScaleUpSelectedItem(
OverviewAnimationType animation_type) {
gfx::Rect scaled_bounds(target_bounds());
scaled_bounds.Inset(-scaled_bounds.width() * kDragWindowScale,
-scaled_bounds.height() * kDragWindowScale);
SetBounds(scaled_bounds, animation_type);
}
void WindowSelectorItem::SetDimmed(bool dimmed) {
dimmed_ = dimmed;
SetOpacity(dimmed ? kDimmedItemOpacity : 1.0f);
}
void WindowSelectorItem::RestackItemWidget() {
aura::Window* widget_window = item_widget_->GetNativeWindow();
widget_window->parent()->StackChildAbove(widget_window,
GetWindowForStacking());
}
void WindowSelectorItem::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (IsSlidingOutOverviewFromShelf())
return;
if (sender == close_button_) {
base::RecordAction(
base::UserMetricsAction("WindowSelector_OverviewCloseButton"));
if (Shell::Get()
->tablet_mode_controller()
->IsTabletModeWindowManagerEnabled()) {
base::RecordAction(
base::UserMetricsAction("Tablet_WindowCloseFromOverviewButton"));
}
CloseWindow();
return;
}
CHECK(sender == caption_container_view_->listener_button());
// For other cases, the event is handled in OverviewWindowDragController.
if (!SplitViewController::ShouldAllowSplitView())
window_selector_->SelectWindow(this);
}
void WindowSelectorItem::OnWindowBoundsChanged(
aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
if (reason == ui::PropertyChangeReason::NOT_FROM_ANIMATION)
transform_window_.ResizeMinimizedWidgetIfNeeded();
}
void WindowSelectorItem::OnWindowDestroying(aura::Window* window) {
window->RemoveObserver(this);
transform_window_.OnWindowDestroyed();
}
void WindowSelectorItem::OnWindowTitleChanged(aura::Window* window) {
// TODO(flackr): Maybe add the new title to a vector of titles so that we can
// filter any of the titles the window had while in the overview session.
label_view_->SetText(window->GetTitle());
UpdateAccessibilityName();
}
void WindowSelectorItem::OnImplicitAnimationsCompleted() {
transform_window_.Close();
}
void WindowSelectorItem::HandlePressEvent(
const gfx::Point& location_in_screen) {
// We allow switching finger while dragging, but do not allow dragging two or more items.
if (window_selector_->window_drag_controller() &&
window_selector_->window_drag_controller()->item()) {
return;
}
StartDrag();
window_selector_->InitiateDrag(this, location_in_screen);
}
void WindowSelectorItem::HandleReleaseEvent(
const gfx::Point& location_in_screen) {
if (!IsDragItem())
return;
window_grid_->SetSelectionWidgetVisibility(true);
window_selector_->CompleteDrag(this, location_in_screen);
}
void WindowSelectorItem::HandleDragEvent(const gfx::Point& location_in_screen) {
if (!IsDragItem())
return;
window_selector_->Drag(this, location_in_screen);
}
void WindowSelectorItem::HandleLongPressEvent(
const gfx::Point& location_in_screen) {
if (!SplitViewController::ShouldAllowSplitView())
return;
window_selector_->StartSplitViewDragMode(location_in_screen);
}
void WindowSelectorItem::HandleFlingStartEvent(
const gfx::Point& location_in_screen,
float velocity_x,
float velocity_y) {
window_selector_->Fling(this, location_in_screen, velocity_x, velocity_y);
}
void WindowSelectorItem::ActivateDraggedWindow() {
if (!IsDragItem())
return;
window_selector_->ActivateDraggedWindow();
}
void WindowSelectorItem::ResetDraggedWindowGesture() {
if (!IsDragItem())
return;
OnSelectorItemDragEnded();
window_selector_->ResetDraggedWindowGesture();
}
bool WindowSelectorItem::IsDragItem() {
return window_selector_->window_drag_controller() &&
window_selector_->window_drag_controller()->item() == this;
}
void WindowSelectorItem::OnDragAnimationCompleted() {
// This is function is called whenever the grid repositions its windows, but
// we only need to restack the windows if an item was being dragged around
// and then released.
if (!should_restack_on_animation_end_)
return;
should_restack_on_animation_end_ = false;
// First stack this item's window below the snapped window if split view mode
// is active.
aura::Window* dragged_window = GetWindowForStacking();
aura::Window* dragged_widget_window = item_widget_->GetNativeWindow();
aura::Window* parent_window = dragged_widget_window->parent();
if (Shell::Get()->IsSplitViewModeActive()) {
aura::Window* snapped_window =
Shell::Get()->split_view_controller()->GetDefaultSnappedWindow();
if (snapped_window->parent() == parent_window &&
dragged_window->parent() == parent_window) {
parent_window->StackChildBelow(dragged_widget_window, snapped_window);
parent_window->StackChildBelow(dragged_window, dragged_widget_window);
}
}
// Then find the window which was stacked right above this selector item's
// window before dragging and stack this selector item's window below it.
const std::vector<std::unique_ptr<WindowSelectorItem>>& selector_items =
window_grid_->window_list();
aura::Window* stacking_target = nullptr;
for (size_t index = 0; index < selector_items.size(); index++) {
if (index > 0) {
aura::Window* window = selector_items[index - 1].get()->GetWindow();
if (window->parent() == parent_window &&
dragged_window->parent() == parent_window) {
stacking_target = window;
}
}
if (selector_items[index].get() == this && stacking_target) {
parent_window->StackChildBelow(dragged_widget_window, stacking_target);
parent_window->StackChildBelow(dragged_window, dragged_widget_window);
break;
}
}
}
void WindowSelectorItem::SetShadowBounds(
base::Optional<gfx::Rect> bounds_in_screen) {
// Shadow is normally turned off during animations and reapplied when they
// are finished. On destruction, |shadow_| is cleaned up before
// |transform_window_|, which may call this function, so early exit if
// |shadow_| is nullptr.
if (!shadow_)
return;
if (bounds_in_screen == base::nullopt) {
shadow_->layer()->SetVisible(false);
return;
}
shadow_->layer()->SetVisible(true);
gfx::Rect bounds_in_item =
gfx::Rect(item_widget_->GetNativeWindow()->GetTargetBounds().size());
bounds_in_item.Inset(kWindowSelectorMargin, kWindowSelectorMargin);
bounds_in_item.Inset(0, close_button_->GetPreferredSize().height(), 0, 0);
bounds_in_item.ClampToCenteredSize(bounds_in_screen.value().size());
shadow_->SetContentBounds(bounds_in_item);
}
void WindowSelectorItem::SetOpacity(float opacity) {
item_widget_->SetOpacity(opacity);
transform_window_.SetOpacity(opacity);
}
float WindowSelectorItem::GetOpacity() {
return item_widget_->GetNativeWindow()->layer()->opacity();
}
OverviewAnimationType WindowSelectorItem::GetExitOverviewAnimationType() {
return should_animate_when_exiting_
? OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS_ON_EXIT
: OVERVIEW_ANIMATION_NONE;
}
OverviewAnimationType WindowSelectorItem::GetExitTransformAnimationType() {
return should_animate_when_exiting_ ? OVERVIEW_ANIMATION_RESTORE_WINDOW
: OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO;
}
float WindowSelectorItem::GetCloseButtonVisibilityForTesting() const {
return close_button_->layer()->opacity();
}
float WindowSelectorItem::GetTitlebarOpacityForTesting() const {
return caption_container_view_->header_view()->layer()->opacity();
}
gfx::Rect WindowSelectorItem::GetShadowBoundsForTesting() {
if (!shadow_ || !shadow_->layer()->visible())
return gfx::Rect();
return shadow_->content_bounds();
}
void WindowSelectorItem::SetItemBounds(const gfx::Rect& target_bounds,
OverviewAnimationType animation_type) {
aura::Window* window = GetWindow();
DCHECK(root_window_ == window->GetRootWindow());
gfx::Rect screen_rect = GetTargetBoundsInScreen();
// Avoid division by zero by ensuring screen bounds is not empty.
gfx::Size screen_size(screen_rect.size());
screen_size.SetToMax(gfx::Size(1, 1));
screen_rect.set_size(screen_size);
const int top_view_inset = transform_window_.GetTopInset();
const int title_height = close_button_->GetPreferredSize().height();
gfx::Rect selector_item_bounds =
transform_window_.ShrinkRectToFitPreservingAspectRatio(
screen_rect, target_bounds, top_view_inset, title_height);
// Do not set transform for drop target, set bounds instead.
if (window_grid_->IsDropTargetWindow(window)) {
window->layer()->SetBounds(selector_item_bounds);
transform_window_.GetOverviewWindow()->SetTransform(gfx::Transform());
return;
}
gfx::Transform transform = ScopedTransformOverviewWindow::GetTransformForRect(
screen_rect, selector_item_bounds);
ScopedTransformOverviewWindow::ScopedAnimationSettings animation_settings;
transform_window_.BeginScopedAnimation(animation_type, &animation_settings);
SetTransform(transform_window_.GetOverviewWindow(), transform);
}
void WindowSelectorItem::CreateWindowLabel(const base::string16& title) {
views::Widget::InitParams params_label;
params_label.type = views::Widget::InitParams::TYPE_POPUP;
params_label.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params_label.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params_label.visible_on_all_workspaces = true;
params_label.layer_type = ui::LAYER_NOT_DRAWN;
params_label.name = "OverviewModeLabel";
params_label.activatable =
views::Widget::InitParams::Activatable::ACTIVATABLE_DEFAULT;
params_label.accept_events = true;
item_widget_ = std::make_unique<views::Widget>();
params_label.parent = transform_window_.window()->parent();
item_widget_->set_focus_on_creation(false);
item_widget_->Init(params_label);
aura::Window* widget_window = item_widget_->GetNativeWindow();
// Stack the widget above the transform window so that it can block events.
widget_window->parent()->StackChildAbove(widget_window,
transform_window_.window());
// Create an image view the header icon. Tries to use the app icon, as it is
// higher resolution. If it does not exist, use the window icon. If neither
// exist, display nothing.
views::ImageView* image_view = nullptr;
gfx::ImageSkia* icon = GetWindow()->GetProperty(aura::client::kAppIconKey);
if (!icon || icon->size().IsEmpty())
icon = GetWindow()->GetProperty(aura::client::kWindowIconKey);
if (icon && !icon->size().IsEmpty()) {
image_view = new views::ImageView();
image_view->SetImage(gfx::ImageSkiaOperations::CreateResizedImage(
*icon, skia::ImageOperations::RESIZE_BEST, kIconSize));
image_view->SetSize(kIconSize);
}
label_view_ = new views::Label(title);
label_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label_view_->SetAutoColorReadabilityEnabled(false);
label_view_->SetEnabledColor(kLabelColor);
label_view_->SetSubpixelRenderingEnabled(false);
label_view_->SetFontList(gfx::FontList().Derive(
kLabelFontDelta, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
shadow_ = std::make_unique<ui::Shadow>();
shadow_->Init(kShadowElevation);
item_widget_->GetLayer()->Add(shadow_->layer());
cannot_snap_label_view_ = new views::Label(
l10n_util::GetStringUTF16(IDS_ASH_SPLIT_VIEW_CANNOT_SNAP));
cannot_snap_label_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
cannot_snap_label_view_->SetAutoColorReadabilityEnabled(false);
cannot_snap_label_view_->SetEnabledColor(kSplitviewLabelEnabledColor);
cannot_snap_label_view_->SetBackgroundColor(kSplitviewLabelBackgroundColor);
caption_container_view_ = new CaptionContainerView(
this, image_view, label_view_, cannot_snap_label_view_, close_button_);
UpdateCannotSnapWarningVisibility();
item_widget_->SetContentsView(caption_container_view_);
item_widget_->Show();
item_widget_->SetOpacity(0);
item_widget_->GetLayer()->SetMasksToBounds(false);
FadeInWidgetAndMaybeSlideOnEnter(
item_widget_.get(), OVERVIEW_ANIMATION_ENTER_OVERVIEW_MODE_FADE_IN,
/*slide=*/false);
}
void WindowSelectorItem::UpdateHeaderLayout(
HeaderFadeInMode mode,
OverviewAnimationType animation_type) {
gfx::Rect transformed_window_bounds =
transform_window_.window_selector_bounds().value_or(
transform_window_.GetTransformedBounds());
::wm::ConvertRectFromScreen(root_window_, &transformed_window_bounds);
gfx::Rect label_rect(close_button_->GetPreferredSize());
label_rect.set_width(transformed_window_bounds.width());
// For tabbed windows the initial bounds of the caption are set such that it
// appears to be "growing" up from the window content area.
label_rect.set_y(
(mode != HeaderFadeInMode::kEnter || transform_window_.GetTopInset())
? -label_rect.height()
: 0);
aura::Window* widget_window = item_widget_->GetNativeWindow();
// For the first update, place the widget at its destination.
ScopedOverviewAnimationSettings animation_settings(
mode == HeaderFadeInMode::kFirstUpdate ? OVERVIEW_ANIMATION_NONE
: animation_type,
widget_window);
// |widget_window| covers both the transformed window and the header
// as well as the gap between the windows to prevent events from reaching
// the window including its sizing borders.
if (mode != HeaderFadeInMode::kEnter) {
label_rect.set_height(close_button_->GetPreferredSize().height() +
transformed_window_bounds.height());
}
label_rect.Inset(-kWindowSelectorMargin, -kWindowSelectorMargin);
widget_window->SetBounds(label_rect);
gfx::Transform label_transform;
label_transform.Translate(transformed_window_bounds.x(),
transformed_window_bounds.y());
widget_window->SetTransform(label_transform);
}
void WindowSelectorItem::AnimateOpacity(float opacity,
OverviewAnimationType animation_type) {
DCHECK_GE(opacity, 0.f);
DCHECK_LE(opacity, 1.f);
ScopedTransformOverviewWindow::ScopedAnimationSettings animation_settings;
transform_window_.BeginScopedAnimation(animation_type, &animation_settings);
transform_window_.SetOpacity(opacity);
const float header_opacity = selected_ ? 0.f : kHeaderOpacity * opacity;
aura::Window* widget_window = item_widget_->GetNativeWindow();
ScopedOverviewAnimationSettings animation_settings_label(animation_type,
widget_window);
widget_window->layer()->SetOpacity(header_opacity);
}
void WindowSelectorItem::UpdateAccessibilityName() {
caption_container_view_->listener_button()->SetAccessibleName(
GetWindow()->GetTitle());
}
aura::Window* WindowSelectorItem::GetOverviewWindowForMinimizedStateForTest() {
return transform_window_.GetOverviewWindowForMinimizedState();
}
void WindowSelectorItem::StartDrag() {
window_grid_->SetSelectionWidgetVisibility(false);
// |transform_window_| handles hiding shadow and rounded edges mask while
// animating, and applies them after animation is complete. Prevent the
// shadow and rounded edges mask from showing up after dragging in the case
// the window is pressed while still animating.
transform_window_.CancelAnimationsListener();
aura::Window* widget_window = item_widget_->GetNativeWindow();
aura::Window* window = GetWindowForStacking();
if (widget_window && widget_window->parent() == window->parent()) {
// TODO(xdai): This might not work if there is an always on top window.
// See crbug.com/733760.
widget_window->parent()->StackChildAtTop(widget_window);
widget_window->parent()->StackChildBelow(window, widget_window);
}
}
} // namespace ash