blob: ee005f6adcf17bf9377f5bfa65730303b54d0f0a [file] [log] [blame]
// Copyright 2018 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/caption_container_view.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/overview/overview_constants.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/rounded_rect_view.h"
#include "ash/wm/overview/scoped_overview_animation_settings.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.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/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
// Foreground label color.
constexpr SkColor kLabelColor = SK_ColorWHITE;
// Horizontal padding for the label, on both sides.
constexpr int kHorizontalLabelPaddingDp = 12;
// The size in dp of the window icon shown on the overview window next to the
// title.
constexpr gfx::Size kIconSize{24, 24};
// The amount we need to offset the close button so that the icon, which is
// smaller than the actual button is lined up with the right side of the window
// preview.
constexpr int kCloseButtonOffsetDp = 8;
constexpr int kCloseButtonInkDropInsetDp = 2;
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;
void AddChildWithLayer(views::View* parent, views::View* child) {
child->SetPaintToLayer();
child->layer()->SetFillsBoundsOpaquely(false);
parent->AddChildView(child);
}
} // namespace
// The close button for the caption container view. It has a custom ink drop.
class CaptionContainerView::OverviewCloseButton : public views::ImageButton {
public:
explicit 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));
}
~OverviewCloseButton() override = default;
// Resets the listener so that the listener can go out of scope.
void ResetListener() { listener_ = nullptr; }
protected:
// views::Button:
std::unique_ptr<views::InkDrop> CreateInkDrop() override {
auto 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> CreateInkDropRipple() const override {
return std::make_unique<views::FloodFillInkDropRipple>(
size(), gfx::Insets(), GetInkDropCenterBasedOnLastEvent(),
kCloseButtonInkDropRippleColor, /*visible_opacity=*/1.f);
}
std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
const override {
return std::make_unique<views::InkDropHighlight>(
gfx::PointF(GetLocalBounds().CenterPoint()),
std::make_unique<views::CircleLayerDelegate>(
kCloseButtonInkDropRippleHighlightColor, GetInkDropRadius()));
}
std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override {
return std::make_unique<views::CircleInkDropMask>(
size(), GetLocalBounds().CenterPoint(), GetInkDropRadius());
}
private:
int GetInkDropRadius() const {
return std::min(size().width(), size().height()) / 2 -
kCloseButtonInkDropInsetDp;
}
DISALLOW_COPY_AND_ASSIGN(OverviewCloseButton);
};
// A Button that has a listener and listens to mouse / gesture events on the
// visible part of an overview window.
class CaptionContainerView::ShieldButton : public views::Button {
public:
ShieldButton(views::ButtonListener* listener, const base::string16& name)
: views::Button(listener) {
// The shield button should not be focusable. It's also to avoid
// accessibility error when |name| is empty.
SetFocusBehavior(FocusBehavior::NEVER);
SetAccessibleName(name);
}
~ShieldButton() override = default;
// When OverviewItem (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 OverviewItem has been destroyed.
void ResetListener() { listener_ = nullptr; }
// views::View:
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);
}
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 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);
}
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);
}
OverviewItem* listener() { return static_cast<OverviewItem*>(listener_); }
protected:
// views::View:
const char* GetClassName() const override { return "ShieldButton"; }
private:
DISALLOW_COPY_AND_ASSIGN(ShieldButton);
};
CaptionContainerView::CaptionContainerView(views::ButtonListener* listener,
aura::Window* window) {
listener_button_ = new ShieldButton(listener, window->GetTitle());
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));
AddChildWithLayer(listener_button_, header_view_);
gfx::ImageSkia* icon = window->GetProperty(aura::client::kAppIconKey);
if (!icon || icon->size().IsEmpty())
icon = window->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);
header_view_->AddChildView(image_view_);
}
title_label_ = new views::Label(window->GetTitle());
title_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title_label_->SetAutoColorReadabilityEnabled(false);
title_label_->SetEnabledColor(kLabelColor);
title_label_->SetSubpixelRenderingEnabled(false);
title_label_->SetFontList(gfx::FontList().Derive(
kLabelFontDelta, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
header_view_->AddChildView(title_label_);
layout->SetFlexForView(title_label_, 1);
close_button_ = new OverviewCloseButton(listener);
AddChildWithLayer(header_view_, close_button_);
}
CaptionContainerView::~CaptionContainerView() = default;
RoundedRectView* CaptionContainerView::GetCannotSnapContainer() {
if (!cannot_snap_container_) {
cannot_snap_label_ = new views::Label(
l10n_util::GetStringUTF16(IDS_ASH_SPLIT_VIEW_CANNOT_SNAP));
cannot_snap_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
cannot_snap_label_->SetAutoColorReadabilityEnabled(false);
cannot_snap_label_->SetEnabledColor(kSplitviewLabelEnabledColor);
cannot_snap_label_->SetBackgroundColor(kSplitviewLabelBackgroundColor);
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_;
}
void CaptionContainerView::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 CaptionContainerView::SetCannotSnapLabelVisibility(bool visible) {
if (!cannot_snap_container_ && !visible)
return;
DoSplitviewOpacityAnimation(GetCannotSnapContainer()->layer(),
visible
? SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_IN
: SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_OUT);
}
void CaptionContainerView::ResetListener() {
listener_button_->ResetListener();
close_button_->ResetListener();
}
void CaptionContainerView::SetTitle(const base::string16& title) {
title_label_->SetText(title);
listener_button_->SetAccessibleName(title);
}
views::View* CaptionContainerView::GetListenerButton() {
return listener_button_;
}
views::ImageButton* CaptionContainerView::GetCloseButton() {
return close_button_;
}
void CaptionContainerView::Layout() {
gfx::Rect bounds(GetLocalBounds());
bounds.Inset(kOverviewMargin, kOverviewMargin);
listener_button_->SetBoundsRect(bounds);
const int visible_height = close_button_->GetPreferredSize().height();
backdrop_bounds_ = bounds;
backdrop_bounds_.Inset(0, visible_height, 0, 0);
if (cannot_snap_container_) {
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));
// 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 right side of the header should be
// positioned so that the rightmost of the close icon matches the right side
// of the window preview.
gfx::Rect header_bounds = GetLocalBounds();
header_bounds.Inset(0, 0, kCloseButtonOffsetDp, 0);
header_bounds.set_height(visible_height);
header_view_->SetBoundsRect(header_bounds);
}
const char* CaptionContainerView::GetClassName() const {
return "CaptionContainerView";
}
void CaptionContainerView::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);
ScopedOverviewAnimationSettings settings(
visible ? OVERVIEW_ANIMATION_OVERVIEW_TITLE_FADE_IN
: OVERVIEW_ANIMATION_OVERVIEW_TITLE_FADE_OUT,
layer->GetAnimator());
layer->SetOpacity(target_opacity);
}
} // namespace ash