blob: d0f8eae361a672354f5dbb74f9bb5e4ab751d61f [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/wm/overview/rounded_rect_view.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/image/image_skia_operations.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 font delta of the window title.
constexpr int kLabelFontDelta = 2;
// Values of the backdrop.
constexpr int kBackdropRoundingDp = 4;
constexpr SkColor kBackdropColor = SkColorSetA(SK_ColorWHITE, 0x24);
constexpr int kHeaderPreferredHeightDp = 40;
constexpr int kMarginDp = 10;
// Duration of the show/hide animation of the header.
constexpr base::TimeDelta kHeaderFadeDuration =
base::TimeDelta::FromMilliseconds(167);
// Delay before the show animation of the header.
constexpr base::TimeDelta kHeaderFadeInDelay =
base::TimeDelta::FromMilliseconds(83);
// Duration of the slow show animation of the close button.
constexpr base::TimeDelta kCloseButtonSlowFadeInDuration =
base::TimeDelta::FromMilliseconds(300);
// Delay before the slow show animation of the close button.
constexpr base::TimeDelta kCloseButtonSlowFadeInDelay =
base::TimeDelta::FromMilliseconds(750);
void AddChildWithLayer(views::View* parent, views::View* child) {
child->SetPaintToLayer();
child->layer()->SetFillsBoundsOpaquely(false);
parent->AddChildView(child);
}
gfx::PointF ConvertToScreen(views::View* view, const gfx::Point& location) {
gfx::Point location_copy(location);
views::View::ConvertPointToScreen(view, &location_copy);
return gfx::PointF(location_copy);
}
// 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);
gfx::Tween::Type tween =
visible ? gfx::Tween::LINEAR_OUT_SLOW_IN : gfx::Tween::FAST_OUT_LINEAR_IN;
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetTransitionDuration(kHeaderFadeDuration);
settings.SetTweenType(tween);
settings.SetPreemptionStrategy(ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
if (visible) {
layer->GetAnimator()->SchedulePauseForProperties(
kHeaderFadeInDelay, ui::LayerAnimationElement::OPACITY);
}
layer->SetOpacity(target_opacity);
}
} // namespace
CaptionContainerView::CaptionContainerView(EventDelegate* event_delegate,
aura::Window* window,
views::ImageButton* close_button)
: Button(nullptr),
event_delegate_(event_delegate),
close_button_(close_button) {
// This should not be focusable. It's also to avoid accessibility error when
// |window->GetTitle()| is empty.
SetFocusBehavior(FocusBehavior::NEVER);
SetAccessibleName(window->GetTitle());
header_view_ = new views::View();
views::BoxLayout* layout =
header_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(),
kHorizontalLabelPaddingDp));
AddChildWithLayer(this, header_view_);
// Prefer kAppIconSmallKey (set by the client in Mash), then kAppIconKey and
// kWindowIconKey (set for client windows in classic Ash but not Mash).
gfx::ImageSkia* icon = window->GetProperty(aura::client::kAppIconSmallKey);
if (!icon || icon->size().IsEmpty())
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);
if (close_button)
AddChildWithLayer(header_view_, close_button);
}
CaptionContainerView::~CaptionContainerView() = default;
void CaptionContainerView::SetHeaderVisibility(HeaderVisibility visibility) {
DCHECK(header_view_->layer());
if (visibility == current_header_visibility_)
return;
const HeaderVisibility previous_visibility = current_header_visibility_;
current_header_visibility_ = visibility;
const bool all_invisible = visibility == HeaderVisibility::kInvisible;
AnimateLayerOpacity(header_view_->layer(), !all_invisible);
// If |header_view_| is fading out, we are done. Depending on if the close
// button was visible, it will fade out with |header_view_| or stay hidden.
if (all_invisible || !close_button_)
return;
const bool close_button_visible = visibility == HeaderVisibility::kVisible;
// If |header_view_| was hidden and is fading in, set the opacity of
// |close_button_| depending on whether the close button should fade in with
// |header_view_| or stay hidden.
if (previous_visibility == HeaderVisibility::kInvisible) {
close_button_->layer()->SetOpacity(close_button_visible ? 1.f : 0.f);
return;
}
AnimateLayerOpacity(close_button_->layer(), close_button_visible);
}
void CaptionContainerView::HideCloseInstantlyAndThenShowItSlowly() {
DCHECK(close_button_);
DCHECK_NE(HeaderVisibility::kInvisible, current_header_visibility_);
ui::Layer* layer = close_button_->layer();
DCHECK(layer);
current_header_visibility_ = HeaderVisibility::kVisible;
layer->SetOpacity(0.f);
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetTransitionDuration(kCloseButtonSlowFadeInDuration);
settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
settings.SetPreemptionStrategy(ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
layer->GetAnimator()->SchedulePauseForProperties(
kCloseButtonSlowFadeInDelay, ui::LayerAnimationElement::OPACITY);
layer->SetOpacity(1.f);
}
void CaptionContainerView::SetBackdropVisibility(bool visible) {
if (!backdrop_view_ && !visible)
return;
if (!backdrop_view_) {
backdrop_view_ = new RoundedRectView(kBackdropRoundingDp, kBackdropColor);
backdrop_view_->set_can_process_events_within_subtree(false);
AddChildWithLayer(this, backdrop_view_);
Layout();
}
backdrop_view_->SetVisible(visible);
}
void CaptionContainerView::ResetEventDelegate() {
event_delegate_ = nullptr;
}
void CaptionContainerView::SetTitle(const base::string16& title) {
title_label_->SetText(title);
SetAccessibleName(title);
}
void CaptionContainerView::Layout() {
gfx::Rect bounds(GetLocalBounds());
bounds.Inset(kMarginDp, kMarginDp);
if (backdrop_view_) {
gfx::Rect backdrop_bounds = bounds;
backdrop_bounds.Inset(0, kHeaderPreferredHeightDp, 0, 0);
backdrop_view_->SetBoundsRect(backdrop_bounds);
}
// Position the header at the top.
const gfx::Rect header_bounds(kMarginDp, kMarginDp,
GetLocalBounds().width() - kMarginDp,
kHeaderPreferredHeightDp);
header_view_->SetBoundsRect(header_bounds);
}
const char* CaptionContainerView::GetClassName() const {
return "CaptionContainerView";
}
bool CaptionContainerView::OnMousePressed(const ui::MouseEvent& event) {
if (!event_delegate_)
return Button::OnMousePressed(event);
event_delegate_->HandlePressEvent(ConvertToScreen(this, event.location()));
return true;
}
bool CaptionContainerView::OnMouseDragged(const ui::MouseEvent& event) {
if (!event_delegate_)
return Button::OnMouseDragged(event);
event_delegate_->HandleDragEvent(ConvertToScreen(this, event.location()));
return true;
}
void CaptionContainerView::OnMouseReleased(const ui::MouseEvent& event) {
if (!event_delegate_) {
Button::OnMouseReleased(event);
return;
}
event_delegate_->HandleReleaseEvent(ConvertToScreen(this, event.location()));
}
void CaptionContainerView::OnGestureEvent(ui::GestureEvent* event) {
if (!event_delegate_)
return;
if (event_delegate_->ShouldIgnoreGestureEvents()) {
event->SetHandled();
return;
}
const gfx::PointF location = event->details().bounding_box_f().CenterPoint();
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN:
event_delegate_->HandlePressEvent(location);
break;
case ui::ET_GESTURE_SCROLL_UPDATE:
event_delegate_->HandleDragEvent(location);
break;
case ui::ET_SCROLL_FLING_START:
event_delegate_->HandleFlingStartEvent(location,
event->details().velocity_x(),
event->details().velocity_y());
break;
case ui::ET_GESTURE_SCROLL_END:
event_delegate_->HandleReleaseEvent(location);
break;
case ui::ET_GESTURE_LONG_PRESS:
event_delegate_->HandleLongPressEvent(location);
break;
case ui::ET_GESTURE_TAP:
event_delegate_->HandleTapEvent();
break;
case ui::ET_GESTURE_END:
event_delegate_->HandleGestureEndEvent();
break;
default:
break;
}
event->SetHandled();
}
bool CaptionContainerView::CanAcceptEvent(const ui::Event& event) {
bool accept_events = true;
// Do not process or accept press down events that are on the border.
static ui::EventType press_types[] = {ui::ET_GESTURE_TAP_DOWN,
ui::ET_MOUSE_PRESSED};
if (event.IsLocatedEvent() &&
base::ContainsValue(press_types, event.type())) {
gfx::Rect inset_bounds = GetLocalBounds();
inset_bounds.Inset(gfx::Insets(kMarginDp));
if (!inset_bounds.Contains(event.AsLocatedEvent()->location()))
accept_events = false;
}
return accept_events && Button::CanAcceptEvent(event);
}
} // namespace ash