Commit 633cd1bc authored by Richard Knoll's avatar Richard Knoll Committed by Commit Bot

Update Click to Call dialog illustration.

This updates the illustration and shows it for all dialog types except
the error dialog. The illustration is shown behind the dialog close
button above the title.

The BubbleFrameView is updated to support this illustration by adding a
header view. This view is similar to the existing footnote view and is
shown at the top of the dialog, above the title and behind the close
button.

Bug: 993378
Change-Id: I9d236557c2a3f31197441666e5aae526306e00cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1778545Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Commit-Queue: Richard Knoll <knollr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#693614}
parent 85d4a0fa
......@@ -76,6 +76,8 @@
<structure type="chrome_scaled_image" name="IDR_BUTTON_USER_IMAGE_TAKE_PHOTO" file="cros/take_photo.png" />
</if>
<if expr="not is_android">
<structure type="chrome_scaled_image" name="IDR_CLICK_TO_CALL_ILLUSTRATION_LIGHT" file="common/click_to_call_illustration_light.png" />
<structure type="chrome_scaled_image" name="IDR_CLICK_TO_CALL_ILLUSTRATION_DARK" file="common/click_to_call_illustration_dark.png" />
<!-- Note: Tab close buttons are not traditional buttons. Tab close buttons
fill a background with a color from the theme and tile IDR_CLOSE_1 over it.
See chrome/browser/ui/views/tabs/tab.cc -->
......@@ -255,7 +257,6 @@
</if>
<if expr="not is_android">
<structure type="chrome_scaled_image" name="IDR_SETTINGS_FAVICON" file="common/favicon_settings.png" />
<structure type="chrome_scaled_image" name="IDR_SHARING_EMPTY_LIST" file="common/sharing_empty_list.png" />
<structure type="chrome_scaled_image" name="IDR_SHOW_PASSWORD_HOVER" file="common/show_password_hover.png" />
</if>
<if expr="chromeos">
......
......@@ -17,11 +17,13 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/grit/theme_resources.h"
#include "components/sync_device_info/device_info.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/native_theme/native_theme.h"
#include "ui/strings/grit/ui_strings.h"
using SharingMessage = chrome_browser_sharing::SharingMessage;
......@@ -162,4 +164,17 @@ void ClickToCallUiController::OnHelpTextClicked(SharingDialogType dialog_type) {
SharingUiController::OnHelpTextClicked(dialog_type);
}
int ClickToCallUiController::GetHeaderImageId() const {
// Do not add the header image for error dialogs.
if (HasSendFailed())
return 0;
const ui::NativeTheme* native_theme =
ui::NativeTheme::GetInstanceForNativeUi();
bool is_dark = native_theme && native_theme->ShouldUseDarkColors();
return is_dark ? IDR_CLICK_TO_CALL_ILLUSTRATION_DARK
: IDR_CLICK_TO_CALL_ILLUSTRATION_LIGHT;
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(ClickToCallUiController)
......@@ -51,6 +51,7 @@ class ClickToCallUiController
SharingFeatureName GetFeatureMetricsPrefix() const override;
base::string16 GetEducationWindowTitleText() const override;
void OnHelpTextClicked(SharingDialogType dialog_type) override;
int GetHeaderImageId() const override;
protected:
explicit ClickToCallUiController(content::WebContents* web_contents);
......
......@@ -195,6 +195,10 @@ base::string16 SharingUiController::GetErrorDialogText() const {
}
}
int SharingUiController::GetHeaderImageId() const {
return 0;
}
void SharingUiController::OnAppsReceived(int dialog_id, std::vector<App> apps) {
if (dialog_id != last_dialog_id_)
return;
......
......@@ -87,6 +87,9 @@ class SharingUiController {
// |send_result_|.
virtual base::string16 GetErrorDialogText() const;
// Returns the image id shown as a header in the dialog.
virtual int GetHeaderImageId() const;
// Returns the currently open SharingDialog or nullptr if there is no
// dialog open.
SharingDialog* dialog() const { return dialog_; }
......
......@@ -15,13 +15,13 @@
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/hover_button.h"
#include "chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h"
#include "chrome/grit/theme_resources.h"
#include "components/sync_device_info/device_info.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
......@@ -31,8 +31,7 @@ namespace {
// Icon sizes in DIP.
constexpr int kPrimaryIconSize = 20;
constexpr int kPrimaryIconBorderWidth = 8;
constexpr int kEmptyImageHeight = 88;
constexpr int kEmptyImageTopPadding = 16;
constexpr int kHeaderImageHeight = 100;
SkColor GetColorFromTheme() {
const ui::NativeTheme* native_theme =
......@@ -49,23 +48,21 @@ std::unique_ptr<views::ImageView> CreateIconView(const gfx::ImageSkia& icon) {
return icon_view;
}
std::unique_ptr<views::ImageView> CreateVectorIconView(
const gfx::VectorIcon& vector_icon) {
return CreateIconView(gfx::CreateVectorIcon(vector_icon, kPrimaryIconSize,
GetColorFromTheme()));
gfx::ImageSkia CreateVectorIcon(const gfx::VectorIcon& vector_icon) {
return gfx::CreateVectorIcon(vector_icon, kPrimaryIconSize,
GetColorFromTheme());
}
std::unique_ptr<views::ImageView> CreateDeviceIcon(
gfx::ImageSkia CreateDeviceIcon(
const sync_pb::SyncEnums::DeviceType device_type) {
return CreateVectorIconView(device_type == sync_pb::SyncEnums::TYPE_TABLET
? kTabletIcon
: kHardwareSmartphoneIcon);
return CreateVectorIcon(device_type == sync_pb::SyncEnums::TYPE_TABLET
? kTabletIcon
: kHardwareSmartphoneIcon);
}
std::unique_ptr<views::ImageView> CreateAppIcon(
const SharingUiController::App& app) {
return app.vector_icon ? CreateVectorIconView(*app.vector_icon)
: CreateIconView(app.image.AsImageSkia());
gfx::ImageSkia CreateAppIcon(const SharingUiController::App& app) {
return app.vector_icon ? CreateVectorIcon(*app.vector_icon)
: app.image.AsImageSkia();
}
std::unique_ptr<views::StyledLabel> CreateHelpText(
......@@ -143,36 +140,37 @@ SharingDialogType SharingDialogView::GetDialogType() const {
void SharingDialogView::Init() {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
dialog_buttons_.clear();
button_icons_.clear();
auto* provider = ChromeLayoutProvider::Get();
gfx::Insets insets =
provider->GetDialogInsetsForContentType(views::TEXT, views::TEXT);
SharingDialogType type = GetDialogType();
LogSharingDialogShown(controller_->GetFeatureMetricsPrefix(), type);
switch (type) {
case SharingDialogType::kErrorDialog:
set_margins(
provider->GetDialogInsetsForContentType(views::TEXT, views::TEXT));
InitErrorView();
break;
case SharingDialogType::kEducationalDialog:
set_margins(
provider->GetDialogInsetsForContentType(views::TEXT, views::TEXT));
InitEmptyView();
break;
case SharingDialogType::kDialogWithoutDevicesWithApp:
case SharingDialogType::kDialogWithDevicesMaybeApps:
set_margins(
gfx::Insets(provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL),
0,
provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL),
0));
// Spread buttons across the whole dialog width.
int top_margin = provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL);
int bottom_margin = provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL);
insets = gfx::Insets(top_margin, 0, bottom_margin, 0);
InitListView();
break;
}
set_margins(gfx::Insets(insets.top(), 0, insets.bottom(), 0));
SetBorder(views::CreateEmptyBorder(0, insets.left(), 0, insets.right()));
// TODO(yasmo): See if GetWidget can be not null:
if (GetWidget())
SizeToContents();
......@@ -206,6 +204,56 @@ void SharingDialogView::ButtonPressed(views::Button* sender,
}
}
void SharingDialogView::MaybeShowHeaderImage() {
views::BubbleFrameView* frame_view = GetBubbleFrameView();
if (!frame_view)
return;
int image_id = controller_->GetHeaderImageId();
if (!image_id) {
// Clear any previously set header image.
frame_view->SetHeaderView(nullptr);
return;
}
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
const gfx::ImageSkia* image = rb.GetNativeImageNamed(image_id).ToImageSkia();
gfx::Size image_size(image->width(), image->height());
const int image_width = image->width() * kHeaderImageHeight / image->height();
image_size.SetToMin(gfx::Size(image_width, kHeaderImageHeight));
auto image_view = std::make_unique<NonAccessibleImageView>();
image_view->SetImageSize(image_size);
image_view->SetImage(*image);
frame_view->SetHeaderView(std::move(image_view));
}
void SharingDialogView::AddedToWidget() {
MaybeShowHeaderImage();
}
void SharingDialogView::OnThemeChanged() {
LocationBarBubbleDelegateView::OnThemeChanged();
MaybeShowHeaderImage();
const std::vector<std::unique_ptr<syncer::DeviceInfo>>& devices =
controller_->devices();
const std::vector<SharingUiController::App>& apps = controller_->apps();
DCHECK_EQ(devices.size() + apps.size(), button_icons_.size());
size_t button_index = 0;
for (const auto& device : devices) {
button_icons_[button_index]->SetImage(
CreateDeviceIcon(device->device_type()));
button_index++;
}
for (const auto& app : apps) {
button_icons_[button_index]->SetImage(CreateAppIcon(app));
button_index++;
}
}
void SharingDialogView::InitListView() {
const std::vector<std::unique_ptr<syncer::DeviceInfo>>& devices =
controller_->devices();
......@@ -216,9 +264,10 @@ void SharingDialogView::InitListView() {
LogSharingDevicesToShow(controller_->GetFeatureMetricsPrefix(),
kSharingUiDialog, devices.size());
for (const auto& device : devices) {
auto icon = CreateIconView(CreateDeviceIcon(device->device_type()));
button_icons_.push_back(icon.get());
auto dialog_button = std::make_unique<HoverButton>(
this, CreateDeviceIcon(device->device_type()),
base::UTF8ToUTF16(device->client_name()),
this, std::move(icon), base::UTF8ToUTF16(device->client_name()),
GetLastUpdatedTimeInDays(device->last_updated_timestamp()));
dialog_button->SetEnabled(true);
dialog_button->set_tag(tag++);
......@@ -229,8 +278,10 @@ void SharingDialogView::InitListView() {
LogSharingAppsToShow(controller_->GetFeatureMetricsPrefix(), kSharingUiDialog,
apps.size());
for (const auto& app : apps) {
auto icon = CreateIconView(CreateAppIcon(app));
button_icons_.push_back(icon.get());
auto dialog_button =
std::make_unique<HoverButton>(this, CreateAppIcon(app), app.name,
std::make_unique<HoverButton>(this, std::move(icon), app.name,
/* subtitle= */ base::string16());
dialog_button->SetEnabled(true);
dialog_button->set_tag(tag++);
......@@ -240,21 +291,6 @@ void SharingDialogView::InitListView() {
void SharingDialogView::InitEmptyView() {
AddChildView(CreateHelpText(this));
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
auto image_view = std::make_unique<NonAccessibleImageView>();
const gfx::ImageSkia* image =
rb.GetNativeImageNamed(IDR_SHARING_EMPTY_LIST).ToImageSkia();
gfx::Size image_size(image->width(), image->height());
const int image_width = image->width() * kEmptyImageHeight / image->height();
image_size.SetToMin(gfx::Size(image_width, kEmptyImageHeight));
image_view->SetImageSize(image_size);
image_view->SetImage(*image);
image_view->SetBorder(
views::CreateEmptyBorder(kEmptyImageTopPadding, 0, 0, 0));
AddChildView(std::move(image_view));
}
void SharingDialogView::InitErrorView() {
......
......@@ -15,6 +15,7 @@
#include "ui/views/controls/styled_label_listener.h"
namespace views {
class ImageView;
class StyledLabel;
class View;
} // namespace views
......@@ -57,6 +58,8 @@ class SharingDialogView : public SharingDialog,
// views::View:
gfx::Size CalculatePreferredSize() const override;
void AddedToWidget() override;
void OnThemeChanged() override;
static views::BubbleDialogDelegateView* GetAsBubble(SharingDialog* dialog);
......@@ -71,6 +74,9 @@ class SharingDialogView : public SharingDialog,
// views::BubbleDialogDelegateView:
void Init() override;
// Shows a header image in the dialog view.
void MaybeShowHeaderImage();
// Populates the dialog view containing valid devices and apps.
void InitListView();
// Populates the dialog view containing no devices or apps.
......@@ -79,9 +85,10 @@ class SharingDialogView : public SharingDialog,
void InitErrorView();
SharingUiController* controller_ = nullptr;
// Contains references to device and app buttons in
// the order they appear.
// References to device and app buttons views.
std::vector<HoverButton*> dialog_buttons_;
// References to device and app button icons.
std::vector<views::ImageView*> button_icons_;
Browser* browser_ = nullptr;
bool send_failed_ = false;
......
......@@ -72,14 +72,11 @@ const char BubbleFrameView::kViewClassName[] = "BubbleFrameView";
BubbleFrameView::BubbleFrameView(const gfx::Insets& title_margins,
const gfx::Insets& content_margins)
: bubble_border_(nullptr),
title_margins_(title_margins),
: title_margins_(title_margins),
content_margins_(content_margins),
footnote_margins_(content_margins_),
title_icon_(new views::ImageView()),
default_title_(CreateDefaultTitleLabel(base::string16()).release()),
custom_title_(nullptr),
footnote_container_(nullptr) {
default_title_(CreateDefaultTitleLabel(base::string16()).release()) {
AddChildView(title_icon_);
default_title_->SetVisible(false);
......@@ -267,7 +264,7 @@ void BubbleFrameView::SetTitleView(std::unique_ptr<View> title_view) {
delete custom_title_;
custom_title_ = title_view.get();
// Keep the title after the icon for focus order.
AddChildViewAt(title_view.release(), 1);
AddChildViewAt(title_view.release(), GetIndexOf(title_icon_) + 1);
}
const char* BubbleFrameView::GetClassName() const {
......@@ -320,6 +317,19 @@ void BubbleFrameView::Layout() {
const gfx::Rect contents_bounds = GetContentsBounds();
gfx::Rect bounds = contents_bounds;
bounds.Inset(title_margins_);
int header_bottom = 0;
gfx::Size header_pref_size = GetHeaderSize();
if (!header_pref_size.IsEmpty()) {
int header_width = header_pref_size.width();
int header_offset = (contents_bounds.width() - header_width) / 2;
header_view_->SetBounds(contents_bounds.x() + header_offset,
contents_bounds.y(), header_pref_size.width(),
header_pref_size.height());
bounds.Inset(0, header_pref_size.height(), 0, 0);
header_bottom = header_view_->bounds().bottom();
}
if (bounds.IsEmpty())
return;
......@@ -331,7 +341,11 @@ void BubbleFrameView::Layout() {
close_->SetPosition(
gfx::Point(contents_bounds.right() - close_margin - close_->width(),
contents_bounds.y() + close_margin));
title_label_right = std::min(title_label_right, close_->x() - close_margin);
// Only reserve space if the close button extends over the header.
if (close_->bounds().bottom() > header_bottom) {
title_label_right =
std::min(title_label_right, close_->x() - close_margin);
}
}
gfx::Size title_icon_pref_size(title_icon_->GetPreferredSize());
......@@ -443,6 +457,16 @@ void BubbleFrameView::SetBubbleBorder(std::unique_ptr<BubbleBorder> border) {
SetBackground(std::make_unique<views::BubbleBackground>(bubble_border_));
}
void BubbleFrameView::SetHeaderView(std::unique_ptr<View> view) {
if (header_view_) {
delete header_view_;
header_view_ = nullptr;
}
if (view)
header_view_ = AddChildViewAt(std::move(view), 0);
}
void BubbleFrameView::SetFootnoteView(std::unique_ptr<View> view) {
if (!view) {
delete footnote_container_;
......@@ -665,14 +689,20 @@ bool BubbleFrameView::HasTitle() const {
}
gfx::Insets BubbleFrameView::GetTitleLabelInsetsFromFrame() const {
int header_height = GetHeaderSize().height();
int insets_right = 0;
if (GetWidget()->widget_delegate()->ShouldShowCloseButton()) {
const int close_margin =
LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
insets_right = 2 * close_margin + close_->width();
// Note: |close_margin| is not applied on the bottom of the icon.
int close_height = close_margin + close_->height();
// Only reserve space if the close button extends over the header.
if (close_height > header_height)
insets_right = 2 * close_margin + close_->width();
}
if (!HasTitle())
return gfx::Insets(0, 0, 0, insets_right);
return gfx::Insets(header_height, 0, 0, insets_right);
insets_right = std::max(insets_right, title_margins_.right());
const gfx::Size title_icon_pref_size = title_icon_->GetPreferredSize();
......@@ -680,12 +710,13 @@ gfx::Insets BubbleFrameView::GetTitleLabelInsetsFromFrame() const {
title_icon_pref_size.width() > 0 ? title_margins_.left() : 0;
const int insets_left =
title_margins_.left() + title_icon_pref_size.width() + title_icon_padding;
return gfx::Insets(title_margins_.top(), insets_left, title_margins_.bottom(),
insets_right);
return gfx::Insets(header_height + title_margins_.top(), insets_left,
title_margins_.bottom(), insets_right);
}
gfx::Insets BubbleFrameView::GetClientInsetsForFrameWidth(
int frame_width) const {
int header_height = GetHeaderSize().height();
int close_height = 0;
if (!ExtendClientIntoTitle() &&
GetWidget()->widget_delegate()->ShouldShowCloseButton()) {
......@@ -694,8 +725,11 @@ gfx::Insets BubbleFrameView::GetClientInsetsForFrameWidth(
// Note: |close_margin| is not applied on the bottom of the icon.
close_height = close_margin + close_->height();
}
if (!HasTitle())
return content_margins_ + gfx::Insets(close_height, 0, 0, 0);
if (!HasTitle()) {
return content_margins_ +
gfx::Insets(std::max(header_height, close_height), 0, 0, 0);
}
const int icon_height = title_icon_->GetPreferredSize().height();
const int label_height = title()->GetHeightForWidth(
......@@ -703,7 +737,14 @@ gfx::Insets BubbleFrameView::GetClientInsetsForFrameWidth(
const int title_height =
std::max(icon_height, label_height) + title_margins_.height();
return content_margins_ +
gfx::Insets(std::max(title_height, close_height), 0, 0, 0);
gfx::Insets(std::max(title_height + header_height, close_height), 0, 0,
0);
}
gfx::Size BubbleFrameView::GetHeaderSize() const {
return header_view_ && header_view_->GetVisible()
? header_view_->GetPreferredSize()
: gfx::Size();
}
} // namespace views
......@@ -89,6 +89,19 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
gfx::Insets content_margins() const { return content_margins_; }
// Sets a custom header view for the dialog. If there is an existing header
// view it will be deleted. The header view will be inserted above the title,
// so outside the content bounds. If there is a close button, it will be shown
// in front of the header view and will overlap with it. The title will be
// shown below the header and / or the close button, depending on which is
// lower. An example usage for a header view would be a banner image.
void SetHeaderView(std::unique_ptr<View> view);
// Sets a custom footnote view for the dialog. If there is an existing
// footnote view it will be deleted. The footnote will be rendered at the
// bottom of the bubble, after the content view. It is separated by a 1 dip
// line and has a solid background by being embedded in a
// FootnoteContainerView. An example footnote would be some help text.
void SetFootnoteView(std::unique_ptr<View> view);
void set_footnote_margins(const gfx::Insets& footnote_margins) {
footnote_margins_ = footnote_margins;
......@@ -190,8 +203,12 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
// The client_view insets (from the frame view) for the given |frame_width|.
gfx::Insets GetClientInsetsForFrameWidth(int frame_width) const;
// Gets the size of the |header_view_| or an empty size if there is no header
// view or if it is not visible.
gfx::Size GetHeaderSize() const;
// The bubble border.
BubbleBorder* bubble_border_;
BubbleBorder* bubble_border_ = nullptr;
// Margins around the title label.
gfx::Insets title_margins_;
......@@ -203,19 +220,22 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
gfx::Insets footnote_margins_;
// The optional title icon.
views::ImageView* title_icon_;
ImageView* title_icon_ = nullptr;
// One of these fields is used as the dialog title. If SetTitleView is called
// the custom title view is stored in |custom_title_| and this class assumes
// ownership. Otherwise |default_title_| is used.
Label* default_title_;
View* custom_title_;
Label* default_title_ = nullptr;
View* custom_title_ = nullptr;
// The optional close button (the X).
Button* close_;
Button* close_ = nullptr;
// The optional header view.
View* header_view_ = nullptr;
// A view to contain the footnote view, if it exists.
FootnoteContainerView* footnote_container_;
FootnoteContainerView* footnote_container_ = nullptr;
// Set preference for how the arrow will be adjusted if the window is outside
// the available bounds.
......
......@@ -853,6 +853,60 @@ TEST_F(BubbleFrameViewTest, GetMaximumSize) {
#endif
}
TEST_F(BubbleFrameViewTest, LayoutWithHeader) {
// Test header view: adding a header should increase the preferred size, but
// only when the header is visible.
TestBubbleFrameView frame(this);
constexpr int kHeaderHeight = 20;
const gfx::Size no_header_size = frame.GetPreferredSize();
std::unique_ptr<View> header =
std::make_unique<StaticSizedView>(gfx::Size(10, kHeaderHeight));
header->SetVisible(false);
View* header_raw_pointer = header.get();
frame.SetHeaderView(std::move(header));
EXPECT_EQ(no_header_size, frame.GetPreferredSize()); // No change.
header_raw_pointer->SetVisible(true);
gfx::Size with_header_size = no_header_size;
with_header_size.Enlarge(0, kHeaderHeight);
EXPECT_EQ(with_header_size, frame.GetPreferredSize());
header_raw_pointer->SetVisible(false);
EXPECT_EQ(no_header_size, frame.GetPreferredSize());
}
TEST_F(BubbleFrameViewTest, LayoutWithHeaderAndCloseButton) {
// Test header view with close button: the client bounds should be positioned
// below the header and close button, whichever is further down.
TestBubbleFrameView frame(this);
frame.widget_delegate()->SetShouldShowCloseButton(true);
const int close_margin =
frame.GetCloseButtonForTest()->height() +
LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
const gfx::Insets content_margins = frame.content_margins();
const gfx::Insets insets = frame.GetBorderInsets();
// Header is smaller than close button + margin, expect bounds to be below the
// close button.
frame.SetHeaderView(
std::make_unique<StaticSizedView>(gfx::Size(10, close_margin - 1)));
gfx::Rect client_view_bounds = frame.GetBoundsForClientView();
EXPECT_EQ(insets.top() + content_margins.top() + close_margin,
client_view_bounds.y());
// Header is larger than close button + margin, expect bounds to be below the
// header view.
frame.SetHeaderView(
std::make_unique<StaticSizedView>(gfx::Size(10, close_margin + 1)));
client_view_bounds = frame.GetBoundsForClientView();
EXPECT_EQ(insets.top() + content_margins.top() + close_margin + 1,
client_view_bounds.y());
}
namespace {
class TestBubbleDialogDelegateView : public BubbleDialogDelegateView {
......@@ -882,6 +936,10 @@ class TestBubbleDialogDelegateView : public BubbleDialogDelegateView {
bool ShouldShowWindowIcon() const override { return !icon_.isNull(); }
base::string16 GetWindowTitle() const override { return title_; }
bool ShouldShowWindowTitle() const override { return !title_.empty(); }
bool ShouldShowCloseButton() const override { return should_show_close_; }
void SetShouldShowCloseButton(bool should_show_close) {
should_show_close_ = should_show_close;
}
void DeleteDelegate() override {
// This delegate is owned by the test case itself, so it should not delete
......@@ -903,6 +961,7 @@ class TestBubbleDialogDelegateView : public BubbleDialogDelegateView {
gfx::ImageSkia icon_;
base::string16 title_;
bool destroyed_ = false;
bool should_show_close_ = false;
DISALLOW_COPY_AND_ASSIGN(TestBubbleDialogDelegateView);
};
......@@ -1057,6 +1116,60 @@ TEST_F(BubbleFrameViewTest, LayoutEdgeCases) {
// When |anchor| goes out of scope it should take |bubble| with it.
}
// Tests edge cases when the frame's title view starts to wrap text when a
// header view is set. This is to ensure the title leaves enough space for the
// close button when there is a header or not.
TEST_F(BubbleFrameViewTest, LayoutEdgeCasesWithHeader) {
test::TestLayoutProvider provider;
TestBubbleDialogDelegateView delegate;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
delegate.SetAnchorView(anchor.widget().GetContentsView());
delegate.SetShouldShowCloseButton(true);
Widget* bubble = BubbleDialogDelegateView::CreateBubble(&delegate);
bubble->Show();
BubbleFrameView* frame = delegate.GetBubbleFrameView();
const int close_margin =
frame->GetCloseButtonForTest()->height() +
LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
// Set a header view that is 1 dip smaller smaller than the close button.
frame->SetHeaderView(
std::make_unique<StaticSizedView>(gfx::Size(10, close_margin - 1)));
// Starting with a short title.
base::string16 title(1, 'i');
delegate.ChangeTitle(title);
const int min_bubble_height = bubble->GetWindowBoundsInScreen().height();
// Grow the title incrementally until word wrap is required.
while (bubble->GetWindowBoundsInScreen().height() == min_bubble_height) {
title += ' ';
title += 'i';
delegate.ChangeTitle(title);
}
// Sanity check that something interesting happened. The bubble should have
// grown by "a line" for the wrapped title.
const int two_line_height = bubble->GetWindowBoundsInScreen().height();
EXPECT_LT(12, two_line_height - min_bubble_height);
EXPECT_GT(25, two_line_height - min_bubble_height);
// Now grow the header view to be the same size as the close button. This
// should allow the text to fit into a single line again as it is now allowed
// to grow below the close button.
frame->SetHeaderView(
std::make_unique<StaticSizedView>(gfx::Size(10, close_margin)));
delegate.SizeToContents();
// Height should go back to |min_bubble_height| + 1 since the window is wider:
// word wrapping should no longer happen, the 1 dip extra height is caused by
// growing the header view.
EXPECT_EQ(min_bubble_height + 1, bubble->GetWindowBoundsInScreen().height());
// When |anchor| goes out of scope it should take |bubble| with it.
}
TEST_F(BubbleFrameViewTest, LayoutWithIcon) {
TestBubbleDialogDelegateView delegate;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
......
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