blob: a7c92b5a43c7f80bc288686f099607684e5cf9ce [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 "chrome/browser/ui/views/media_router/cast_dialog_sink_button.h"
#include <memory>
#include "base/debug/stack_trace.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_helper.h"
#include "chrome/common/media_router/issue.h"
#include "chrome/grit/generated_resources.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/throbber.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/vector_icons.h"
#if defined(GOOGLE_CHROME_BUILD)
#include "chrome/browser/ui/media_router/internal/vector_icons/vector_icons.h"
#endif
namespace media_router {
namespace {
gfx::ImageSkia CreateSinkIcon(SinkIconType icon_type, bool enabled = true) {
const gfx::VectorIcon* vector_icon;
switch (icon_type) {
case SinkIconType::CAST_AUDIO_GROUP:
vector_icon = &kSpeakerGroupIcon;
break;
case SinkIconType::CAST_AUDIO:
vector_icon = &kSpeakerIcon;
break;
case SinkIconType::EDUCATION:
vector_icon = &kCastForEducationIcon;
break;
case SinkIconType::WIRED_DISPLAY:
vector_icon = &kInputIcon;
break;
// Use proprietary icons only in Chrome builds. The default TV icon is used
// instead for these sink types in Chromium builds.
#if defined(GOOGLE_CHROME_BUILD)
case SinkIconType::MEETING:
vector_icon = &vector_icons::kMeetIcon;
break;
case SinkIconType::HANGOUT:
vector_icon = &vector_icons::kHangoutIcon;
break;
#endif // defined(GOOGLE_CHROME_BUILD)
case SinkIconType::CAST:
case SinkIconType::GENERIC:
default:
vector_icon = &kTvIcon;
break;
}
SkColor icon_color = enabled ? gfx::kChromeIconGrey : gfx::kGoogleGrey500;
return gfx::CreateVectorIcon(*vector_icon, kPrimaryIconSize, icon_color);
}
gfx::ImageSkia CreateDisabledSinkIcon(SinkIconType icon_type) {
return CreateSinkIcon(icon_type, false);
}
std::unique_ptr<views::ImageView> CreatePrimaryIconView(
const gfx::ImageSkia& image) {
auto icon_view = std::make_unique<views::ImageView>();
icon_view->SetImage(image);
icon_view->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kPrimaryIconBorderWidth)));
return icon_view;
}
std::unique_ptr<views::View> CreatePrimaryIconForSink(
CastDialogSinkButton* sink_button,
views::ButtonListener* button_listener,
const UIMediaSink& sink,
int button_tag) {
// The stop button has the highest priority, and the issue icon comes second.
if (sink.state == UIMediaSinkState::CONNECTED) {
return CreatePrimaryIconView(gfx::CreateVectorIcon(
kGenericStopIcon, kPrimaryIconSize, gfx::kGoogleBlue500));
} else if (sink.issue) {
const SkColor icon_color =
ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
ui::NativeTheme::kColorId_DefaultIconColor);
return CreatePrimaryIconView(gfx::CreateVectorIcon(
::vector_icons::kInfoOutlineIcon, kPrimaryIconSize, icon_color));
} else if (sink.state == UIMediaSinkState::CONNECTING ||
sink.state == UIMediaSinkState::DISCONNECTING) {
return CreateThrobber();
}
return CreatePrimaryIconView(CreateSinkIcon(sink.icon_type));
}
base::string16 GetStatusTextForSink(const UIMediaSink& sink) {
if (sink.issue)
return base::UTF8ToUTF16(sink.issue->info().title);
// If the sink is disconnecting, say so instead of using the source info
// stored in |sink.status_text|.
if (sink.state == UIMediaSinkState::DISCONNECTING)
return l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_SINK_DISCONNECTING);
if (!sink.status_text.empty())
return sink.status_text;
switch (sink.state) {
case UIMediaSinkState::AVAILABLE:
return l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_SINK_AVAILABLE);
case UIMediaSinkState::CONNECTING:
return l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_SINK_CONNECTING);
default:
return base::string16();
}
}
} // namespace
CastDialogSinkButton::CastDialogSinkButton(
views::ButtonListener* button_listener,
const UIMediaSink& sink,
int button_tag)
: HoverButton(
button_listener,
CreatePrimaryIconForSink(this, button_listener, sink, button_tag),
sink.friendly_name,
GetStatusTextForSink(sink),
/** secondary_icon_view */ nullptr),
sink_(sink) {
set_tag(button_tag);
SetEnabled(sink.state == UIMediaSinkState::AVAILABLE ||
sink.state == UIMediaSinkState::CONNECTED);
}
CastDialogSinkButton::~CastDialogSinkButton() = default;
void CastDialogSinkButton::OverrideStatusText(
const base::string16& status_text) {
if (subtitle()) {
if (!saved_status_text_)
saved_status_text_ = subtitle()->GetText();
subtitle()->SetText(status_text);
}
}
void CastDialogSinkButton::RestoreStatusText() {
if (saved_status_text_) {
if (subtitle())
subtitle()->SetText(*saved_status_text_);
saved_status_text_.reset();
}
}
bool CastDialogSinkButton::OnMousePressed(const ui::MouseEvent& event) {
if (event.IsRightMouseButton())
return true;
return HoverButton::OnMousePressed(event);
}
void CastDialogSinkButton::OnMouseReleased(const ui::MouseEvent& event) {
if (event.IsRightMouseButton())
return;
HoverButton::OnMouseReleased(event);
}
void CastDialogSinkButton::OnEnabledChanged() {
// Prevent a DCHECK failure seen at https://crbug.com/912687 by not having an
// InkDrop if the button is disabled.
SetInkDropMode(GetEnabled() ? InkDropMode::ON : InkDropMode::OFF);
// If the button has a state other than AVAILABLE (e.g. CONNECTED), there is
// no need to change the status or the icon.
if (sink_.state != UIMediaSinkState::AVAILABLE)
return;
SkColor background_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ProminentButtonColor);
if (GetEnabled()) {
SetTitleTextStyle(views::style::STYLE_PRIMARY, background_color);
if (saved_status_text_)
RestoreStatusText();
static_cast<views::ImageView*>(icon_view())
->SetImage(CreateSinkIcon(sink_.icon_type));
} else {
SetTitleTextStyle(views::style::STYLE_DISABLED, background_color);
OverrideStatusText(
l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_SOURCE_NOT_SUPPORTED));
static_cast<views::ImageView*>(icon_view())
->SetImage(CreateDisabledSinkIcon(sink_.icon_type));
}
// Apply the style change to the title text.
title()->Layout();
}
void CastDialogSinkButton::RequestFocus() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
static bool requesting_focus = false;
if (requesting_focus) {
// TODO(jrw): Figure out why this happens.
DLOG(ERROR) << "Recursive call to RequestFocus\n"
<< base::debug::StackTrace();
return;
}
requesting_focus = true;
if (GetEnabled()) {
HoverButton::RequestFocus();
} else {
// The sink button is disabled, but the icon within it may be enabled and
// want focus.
icon_view()->RequestFocus();
}
requesting_focus = false;
}
void CastDialogSinkButton::OnFocus() {
HoverButton::OnFocus();
if (sink_.state == UIMediaSinkState::CONNECTED) {
OverrideStatusText(
l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_STOP_CASTING));
}
}
void CastDialogSinkButton::OnBlur() {
if (sink_.state == UIMediaSinkState::CONNECTED)
RestoreStatusText();
}
} // namespace media_router