blob: 5681f7bbf436d3256402e50a1978bad7fcc64f31 [file] [log] [blame]
// Copyright 2014 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 "chrome/browser/ui/views/tabs/alert_indicator.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/multi_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/paint_vector_icon.h"
namespace {
// Fade-in/out duration for the tab indicator animations. Fade-in is quick to
// immediately notify the user. Fade-out is more gradual, so that the user has
// a chance of finding a tab that has quickly "blipped" on and off.
constexpr int kIndicatorFadeInDurationMs = 200;
constexpr int kIndicatorFadeOutDurationMs = 1000;
// Interval between frame updates of the tab indicator animations. This is not
// the usual 60 FPS because a trade-off must be made between tab UI animation
// smoothness and media recording/playback performance on low-end hardware.
constexpr base::TimeDelta kIndicatorFrameInterval =
base::TimeDelta::FromMilliseconds(50); // 20 FPS
// Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
// "in" state.
class TabRecordingIndicatorAnimation : public gfx::MultiAnimation {
public:
~TabRecordingIndicatorAnimation() override {}
// Overridden to provide alternating "towards in" and "towards out" behavior.
double GetCurrentValue() const override;
static std::unique_ptr<TabRecordingIndicatorAnimation> Create();
private:
TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts& parts,
const base::TimeDelta interval)
: MultiAnimation(parts, interval) {}
// Number of times to "toggle throb" the recording and tab capture indicators
// when they first appear.
static const int kCaptureIndicatorThrobCycles = 5;
};
double TabRecordingIndicatorAnimation::GetCurrentValue() const {
return current_part_index() % 2 ? 1.0 - MultiAnimation::GetCurrentValue()
: MultiAnimation::GetCurrentValue();
}
std::unique_ptr<TabRecordingIndicatorAnimation>
TabRecordingIndicatorAnimation::Create() {
MultiAnimation::Parts parts;
static_assert(
kCaptureIndicatorThrobCycles % 2 != 0,
"odd number of cycles required so animation finishes in showing state");
for (int i = 0; i < kCaptureIndicatorThrobCycles; ++i) {
parts.push_back(MultiAnimation::Part(
i % 2 ? kIndicatorFadeOutDurationMs : kIndicatorFadeInDurationMs,
gfx::Tween::EASE_IN));
}
std::unique_ptr<TabRecordingIndicatorAnimation> animation(
new TabRecordingIndicatorAnimation(parts, kIndicatorFrameInterval));
animation->set_continuous(false);
return animation;
}
// Returns a cached image, to be shown by the alert indicator for the given
// |alert_state|. Uses the global ui::ResourceBundle shared instance.
gfx::Image GetTabAlertIndicatorImage(TabAlertState alert_state,
SkColor button_color) {
const gfx::VectorIcon* icon = nullptr;
int image_width = GetLayoutConstant(TAB_ALERT_INDICATOR_ICON_WIDTH);
const bool is_touch_optimized_ui =
ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
switch (alert_state) {
case TabAlertState::AUDIO_PLAYING:
icon = is_touch_optimized_ui ? &kTabAudioRoundedIcon : &kTabAudioIcon;
break;
case TabAlertState::AUDIO_MUTING:
icon = is_touch_optimized_ui ? &kTabAudioMutingRoundedIcon
: &kTabAudioMutingIcon;
break;
case TabAlertState::MEDIA_RECORDING:
case TabAlertState::DESKTOP_CAPTURING:
icon = &kTabMediaRecordingIcon;
break;
case TabAlertState::TAB_CAPTURING:
icon = is_touch_optimized_ui ? &kTabMediaCapturingWithArrowIcon
: &kTabMediaCapturingIcon;
// Tab capturing and presenting icon uses a different width compared to
// the other tab alert indicator icons.
image_width = GetLayoutConstant(TAB_ALERT_INDICATOR_CAPTURE_ICON_WIDTH);
break;
case TabAlertState::BLUETOOTH_CONNECTED:
icon = &kTabBluetoothConnectedIcon;
break;
case TabAlertState::USB_CONNECTED:
icon = &kTabUsbConnectedIcon;
break;
case TabAlertState::PIP_PLAYING:
icon = &kPictureInPictureAltIcon;
break;
case TabAlertState::NONE:
return gfx::Image();
}
DCHECK(icon);
return gfx::Image(gfx::CreateVectorIcon(*icon, image_width, button_color));
}
// Returns a non-continuous Animation that performs a fade-in or fade-out
// appropriate for the given |next_alert_state|. This is used by the tab alert
// indicator to alert the user that recording, tab capture, or audio playback
// has started/stopped.
std::unique_ptr<gfx::Animation> CreateTabAlertIndicatorFadeAnimation(
TabAlertState alert_state) {
if (alert_state == TabAlertState::MEDIA_RECORDING ||
alert_state == TabAlertState::TAB_CAPTURING ||
alert_state == TabAlertState::DESKTOP_CAPTURING) {
return TabRecordingIndicatorAnimation::Create();
}
// Note: While it seems silly to use a one-part MultiAnimation, it's the only
// gfx::Animation implementation that lets us control the frame interval.
gfx::MultiAnimation::Parts parts;
const bool is_for_fade_in = (alert_state != TabAlertState::NONE);
parts.push_back(gfx::MultiAnimation::Part(
is_for_fade_in ? kIndicatorFadeInDurationMs : kIndicatorFadeOutDurationMs,
gfx::Tween::EASE_IN));
std::unique_ptr<gfx::MultiAnimation> animation(
new gfx::MultiAnimation(parts, kIndicatorFrameInterval));
animation->set_continuous(false);
return std::move(animation);
}
} // namespace
class AlertIndicator::FadeAnimationDelegate : public gfx::AnimationDelegate {
public:
explicit FadeAnimationDelegate(AlertIndicator* indicator)
: indicator_(indicator) {}
~FadeAnimationDelegate() override {}
private:
// gfx::AnimationDelegate
void AnimationProgressed(const gfx::Animation* animation) override {
indicator_->SchedulePaint();
}
void AnimationCanceled(const gfx::Animation* animation) override {
AnimationEnded(animation);
}
void AnimationEnded(const gfx::Animation* animation) override {
indicator_->showing_alert_state_ = indicator_->alert_state_;
indicator_->SchedulePaint();
indicator_->parent_tab_->AlertStateChanged();
}
AlertIndicator* const indicator_;
DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate);
};
AlertIndicator::AlertIndicator(Tab* parent_tab)
: views::ImageView(),
parent_tab_(parent_tab),
alert_state_(TabAlertState::NONE),
showing_alert_state_(TabAlertState::NONE) {
DCHECK(parent_tab_);
}
AlertIndicator::~AlertIndicator() {}
void AlertIndicator::OnPaint(gfx::Canvas* canvas) {
double opaqueness = 1.0;
if (fade_animation_) {
opaqueness = fade_animation_->GetCurrentValue();
if (alert_state_ == TabAlertState::NONE)
opaqueness = 1.0 - opaqueness; // Fading out, not in.
}
if (opaqueness < 1.0)
canvas->SaveLayerAlpha(opaqueness * SK_AlphaOPAQUE);
ImageView::OnPaint(canvas);
if (opaqueness < 1.0)
canvas->Restore();
}
void AlertIndicator::TransitionToAlertState(TabAlertState next_state) {
if (next_state == alert_state_)
return;
TabAlertState previous_alert_showing_state = showing_alert_state_;
if (next_state != TabAlertState::NONE)
ResetImage(next_state);
if ((alert_state_ == TabAlertState::AUDIO_PLAYING &&
next_state == TabAlertState::AUDIO_MUTING) ||
(alert_state_ == TabAlertState::AUDIO_MUTING &&
next_state == TabAlertState::AUDIO_PLAYING)) {
// Instant user feedback: No fade animation.
showing_alert_state_ = next_state;
fade_animation_.reset();
} else {
if (next_state == TabAlertState::NONE)
showing_alert_state_ = alert_state_; // Fading-out indicator.
else
showing_alert_state_ = next_state; // Fading-in to next indicator.
fade_animation_ = CreateTabAlertIndicatorFadeAnimation(next_state);
if (!fade_animation_delegate_)
fade_animation_delegate_.reset(new FadeAnimationDelegate(this));
fade_animation_->set_delegate(fade_animation_delegate_.get());
fade_animation_->Start();
}
alert_state_ = next_state;
if (previous_alert_showing_state != showing_alert_state_)
parent_tab_->AlertStateChanged();
}
void AlertIndicator::OnParentTabButtonColorChanged() {
if (alert_state_ == TabAlertState::AUDIO_PLAYING ||
alert_state_ == TabAlertState::AUDIO_MUTING)
ResetImage(alert_state_);
}
views::View* AlertIndicator::GetTooltipHandlerForPoint(
const gfx::Point& point) {
return nullptr; // Tab (the parent View) provides the tooltip.
}
void AlertIndicator::ResetImage(TabAlertState state) {
SkColor color = parent_tab_->GetAlertIndicatorColor(state);
gfx::ImageSkia image = GetTabAlertIndicatorImage(state, color).AsImageSkia();
SetImage(&image);
}