Commit 1467bcc7 authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: add throbber support to hover list

This change allows items with a throbber(*) to be added to a hover list
view. These views are used for things like the transport selection and
account selection dialogs.

Have confirmed that there's no pixel differences in either of those
dialogs due to this change. A future change will actually use the
capability.

(*) That's the views name for it. I think most people would call it a
spinner.

BUG=1002262

Change-Id: I3accb09edcef4684275c67bef76b2f5acb229192
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2025576Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#736054}
parent 66fbd46a
......@@ -19,6 +19,7 @@
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/throbber.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/vector_icons.h"
......@@ -67,6 +68,12 @@ class WebauthnHoverButton : public HoverButton {
base::Optional<int> vert_inset_;
};
enum class ItemType {
kButton,
kPlaceholder,
kThrobber,
};
std::unique_ptr<HoverButton> CreateHoverButtonForListItem(
int item_tag,
const gfx::VectorIcon* vector_icon,
......@@ -74,7 +81,7 @@ std::unique_ptr<HoverButton> CreateHoverButtonForListItem(
base::string16 item_description,
views::ButtonListener* listener,
bool is_two_line_item,
bool is_placeholder_item = false) {
ItemType item_type = ItemType::kButton) {
// Derive the icon color from the text color of an enabled label.
auto color_reference_label = std::make_unique<views::Label>(
base::string16(), CONTEXT_BODY_TEXT_SMALL, views::style::STYLE_PRIMARY);
......@@ -88,36 +95,57 @@ std::unique_ptr<HoverButton> CreateHoverButtonForListItem(
gfx::CreateVectorIcon(*vector_icon, kIconSize, icon_color));
}
std::unique_ptr<views::ImageView> chevron_image = nullptr;
std::unique_ptr<views::View> secondary_view = nullptr;
// kTwoLineVertInset is the top and bottom padding of the HoverButton if
// |is_two_line_item| is true. This ensures that the spacing between the two
// lines isn't too large because HoverButton will otherwise spread the lines
// evenly over the given vertical space.
// |is_two_line_item| is true. This ensures that the spacing between the
// two lines isn't too large because HoverButton will otherwise spread the
// lines evenly over the given vertical space.
constexpr int kTwoLineVertInset = 6;
if (!is_placeholder_item) {
constexpr int kChevronSize = 8;
chevron_image = std::make_unique<views::ImageView>();
chevron_image->SetImage(gfx::CreateVectorIcon(views::kSubmenuArrowIcon,
kChevronSize, icon_color));
int chevron_vert_inset = 0;
if (is_two_line_item) {
// Items that are sized for two lines use the top and bottom insets of the
// chevron image to pad single-line items out to a uniform height of
// |kHeight|.
constexpr int kHeight = 56;
chevron_vert_inset =
(kHeight - (2 * kTwoLineVertInset) - kChevronSize) / 2;
switch (item_type) {
case ItemType::kPlaceholder:
// No secondary view in this case.
break;
case ItemType::kButton: {
constexpr int kChevronSize = 8;
auto chevron_image = std::make_unique<views::ImageView>();
chevron_image->SetImage(gfx::CreateVectorIcon(views::kSubmenuArrowIcon,
kChevronSize, icon_color));
int vert_inset = 0;
if (is_two_line_item) {
// Items that are sized for two lines use the top and bottom insets of
// the chevron image to pad single-line items out to a uniform height of
// |kHeight|.
constexpr int kHeight = 56;
const gfx::Size size = chevron_image->GetPreferredSize();
vert_inset = (kHeight - (2 * kTwoLineVertInset) - size.height()) / 2;
}
chevron_image->SetBorder(views::CreateEmptyBorder(
gfx::Insets(/*top=*/vert_inset, /*left=*/12,
/*bottom=*/vert_inset, /*right=*/0)));
secondary_view.reset(chevron_image.release());
break;
}
case ItemType::kThrobber: {
auto throbber = std::make_unique<views::Throbber>();
throbber->Start();
secondary_view.reset(throbber.release());
// A border isn't set for kThrobber items because they are assumed to
// always have a description.
DCHECK(!item_description.empty());
break;
}
chevron_image->SetBorder(views::CreateEmptyBorder(
gfx::Insets(/*top=*/chevron_vert_inset, /*left=*/12,
/*bottom=*/chevron_vert_inset, /*right=*/0)));
}
auto hover_button = std::make_unique<WebauthnHoverButton>(
listener, std::move(item_image), std::move(item_title),
std::move(item_description), std::move(chevron_image));
std::move(item_description), std::move(secondary_view));
hover_button->set_tag(item_tag);
if (!vector_icon) {
hover_button->SetInsetForNoIcon();
......@@ -136,13 +164,24 @@ std::unique_ptr<HoverButton> CreateHoverButtonForListItem(
kHorizontalPadding);
hover_button->SetBorder(views::CreateEmptyBorder(padding));
if (is_placeholder_item) {
hover_button->SetState(HoverButton::ButtonState::STATE_DISABLED);
const auto background_color =
hover_button->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_BubbleBackground);
hover_button->SetTitleTextStyle(views::style::STYLE_DISABLED,
background_color);
switch (item_type) {
case ItemType::kPlaceholder: {
hover_button->SetState(HoverButton::ButtonState::STATE_DISABLED);
const auto background_color =
hover_button->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_BubbleBackground);
hover_button->SetTitleTextStyle(views::style::STYLE_DISABLED,
background_color);
break;
}
case ItemType::kButton:
// No extra styling.
break;
case ItemType::kThrobber:
hover_button->SetState(HoverButton::ButtonState::STATE_DISABLED);
break;
}
return hover_button;
......@@ -172,7 +211,16 @@ HoverListView::HoverListView(std::unique_ptr<HoverListModel> model)
item_container_ = item_container.get();
AddSeparatorAsChild(item_container_);
for (const auto item_tag : model_->GetItemTags()) {
for (const auto item_tag : model_->GetThrobberTags()) {
auto button = CreateHoverButtonForListItem(
item_tag, model_->GetItemIcon(item_tag), model_->GetItemText(item_tag),
model_->GetDescriptionText(item_tag), this, true, ItemType::kThrobber);
throbber_views_.push_back(button.get());
item_container_->AddChildView(button.release());
AddSeparatorAsChild(item_container_);
}
for (const auto item_tag : model_->GetButtonTags()) {
AppendListItemView(model_->GetItemIcon(item_tag),
model_->GetItemText(item_tag),
model_->GetDescriptionText(item_tag), item_tag);
......@@ -214,7 +262,7 @@ void HoverListView::CreateAndAppendPlaceholderItem() {
auto placeholder_item = CreateHoverButtonForListItem(
kPlaceHolderItemTag, model_->GetPlaceholderIcon(),
model_->GetPlaceholderText(), base::string16(), nullptr,
true /* is_placeholder_item */);
/*is_two_line_list=*/false, ItemType::kPlaceholder);
item_container_->AddChildView(placeholder_item.get());
auto* separator = AddSeparatorAsChild(item_container_);
placeholder_list_item_view_.emplace(
......@@ -314,10 +362,12 @@ int HoverListView::GetPreferredViewHeight() const {
// contain one separator and one hover button.
const auto separator_height = views::Separator().GetPreferredSize().height();
int size = separator_height;
for (auto iter = tags_to_list_item_views_.begin();
iter != tags_to_list_item_views_.end(); ++iter) {
for (const auto& iter : tags_to_list_item_views_) {
size +=
iter->second.item_view->GetPreferredSize().height() + separator_height;
iter.second.item_view->GetPreferredSize().height() + separator_height;
}
for (const auto* iter : throbber_views_) {
size += iter->GetPreferredSize().height() + separator_height;
}
int reserved_items =
model_->GetPreferredItemCount() - tags_to_list_item_views_.size();
......
......@@ -78,6 +78,7 @@ class HoverListView : public views::View,
std::unique_ptr<HoverListModel> model_;
std::map<int, ListItemViews> tags_to_list_item_views_;
std::vector<HoverButton*> throbber_views_;
base::Optional<ListItemViews> placeholder_list_item_view_;
views::ScrollView* scroll_view_;
views::View* item_container_;
......
......@@ -31,7 +31,11 @@ const gfx::VectorIcon* AccountHoverListModel::GetPlaceholderIcon() const {
return &kUserAccountAvatarIcon;
}
std::vector<int> AccountHoverListModel::GetItemTags() const {
std::vector<int> AccountHoverListModel::GetThrobberTags() const {
return {};
}
std::vector<int> AccountHoverListModel::GetButtonTags() const {
std::vector<int> tag_list(response_list_->size());
for (size_t i = 0; i < response_list_->size(); ++i)
tag_list[i] = i;
......
......@@ -34,7 +34,8 @@ class AccountHoverListModel : public HoverListModel {
bool ShouldShowPlaceholderForEmptyList() const override;
base::string16 GetPlaceholderText() const override;
const gfx::VectorIcon* GetPlaceholderIcon() const override;
std::vector<int> GetItemTags() const override;
std::vector<int> GetThrobberTags() const override;
std::vector<int> GetButtonTags() const override;
base::string16 GetItemText(int item_tag) const override;
base::string16 GetDescriptionText(int item_tag) const override;
const gfx::VectorIcon* GetItemIcon(int item_tag) const override;
......
......@@ -84,7 +84,11 @@ const gfx::VectorIcon* BleDeviceHoverListModel::GetItemIcon(
return GetTransportVectorIcon(AuthenticatorTransport::kBluetoothLowEnergy);
}
std::vector<int> BleDeviceHoverListModel::GetItemTags() const {
std::vector<int> BleDeviceHoverListModel::GetThrobberTags() const {
return {};
}
std::vector<int> BleDeviceHoverListModel::GetButtonTags() const {
std::vector<int> tag_list;
tag_list.reserve(authenticator_tags_.size());
......
......@@ -43,7 +43,8 @@ class BleDeviceHoverListModel : public HoverListModel,
base::string16 GetItemText(int item_tag) const override;
base::string16 GetDescriptionText(int item_tag) const override;
const gfx::VectorIcon* GetItemIcon(int item_tag) const override;
std::vector<int> GetItemTags() const override;
std::vector<int> GetThrobberTags() const override;
std::vector<int> GetButtonTags() const override;
void OnListItemSelected(int item_tag) override;
size_t GetPreferredItemCount() const override;
bool StyleForTwoLines() const override;
......
......@@ -42,7 +42,8 @@ class HoverListModel {
// added. This is distinct from using an empty icon as the latter will still
// take up as much space as any other icon.
virtual const gfx::VectorIcon* GetPlaceholderIcon() const = 0;
virtual std::vector<int> GetItemTags() const = 0;
virtual std::vector<int> GetThrobberTags() const = 0;
virtual std::vector<int> GetButtonTags() const = 0;
virtual base::string16 GetItemText(int item_tag) const = 0;
virtual base::string16 GetDescriptionText(int item_tag) const = 0;
// GetItemIcon may return nullptr to indicate that no icon should be added.
......
......@@ -42,7 +42,11 @@ const gfx::VectorIcon* TransportHoverListModel::GetPlaceholderIcon() const {
return &gfx::kNoneIcon;
}
std::vector<int> TransportHoverListModel::GetItemTags() const {
std::vector<int> TransportHoverListModel::GetThrobberTags() const {
return {};
}
std::vector<int> TransportHoverListModel::GetButtonTags() const {
std::vector<int> tag_list(transport_list_.size());
std::transform(
transport_list_.begin(), transport_list_.end(), tag_list.begin(),
......
......@@ -33,7 +33,8 @@ class TransportHoverListModel : public HoverListModel {
bool ShouldShowPlaceholderForEmptyList() const override;
base::string16 GetPlaceholderText() const override;
const gfx::VectorIcon* GetPlaceholderIcon() const override;
std::vector<int> GetItemTags() const override;
std::vector<int> GetThrobberTags() const override;
std::vector<int> GetButtonTags() const override;
base::string16 GetItemText(int item_tag) const override;
base::string16 GetDescriptionText(int item_tag) const override;
const gfx::VectorIcon* GetItemIcon(int item_tag) const override;
......
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