Commit 7d2d8cc1 authored by Noah Rose Ledesma's avatar Noah Rose Ledesma Committed by Commit Bot

GMC: Redesign audio picker UI

Implement the new UI for the audio device picker in global media
controls. As part of this change, the MediaNotificationBackground will
draw a gradient between the device picker and notification artwork if
the picker is availabile.

Screenshots of this updated UI will be posted on the crbug.

Change-Id: Ic7186c770102663b08e85b56eeddfbfa13833c3c
Bug: 1117160
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2358512
Commit-Queue: Noah Rose Ledesma <noahrose@google.com>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800243}
parent 4c09e3d6
......@@ -1167,6 +1167,12 @@ Chromium is unable to recover your settings.
</message>
</if>
<!-- Global Media Controls -->
<if expr="not is_android">
<message name="IDS_GLOBAL_MEDIA_CONTROLS_DEVICES_BUTTON_LABEL" desc="Label for a button that opens a menu for picking an audio device to play from.">
Devices
</message>
</if>
</messages>
</release>
</grit>
768895d5a9d7cb3c5816e91af22e01b359eb646f
\ No newline at end of file
......@@ -4,115 +4,254 @@
#include "chrome/browser/ui/views/global_media_controls/media_notification_audio_device_selector_view.h"
#include "base/bind.h"
#include "base/strings/string16.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "base/strings/utf_string_conversions.h"
#include "base/util/ranges/algorithm.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_impl.h"
#include "chrome/browser/ui/global_media_controls/media_notification_service.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_audio_device_selector_view_delegate.h"
#include "chrome/grit/chromium_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "media/audio/audio_device_description.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/vector_icons.h"
#include "ui/views/style/typography.h"
namespace {
// Constants for this view
constexpr int kPaddingBetweenContainers = 10;
// Constants for the AudioDeviceEntryView
constexpr gfx::Insets kIconContainerInsets{10, 15};
constexpr int kDeviceIconSize = 18;
constexpr gfx::Insets kLabelsContainerInsets{18, 0};
constexpr gfx::Size kAudioDeviceEntryViewSize{400, 30};
constexpr int kEntryHighlightOpacity = 45;
// Constants for the MediaNotificationAudioDeviceSelectorView
constexpr gfx::Insets kExpandButtonStripInsets{6, 15};
constexpr gfx::Size kExpandButtonStripSize{400, 30};
constexpr gfx::Insets kExpandButtonBorderInsets{4, 8};
constexpr int kExpandButtonBorderCornerRadius = 16;
// Constants for the expand button and its container
// The container for the expand button will take up a fixed amount of
// space in this view. The leftover space will be given to the container
// for device selection buttons.
constexpr int kExpandButtonContainerWidth = 45;
constexpr int kExpandButtonSize = 20;
constexpr int kExpandButtonBorderThickness = 1;
constexpr int kExpandButtonBorderCornerRadius = 2;
class AudioDeviceEntryView : public views::Button {
public:
AudioDeviceEntryView(const SkColor& foreground_color,
const SkColor& background_color,
const std::string& raw_device_id,
const std::string& name,
const std::string& subtext = "");
// Constants for the device buttons and their container
constexpr int kPaddingBetweenDeviceButtons = 5;
constexpr int kDeviceButtonIconSize = 16;
constexpr gfx::Insets kDeviceButtonContainerInsets = gfx::Insets(0, 10, 0, 0);
constexpr gfx::Insets kDeviceButtonInsets = gfx::Insets(5);
const std::string& GetDeviceId() { return raw_device_id_; }
void SetHighlighted(bool highlighted);
void OnColorsChanged(const SkColor& foreground_color,
const SkColor& background_color);
std::string get_label_for_testing();
bool is_highlighted_for_testing() { return is_highlighted_; }
protected:
SkColor foreground_color_, background_color_;
const std::string raw_device_id_;
bool is_highlighted_ = false;
views::View* icon_container_;
views::ImageView* device_icon_;
views::View* labels_container_;
views::Label* device_name_label_;
views::Label* device_subtext_label_ = nullptr;
};
} // anonymous namespace
AudioDeviceEntryView::AudioDeviceEntryView(const SkColor& foreground_color,
const SkColor& background_color,
const std::string& raw_device_id,
const std::string& name,
const std::string& subtext)
: foreground_color_(foreground_color),
background_color_(background_color),
raw_device_id_(raw_device_id) {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
icon_container_ = AddChildView(std::make_unique<views::View>());
auto* icon_container_layout =
icon_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, kIconContainerInsets));
icon_container_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
icon_container_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
device_icon_ =
icon_container_->AddChildView(std::make_unique<views::ImageView>());
device_icon_->SetImage(gfx::CreateVectorIcon(
vector_icons::kHeadsetIcon, kDeviceIconSize, foreground_color));
labels_container_ = AddChildView(std::make_unique<views::View>());
auto* labels_container_layout_ =
labels_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, kLabelsContainerInsets));
labels_container_layout_->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
labels_container_layout_->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
views::Label::CustomFont device_name_label_font{
views::Label::GetDefaultFontList().DeriveWithSizeDelta(1)};
device_name_label_ =
labels_container_->AddChildView(std::make_unique<views::Label>(
base::UTF8ToUTF16(name), device_name_label_font));
device_name_label_->SetEnabledColor(foreground_color);
device_name_label_->SetBackgroundColor(background_color);
if (!subtext.empty()) {
device_subtext_label_ = labels_container_->AddChildView(
std::make_unique<views::Label>(base::UTF8ToUTF16(subtext)));
device_subtext_label_->SetTextStyle(
views::style::TextStyle::STYLE_SECONDARY);
device_subtext_label_->SetEnabledColor(foreground_color);
device_subtext_label_->SetBackgroundColor(background_color);
}
// Ensures that hovering over these items also hovers this view.
icon_container_->set_can_process_events_within_subtree(false);
labels_container_->set_can_process_events_within_subtree(false);
SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
SetInkDropMode(Button::InkDropMode::ON);
set_ink_drop_base_color(foreground_color);
set_has_ink_drop_action_on_click(true);
SetPreferredSize(kAudioDeviceEntryViewSize);
}
void AudioDeviceEntryView::SetHighlighted(bool highlighted) {
is_highlighted_ = highlighted;
if (highlighted) {
SetInkDropMode(Button::InkDropMode::OFF);
set_has_ink_drop_action_on_click(false);
SetBackground(views::CreateSolidBackground(
SkColorSetA(GetInkDropBaseColor(), kEntryHighlightOpacity)));
} else {
SetInkDropMode(Button::InkDropMode::ON);
set_has_ink_drop_action_on_click(true);
SetBackground(nullptr);
}
}
void AudioDeviceEntryView::OnColorsChanged(const SkColor& foreground_color,
const SkColor& background_color) {
foreground_color_ = foreground_color;
background_color_ = background_color;
set_ink_drop_base_color(foreground_color_);
device_icon_->SetImage(gfx::CreateVectorIcon(
vector_icons::kHeadsetIcon, kDeviceIconSize, foreground_color_));
device_name_label_->SetEnabledColor(foreground_color_);
device_name_label_->SetBackgroundColor(background_color_);
if (device_subtext_label_) {
device_subtext_label_->SetEnabledColor(foreground_color_);
device_subtext_label_->SetBackgroundColor(background_color_);
}
// Reapply highlight formatting as some effects rely on these colors.
SetHighlighted(is_highlighted_);
}
std::string AudioDeviceEntryView::get_label_for_testing() {
return base::UTF16ToUTF8(device_name_label_->GetText());
}
MediaNotificationAudioDeviceSelectorView::
MediaNotificationAudioDeviceSelectorView(
MediaNotificationAudioDeviceSelectorViewDelegate* delegate,
MediaNotificationService* service,
gfx::Size size,
const std::string& current_device_id)
const std::string& current_device_id,
const SkColor& foreground_color,
const SkColor& background_color)
: delegate_(delegate),
service_(service),
current_device_id_(current_device_id) {
DCHECK(service);
SetPreferredSize(size);
current_device_id_(current_device_id),
foreground_color_(foreground_color),
background_color_(background_color) {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
kPaddingBetweenContainers));
auto device_button_container_width =
size.width() - kExpandButtonContainerWidth;
auto device_button_container = std::make_unique<views::View>();
device_button_container->SetPreferredSize(
gfx::Size(device_button_container_width, size.height()));
auto* device_button_container_layout =
device_button_container->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
expand_button_strip_ = AddChildView(std::make_unique<views::View>());
auto* expand_button_strip_layout =
expand_button_strip_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
kDeviceButtonContainerInsets, kPaddingBetweenDeviceButtons));
device_button_container_layout->set_main_axis_alignment(
kExpandButtonStripInsets));
expand_button_strip_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kStart);
device_button_container_layout->set_cross_axis_alignment(
expand_button_strip_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
device_button_container_ = AddChildView(std::move(device_button_container));
expand_button_strip_->SetPreferredSize(kExpandButtonStripSize);
auto expand_button_container = std::make_unique<views::View>();
auto* expand_button_container_layout =
expand_button_container->SetLayoutManager(
auto expand_button = std::make_unique<views::LabelButton>(
this, l10n_util::GetStringUTF16(
IDS_GLOBAL_MEDIA_CONTROLS_DEVICES_BUTTON_LABEL));
expand_button->SetTextColor(views::MdTextButton::ButtonState::STATE_NORMAL,
foreground_color_);
expand_button->SetBackground(views::CreateSolidBackground(background_color_));
auto border = std::make_unique<views::BubbleBorder>(
views::BubbleBorder::Arrow::NONE, views::BubbleBorder::Shadow::NO_SHADOW,
background_color_);
border->set_insets(kExpandButtonBorderInsets);
border->SetCornerRadius(kExpandButtonBorderCornerRadius);
expand_button->SetBorder(std::move(border));
expand_button_ = expand_button_strip_->AddChildView(std::move(expand_button));
audio_device_entries_container_ =
AddChildView(std::make_unique<views::View>());
audio_device_entries_container_->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
expand_button_container_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
expand_button_container_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
expand_button_container->SetPreferredSize(
gfx::Size(kExpandButtonContainerWidth, size.height()));
expand_button_container_ = AddChildView(std::move(expand_button_container));
auto expand_button = views::CreateVectorToggleImageButton(this);
expand_button->SetPreferredSize(
gfx::Size(kExpandButtonSize, kExpandButtonSize));
expand_button_ =
expand_button_container_->AddChildView(std::move(expand_button));
expand_button_->SetBorder(views::CreateRoundedRectBorder(
kExpandButtonBorderThickness, kExpandButtonBorderCornerRadius,
SK_ColorLTGRAY));
views::SetImageFromVectorIcon(expand_button_, kKeyboardArrowDownIcon,
kExpandButtonSize, SK_ColorBLACK);
views::SetToggledImageFromVectorIconWithColor(
expand_button_, kKeyboardArrowUpIcon, kExpandButtonSize, SK_ColorBLACK,
SK_ColorBLACK);
views::BoxLayout::Orientation::kVertical));
audio_device_entries_container_->SetVisible(false);
SetBackground(views::CreateSolidBackground(background_color_));
// Set the size of this view
SetPreferredSize(kExpandButtonStripSize);
Layout();
// This view will become visible when devices are discovered.
SetVisible(false);
// Get a list of the connected audio output devices
audio_device_subscription_ =
service_->RegisterAudioOutputDeviceDescriptionsCallback(
service->RegisterAudioOutputDeviceDescriptionsCallback(
base::BindRepeating(&MediaNotificationAudioDeviceSelectorView::
UpdateAvailableAudioDevices,
weak_ptr_factory_.GetWeakPtr()));
}
void MediaNotificationAudioDeviceSelectorView::UpdateCurrentAudioDevice(
const std::string& current_device_id) {
if (current_device_entry_view_) {
current_device_entry_view_->SetHighlighted(false);
current_device_entry_view_ = nullptr;
}
auto it = util::ranges::find_if(
audio_device_entries_container_->children(),
[&current_device_id](auto& item) {
return static_cast<AudioDeviceEntryView*>(item)->GetDeviceId() ==
current_device_id;
});
if (it == audio_device_entries_container_->children().end())
return;
current_device_entry_view_ = static_cast<AudioDeviceEntryView*>(*it);
current_device_entry_view_->SetHighlighted(true);
audio_device_entries_container_->ReorderChildView(current_device_entry_view_,
0);
current_device_entry_view_->Layout();
}
MediaNotificationAudioDeviceSelectorView::
~MediaNotificationAudioDeviceSelectorView() {
audio_device_subscription_.release();
......@@ -124,70 +263,95 @@ void MediaNotificationAudioDeviceSelectorView::UpdateAvailableAudioDevices(
SetVisible(is_visible);
delegate_->OnAudioDeviceSelectorViewSizeChanged();
sink_id_map_.clear();
device_button_container_->RemoveAllChildViews(true);
current_device_button_ = nullptr;
audio_device_entries_container_->RemoveAllChildViews(true);
current_device_entry_view_ = nullptr;
bool current_device_still_exists = false;
for (auto description : device_descriptions) {
CreateDeviceButton(description);
auto device_entry_view = std::make_unique<AudioDeviceEntryView>(
foreground_color_, background_color_, description.unique_id,
description.device_name, "");
device_entry_view->set_listener(this);
audio_device_entries_container_->AddChildView(std::move(device_entry_view));
if (!current_device_still_exists &&
description.unique_id == current_device_id_)
current_device_still_exists = true;
}
UpdateCurrentAudioDevice(current_device_id_);
// If the current device no longer exists, fallback to the default device
UpdateCurrentAudioDevice(
current_device_still_exists
? current_device_id_
: media::AudioDeviceDescription::kDefaultDeviceId);
}
void MediaNotificationAudioDeviceSelectorView::UpdateCurrentAudioDevice(
std::string current_device_id) {
auto it = std::find_if(sink_id_map_.begin(), sink_id_map_.end(),
[&current_device_id](auto& item) {
return item.second == current_device_id;
});
void MediaNotificationAudioDeviceSelectorView::OnColorsChanged(
const SkColor& foreground_color,
const SkColor& background_color) {
foreground_color_ = foreground_color;
background_color_ = background_color;
// If the highlighted device is no longer available, highlight the default
// device.
if (it == sink_id_map_.end()) {
return UpdateCurrentAudioDevice(
media::AudioDeviceDescription::kDefaultDeviceId);
expand_button_->SetTextColor(views::MdTextButton::ButtonState::STATE_NORMAL,
foreground_color_);
expand_button_->SetBackground(
views::CreateSolidBackground(background_color_));
SetBackground(views::CreateSolidBackground(background_color_));
for (auto* view : audio_device_entries_container_->children()) {
static_cast<AudioDeviceEntryView*>(view)->OnColorsChanged(
foreground_color_, background_color_);
}
if (current_device_button_)
current_device_button_->SetProminent(false);
current_device_button_ = static_cast<views::MdTextButton*>(it->first);
current_device_button_->SetProminent(true);
device_button_container_->ReorderChildView(current_device_button_, 0);
device_button_container_->Layout();
current_device_id_ = current_device_id;
SchedulePaint();
}
void MediaNotificationAudioDeviceSelectorView::ButtonPressed(
views::Button* sender,
const ui::Event& event) {
auto it = sink_id_map_.find(sender);
if (it != sink_id_map_.end()) {
delegate_->OnAudioSinkChosen(it->second);
if (sender == expand_button_) {
if (is_expanded_)
HideDevices();
else
ShowDevices();
delegate_->OnAudioDeviceSelectorViewSizeChanged();
} else {
DCHECK(std::find(audio_device_entries_container_->children().cbegin(),
audio_device_entries_container_->children().cend(),
sender) !=
audio_device_entries_container_->children().end());
delegate_->OnAudioSinkChosen(
static_cast<AudioDeviceEntryView*>(sender)->GetDeviceId());
}
}
void MediaNotificationAudioDeviceSelectorView::CreateDeviceButton(
const media::AudioDeviceDescription& device_description) {
auto button = std::make_unique<views::MdTextButton>(
this, base::UTF8ToUTF16(device_description.device_name.c_str()));
button->SetImage(views::Button::ButtonState::STATE_NORMAL,
gfx::CreateVectorIcon(vector_icons::kHeadsetIcon,
kDeviceButtonIconSize, SK_ColorBLACK));
// I'm not sure if this border should be used with a MD button, but it
// looks really nice.
// TODO(noahrose): Investigate other border options.
auto border = std::make_unique<views::LabelButtonBorder>();
border->set_insets(kDeviceButtonInsets);
border->set_color(SK_ColorLTGRAY);
button->SetBorder(std::move(border));
sink_id_map_[button.get()] = device_description.unique_id;
device_button_container_->AddChildView(std::move(button));
device_button_container_->Layout();
// static
std::string
MediaNotificationAudioDeviceSelectorView::get_entry_label_for_testing(
views::View* entry_view) {
return static_cast<AudioDeviceEntryView*>(entry_view)
->get_label_for_testing();
}
// static
bool MediaNotificationAudioDeviceSelectorView::
get_entry_is_highlighted_for_testing(views::View* entry_view) {
return static_cast<AudioDeviceEntryView*>(entry_view)
->is_highlighted_for_testing();
}
void MediaNotificationAudioDeviceSelectorView::ShowDevices() {
DCHECK(!is_expanded_);
is_expanded_ = true;
audio_device_entries_container_->SetVisible(true);
PreferredSizeChanged();
}
void MediaNotificationAudioDeviceSelectorView::HideDevices() {
DCHECK(is_expanded_);
is_expanded_ = false;
audio_device_entries_container_->SetVisible(false);
PreferredSizeChanged();
}
bool MediaNotificationAudioDeviceSelectorView::ShouldBeVisible(
......
......@@ -11,9 +11,9 @@
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/layout/box_layout.h"
namespace views {
class Button;
} // namespace views
namespace {
class AudioDeviceEntryView;
} // anonymous namespace
class MediaNotificationAudioDeviceSelectorViewDelegate;
class MediaNotificationService;
......@@ -24,27 +24,30 @@ class MediaNotificationAudioDeviceSelectorView : public views::View,
MediaNotificationAudioDeviceSelectorView(
MediaNotificationAudioDeviceSelectorViewDelegate* delegate,
MediaNotificationService* service,
gfx::Size size,
const std::string& current_device_id);
MediaNotificationAudioDeviceSelectorView(
const MediaNotificationAudioDeviceSelectorView&) = delete;
MediaNotificationAudioDeviceSelectorView& operator=(
const MediaNotificationAudioDeviceSelectorView&) = delete;
const std::string& current_device_id,
const SkColor& foreground_color,
const SkColor& background_color);
~MediaNotificationAudioDeviceSelectorView() override;
// Called when audio output devices are discovered.
void UpdateAvailableAudioDevices(
const media::AudioDeviceDescriptions& device_descriptions);
// Called when an audio device switch has occurred
void UpdateCurrentAudioDevice(std::string current_device_id);
void UpdateCurrentAudioDevice(const std::string& current_device_id);
void OnColorsChanged(const SkColor& foreground_color,
const SkColor& background_color);
// views::ButtonListener
// ButtonListener
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
static std::string get_entry_label_for_testing(views::View* entry_view);
static bool get_entry_is_highlighted_for_testing(views::View* entry_view);
private:
FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest,
DeviceButtonsCreated);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest,
ExpandButtonOpensEntryContainer);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest,
DeviceButtonClickNotifiesContainer);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest,
......@@ -54,31 +57,27 @@ class MediaNotificationAudioDeviceSelectorView : public views::View,
FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest,
DeviceButtonsChange);
void CreateDeviceButton(
const media::AudioDeviceDescription& device_description);
bool ShouldBeVisible(
const media::AudioDeviceDescriptions& device_descriptions);
void ShowDevices();
void HideDevices();
bool is_expanded_ = false;
MediaNotificationAudioDeviceSelectorViewDelegate* const delegate_;
MediaNotificationService* const service_;
std::string current_device_id_;
SkColor foreground_color_, background_color_;
AudioDeviceEntryView* current_device_entry_view_ = nullptr;
// Child views
views::View* expand_button_strip_;
views::LabelButton* expand_button_;
views::View* audio_device_entries_container_;
std::unique_ptr<MediaNotificationDeviceProvider::
GetOutputDevicesCallbackList::Subscription>
audio_device_subscription_;
// Subviews
views::View* device_button_container_ = nullptr;
views::View* expand_button_container_ = nullptr;
views::ToggleImageButton* expand_button_ = nullptr;
views::MdTextButton* current_device_button_ = nullptr;
std::string current_device_id_;
// Maps button pointers to the string ID of the audio sink they represent.
std::map<views::Button*, std::string> sink_id_map_;
base::WeakPtrFactory<MediaNotificationAudioDeviceSelectorView>
weak_ptr_factory_{this};
};
......
......@@ -3,10 +3,10 @@
// found in the LICENSE file.
#include "chrome/browser/ui/views/global_media_controls/media_notification_audio_device_selector_view.h"
#include <memory>
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/util/ranges/algorithm.h"
#include "chrome/browser/media/router/media_router_factory.h"
#include "chrome/browser/media/router/test/mock_media_router.h"
#include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
......@@ -16,8 +16,7 @@
#include "chrome/test/views/chrome_views_test_base.h"
#include "media/audio/audio_device_description.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/gfx/color_palette.h"
class MediaNotificationContainerObserver;
......@@ -93,6 +92,23 @@ class MediaNotificationAudioDeviceSelectorViewTest
ChromeViewsTestBase::TearDown();
}
void SimulateButtonClick(views::View* view) {
view_->ButtonPressed(
static_cast<views::Button*>(view),
ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), 0, 0));
}
static std::string EntryLabelText(views::View* entry_view) {
return MediaNotificationAudioDeviceSelectorView::
get_entry_label_for_testing(entry_view);
}
static bool IsHighlighted(views::View* entry_view) {
return MediaNotificationAudioDeviceSelectorView::
get_entry_is_highlighted_for_testing(entry_view);
}
std::string GetButtonText(views::View* view) {
return base::UTF16ToUTF8(static_cast<views::LabelButton*>(view)->GetText());
}
......@@ -112,25 +128,37 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest, DeviceButtonsCreated) {
MockMediaNotificationAudioDeviceSelectorViewDelegate delegate;
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
&delegate, service_.get(), gfx::Size(), "1");
std::vector<std::string> button_texts;
ASSERT_TRUE(view_->device_button_container_ != nullptr);
std::transform(
view_->device_button_container_->children().cbegin(),
view_->device_button_container_->children().cend(),
std::back_inserter(button_texts), [](views::View* child) {
return base::UTF16ToASCII(
static_cast<const views::LabelButton*>(child)->GetText());
});
EXPECT_THAT(button_texts, testing::UnorderedElementsAre(
"Speaker", "Headphones", "Earbuds"));
&delegate, service_.get(), "1", gfx::kPlaceholderColor,
gfx::kPlaceholderColor);
ASSERT_TRUE(view_->audio_device_entries_container_ != nullptr);
auto container_children = view_->audio_device_entries_container_->children();
ASSERT_EQ(container_children.size(), 3u);
EXPECT_EQ(EntryLabelText(container_children.at(0)), "Speaker");
EXPECT_EQ(EntryLabelText(container_children.at(1)), "Headphones");
EXPECT_EQ(EntryLabelText(container_children.at(2)), "Earbuds");
}
TEST_F(MediaNotificationAudioDeviceSelectorViewTest,
ExpandButtonOpensEntryContainer) {
provider_->AddDevice("Speaker", "1");
service_->set_device_provider_for_testing(std::move(provider_));
MockMediaNotificationAudioDeviceSelectorViewDelegate delegate;
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
&delegate, service_.get(), "1", gfx::kPlaceholderColor,
gfx::kPlaceholderColor);
ASSERT_TRUE(view_->expand_button_);
EXPECT_FALSE(view_->audio_device_entries_container_->GetVisible());
SimulateButtonClick(view_->expand_button_);
EXPECT_TRUE(view_->audio_device_entries_container_->GetVisible());
}
TEST_F(MediaNotificationAudioDeviceSelectorViewTest,
DeviceButtonClickNotifiesContainer) {
// When buttons are clicked the media notification container should be
// When buttons are clicked the media notification delegate should be
// informed.
provider_->AddDevice("Speaker", "1");
provider_->AddDevice("Headphones", "2");
......@@ -143,13 +171,12 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest,
EXPECT_CALL(delegate, OnAudioSinkChosen("3")).Times(1);
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
&delegate, service_.get(), gfx::Size(), "1");
&delegate, service_.get(), "1", gfx::kPlaceholderColor,
gfx::kPlaceholderColor);
for (views::View* child : view_->device_button_container_->children()) {
view_->ButtonPressed(
static_cast<views::Button*>(child),
ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), 0, 0));
for (views::View* child :
view_->audio_device_entries_container_->children()) {
SimulateButtonClick(child);
}
}
......@@ -163,12 +190,13 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest, CurrentDeviceHighlighted) {
MockMediaNotificationAudioDeviceSelectorViewDelegate delegate;
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
&delegate, service_.get(), gfx::Size(), "3");
&delegate, service_.get(), "3", gfx::kPlaceholderColor,
gfx::kPlaceholderColor);
auto* first_button = static_cast<views::MdTextButton*>(
view_->device_button_container_->children().front());
EXPECT_EQ(first_button->GetText(), base::UTF8ToUTF16("Earbuds"));
EXPECT_TRUE(first_button->GetProminent());
auto* first_entry = static_cast<views::MdTextButton*>(
view_->audio_device_entries_container_->children().front());
EXPECT_EQ(EntryLabelText(first_entry), "Earbuds");
EXPECT_TRUE(IsHighlighted(first_entry));
}
TEST_F(MediaNotificationAudioDeviceSelectorViewTest,
......@@ -181,31 +209,26 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest,
MockMediaNotificationAudioDeviceSelectorViewDelegate delegate;
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
&delegate, service_.get(), gfx::Size(), "1");
auto button_is_highlighted = [](views::View* view) {
return static_cast<views::MdTextButton*>(view)->GetProminent();
};
&delegate, service_.get(), "1", gfx::kPlaceholderColor,
gfx::kPlaceholderColor);
auto& buttons = view_->device_button_container_->children();
auto& container_children = view_->audio_device_entries_container_->children();
// There should be only one highlighted button. It should be the first button.
// It's text should be "Speaker"
EXPECT_EQ(
std::count_if(buttons.begin(), buttons.end(), button_is_highlighted), 1);
EXPECT_EQ(std::find_if(buttons.begin(), buttons.end(), button_is_highlighted),
buttons.begin());
EXPECT_EQ(GetButtonText(buttons.front()), "Speaker");
EXPECT_EQ(util::ranges::count_if(container_children, IsHighlighted), 1);
EXPECT_EQ(util::ranges::find_if(container_children, IsHighlighted),
container_children.begin());
EXPECT_EQ(EntryLabelText(container_children.front()), "Speaker");
// Simulate a device change
view_->UpdateCurrentAudioDevice("3");
// The button for "Earbuds" should come before all others & be highlighted.
EXPECT_EQ(
std::count_if(buttons.begin(), buttons.end(), button_is_highlighted), 1);
EXPECT_EQ(std::find_if(buttons.begin(), buttons.end(), button_is_highlighted),
buttons.begin());
EXPECT_EQ(GetButtonText(buttons.front()), "Earbuds");
EXPECT_EQ(util::ranges::count_if(container_children, IsHighlighted), 1);
EXPECT_EQ(util::ranges::find_if(container_children, IsHighlighted),
container_children.begin());
EXPECT_EQ(EntryLabelText(container_children.front()), "Earbuds");
}
TEST_F(MediaNotificationAudioDeviceSelectorViewTest, DeviceButtonsChange) {
......@@ -219,10 +242,8 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest, DeviceButtonsChange) {
MockMediaNotificationAudioDeviceSelectorViewDelegate delegate;
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
&delegate, service_.get(), gfx::Size(), "1");
std::vector<std::string> button_texts;
ASSERT_TRUE(view_->device_button_container_ != nullptr);
&delegate, service_.get(), "1", gfx::kPlaceholderColor,
gfx::kPlaceholderColor);
provider->ResetDevices();
// Make "Monitor" the default device.
......@@ -230,15 +251,14 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest, DeviceButtonsChange) {
media::AudioDeviceDescription::kDefaultDeviceId);
provider->RunUICallback();
EXPECT_EQ(view_->device_button_container_->children().size(), 1u);
ASSERT_FALSE(view_->device_button_container_->children().empty());
auto* button = static_cast<const views::MdTextButton*>(
view_->device_button_container_->children().at(0));
EXPECT_EQ(base::UTF16ToUTF8(button->GetText()), "Monitor");
auto& container_children = view_->audio_device_entries_container_->children();
EXPECT_EQ(container_children.size(), 1u);
ASSERT_FALSE(container_children.empty());
EXPECT_EQ(EntryLabelText(container_children.front()), "Monitor");
// When the device highlighted in the UI is removed, the UI should fall back
// to highlighting the default device.
EXPECT_TRUE(button->GetProminent());
EXPECT_TRUE(IsHighlighted(container_children.front()));
}
TEST_F(MediaNotificationAudioDeviceSelectorViewTest, VisibilityChanges) {
......@@ -253,7 +273,8 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest, VisibilityChanges) {
MockMediaNotificationAudioDeviceSelectorViewDelegate delegate;
EXPECT_CALL(delegate, OnAudioDeviceSelectorViewSizeChanged).Times(1);
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
&delegate, service_.get(), gfx::Size(), "1");
&delegate, service_.get(), "1", gfx::kPlaceholderColor,
gfx::kPlaceholderColor);
EXPECT_FALSE(view_->GetVisible());
testing::Mock::VerifyAndClearExpectations(&delegate);
......
......@@ -50,9 +50,6 @@ constexpr int kMinVisibleActionsForExpanding = 4;
// press as a click.
constexpr int kMinMovementSquaredToBeDragging = 10;
// The height of the |MediaNotificationAudioDeviceSelectorView|.
constexpr int kAudioDeviceSelectorViewHeight = 40;
} // anonymous namespace
class MediaNotificationContainerImplView::DismissButton
......@@ -128,8 +125,8 @@ MediaNotificationContainerImplView::MediaNotificationContainerImplView(
!is_cast_notification) {
auto audio_device_selector_view =
std::make_unique<MediaNotificationAudioDeviceSelectorView>(
this, service_, gfx::Size(kWidth, kAudioDeviceSelectorViewHeight),
audio_sink_id_);
this, service_, audio_sink_id_, foreground_color_,
background_color_);
audio_device_selector_view_ =
AddChildView(std::move(audio_device_selector_view));
view_->UpdateCornerRadius(message_center::kNotificationCornerRadius, 0);
......@@ -334,6 +331,8 @@ void MediaNotificationContainerImplView::OnColorsChanged(SkColor foreground,
background_color_ = background;
UpdateDismissButtonBackground();
}
if (audio_device_selector_view_)
audio_device_selector_view_->OnColorsChanged(foreground, background);
}
void MediaNotificationContainerImplView::OnHeaderClicked() {
......@@ -484,6 +483,8 @@ void MediaNotificationContainerImplView::OnSizeChanged() {
DCHECK(audio_device_selector_view_size.width() == kWidth);
new_size.set_height(new_size.height() +
audio_device_selector_view_size.height());
view_->UpdateAudioDeviceSelectorAvailability(
audio_device_selector_view_->GetVisible());
}
if (overlay_)
......
......@@ -302,6 +302,25 @@ void MediaNotificationBackground::Paint(gfx::Canvas* canvas,
canvas->DrawRect(draw_bounds, flags);
}
if (audio_device_selector_availability_) {
// Draw a gradient to fade the color background of the audio device picker
// and the image together.
gfx::Rect draw_bounds = GetBottomGradientBounds(*view);
const SkColor colors[2] = {
background_color, SkColorSetA(background_color, SK_AlphaTRANSPARENT)};
const SkPoint points[2] = {gfx::PointToSkPoint(draw_bounds.bottom_center()),
gfx::PointToSkPoint(draw_bounds.top_center())};
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setShader(cc::PaintShader::MakeLinearGradient(points, colors, nullptr,
2, SkTileMode::kClamp));
canvas->DrawRect(draw_bounds, flags);
}
}
void MediaNotificationBackground::UpdateArtwork(const gfx::ImageSkia& image) {
......@@ -344,6 +363,14 @@ void MediaNotificationBackground::UpdateFavicon(const gfx::ImageSkia& icon) {
UpdateColorsInternal();
}
void MediaNotificationBackground::UpdateAudioDeviceSelectorAvailability(
bool availability) {
if (audio_device_selector_availability_ == availability)
return;
audio_device_selector_availability_ = availability;
}
SkColor MediaNotificationBackground::GetBackgroundColor(
const views::View& owner) const {
if (background_color_.has_value())
......@@ -421,6 +448,20 @@ gfx::Rect MediaNotificationBackground::GetGradientBounds(
view_bounds.y(), kMediaImageGradientWidth, view_bounds.height()));
}
gfx::Rect MediaNotificationBackground::GetBottomGradientBounds(
const views::View& owner) const {
if (artwork_.isNull())
return gfx::Rect(0, 0, 0, 0);
const gfx::Rect& view_bounds = owner.GetContentsBounds();
return owner.GetMirroredRect(gfx::Rect(
gfx::Point(
view_bounds.width() - GetArtworkVisibleWidth(view_bounds.size()),
view_bounds.bottom() - kMediaImageGradientWidth),
gfx::Size(GetArtworkVisibleWidth(view_bounds.size()),
kMediaImageGradientWidth)));
}
SkPoint MediaNotificationBackground::GetGradientStartPoint(
const gfx::Rect& draw_bounds) const {
return gfx::PointToSkPoint(base::i18n::IsRTL() ? draw_bounds.right_center()
......
......@@ -41,6 +41,7 @@ class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationBackground
bool UpdateCornerRadius(int top_radius, int bottom_radius);
bool UpdateArtworkMaxWidthPct(double max_width_pct);
void UpdateFavicon(const gfx::ImageSkia& icon);
void UpdateAudioDeviceSelectorAvailability(bool availability);
SkColor GetBackgroundColor(const views::View& owner) const;
SkColor GetForegroundColor(const views::View& owner) const;
......@@ -59,6 +60,7 @@ class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationBackground
gfx::Rect GetArtworkBounds(const views::View& owner) const;
gfx::Rect GetFilledBackgroundBounds(const views::View& owner) const;
gfx::Rect GetGradientBounds(const views::View& owner) const;
gfx::Rect GetBottomGradientBounds(const views::View& owner) const;
SkPoint GetGradientStartPoint(const gfx::Rect& draw_bounds) const;
SkPoint GetGradientEndPoint(const gfx::Rect& draw_bounds) const;
SkColor GetDefaultBackgroundColor(const views::View& owner) const;
......@@ -70,6 +72,7 @@ class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationBackground
gfx::ImageSkia favicon_;
gfx::ImageSkia artwork_;
double artwork_max_width_pct_;
bool audio_device_selector_availability_;
base::Optional<SkColor> background_color_;
base::Optional<SkColor> foreground_color_;
......
......@@ -474,6 +474,12 @@ void MediaNotificationViewImpl::OnThemeChanged() {
UpdateForegroundColor();
}
void MediaNotificationViewImpl::UpdateAudioDeviceSelectorAvailability(
bool availability) {
GetMediaNotificationBackground()->UpdateAudioDeviceSelectorAvailability(
availability);
}
views::Button* MediaNotificationViewImpl::GetHeaderRowForTesting() const {
return header_row_;
}
......
......@@ -83,6 +83,8 @@ class COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER) MediaNotificationViewImpl
void UpdateWithVectorIcon(const gfx::VectorIcon& vector_icon) override;
void OnThemeChanged() override;
void UpdateAudioDeviceSelectorAvailability(bool availability);
const views::Label* title_label_for_testing() const { return title_label_; }
const views::Label* artist_label_for_testing() const { return artist_label_; }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment