blob: 95589bced95d5e1b7961d710928d1aef0499714f [file] [log] [blame]
// Copyright 2017 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 "ash/login/ui/login_user_view.h"
#include <memory>
#include "ash/login/ui/animated_rounded_image_view.h"
#include "ash/login/ui/hover_notifier.h"
#include "ash/login/ui/image_parser.h"
#include "ash/login/ui/login_button.h"
#include "ash/login/ui/non_accessible_view.h"
#include "ash/login/ui/user_switch_flip_animation.h"
#include "ash/login/ui/views_utils.h"
#include "ash/public/cpp/ash_constants.h"
#include "ash/public/cpp/login_constants.h"
#include "ash/public/cpp/session/user_info.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "components/user_manager/user_type.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_elider.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/painter.h"
namespace ash {
namespace {
// Vertical spacing between icon, label, and authentication UI.
constexpr int kVerticalSpacingBetweenEntriesDp = 24;
// Horizontal spacing between username label and the dropdown icon.
constexpr int kDistanceBetweenUsernameAndDropdownDp = 8;
// Distance between user icon and the user label in small/extra-small layouts.
constexpr int kSmallManyDistanceFromUserIconToUserLabelDp = 16;
constexpr int kDropdownIconSizeDp = 28;
// Width/height of the user view. Ensures proper centering.
constexpr int kLargeUserViewWidthDp = 306;
constexpr int kLargeUserViewHeightDp = 346;
constexpr int kSmallUserViewWidthDp = 304;
constexpr int kExtraSmallUserViewWidthDp = 282;
// Width/height of the user image.
constexpr int kLargeUserImageSizeDp = 96;
constexpr int kSmallUserImageSizeDp = 74;
constexpr int kExtraSmallUserImageSizeDp = 60;
// Width/height of the enterprise icon circle.
constexpr int kLargeUserIconSizeDp = 26;
constexpr int kSmallUserIconSizeDp = 26;
constexpr int kExtraSmallUserIconSizeDp = 22;
// Size of the icon compared to the one of the white circle.
constexpr float kIconProportion = 0.55f;
// Opacity for when the user view is active/focused and inactive.
constexpr float kOpaqueUserViewOpacity = 1.f;
constexpr float kTransparentUserViewOpacity = 0.63f;
constexpr float kUserFadeAnimationDurationMs = 180;
constexpr char kAccountNameFontFamily[] = "Google Sans";
constexpr char kUserViewClassName[] = "UserView";
constexpr char kLoginUserImageClassName[] = "LoginUserImage";
constexpr char kLoginUserLabelClassName[] = "LoginUserLabel";
// An animation decoder which does not rescale based on the current image_scale.
class PassthroughAnimationDecoder
: public AnimatedRoundedImageView::AnimationDecoder {
public:
PassthroughAnimationDecoder(const AnimationFrames& frames)
: frames_(frames) {}
~PassthroughAnimationDecoder() override = default;
// AnimatedRoundedImageView::AnimationDecoder:
AnimationFrames Decode(float image_scale) override { return frames_; }
private:
AnimationFrames frames_;
DISALLOW_COPY_AND_ASSIGN(PassthroughAnimationDecoder);
};
class IconRoundedView : public views::View {
public:
IconRoundedView(int size) : radius_(size / 2) {
icon_ = gfx::ImageSkiaOperations::CreateResizedImage(
gfx::CreateVectorIcon(
chromeos::kEnterpriseIcon,
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorSecondary)),
skia::ImageOperations::RESIZE_BEST,
gfx::Size(size * kIconProportion, size * kIconProportion));
}
~IconRoundedView() override = default;
IconRoundedView(const IconRoundedView&) = delete;
IconRoundedView& operator=(const IconRoundedView&) = delete;
void OnPaint(gfx::Canvas* canvas) override {
View::OnPaint(canvas);
const gfx::Rect content_bounds(GetContentsBounds());
const gfx::Point center_circle(content_bounds.width() - radius_,
content_bounds.height() - radius_);
const gfx::Point left_corner_icon(
std::round(content_bounds.width() - radius_ * (1 + kIconProportion)),
std::round(content_bounds.height() - radius_ * (1 + kIconProportion)));
gfx::Rect image_bounds(left_corner_icon, icon_.size());
SkPath path;
path.addRect(gfx::RectToSkRect(image_bounds));
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorSecondaryBackground));
flags.setStyle(cc::PaintFlags::kFill_Style);
// The colored circle on which we paint the icon.
canvas->DrawCircle(center_circle, radius_, flags);
canvas->DrawImageInPath(icon_, image_bounds.x(), image_bounds.y(), path,
flags);
}
private:
gfx::ImageSkia icon_;
int radius_;
};
} // namespace
// Renders a user's profile icon.
class LoginUserView::UserImage : public NonAccessibleView {
public:
class ASH_EXPORT TestApi {
public:
explicit TestApi(LoginUserView::UserImage* view) : view_(view) {}
~TestApi() = default;
views::View* enterprise_icon() const { return view_->enterprise_icon_; }
private:
LoginUserView::UserImage* const view_;
};
UserImage(LoginDisplayStyle style)
: NonAccessibleView(kLoginUserImageClassName) {
SetLayoutManager(std::make_unique<views::FillLayout>());
const int image_size = GetImageSize(style);
image_ = new AnimatedRoundedImageView(gfx::Size(image_size, image_size),
image_size / 2);
AddChildView(image_);
const int icon_size = GetIconSize(style);
enterprise_icon_ = new IconRoundedView(icon_size);
enterprise_icon_->SetVisible(false);
AddChildView(enterprise_icon_);
}
~UserImage() override = default;
void UpdateForUser(const LoginUserInfo& user) {
// Set the initial image from |avatar| since we already have it available.
// Then, decode the bytes via blink's PNG decoder and play any animated
// frames if they are available.
if (!user.basic_user_info.avatar.image.isNull())
image_->SetImage(user.basic_user_info.avatar.image);
// Decode the avatar using blink, as blink's PNG decoder supports APNG,
// which is the format used for the animated avators.
if (!user.basic_user_info.avatar.bytes.empty()) {
DecodeAnimation(user.basic_user_info.avatar.bytes,
base::BindOnce(&LoginUserView::UserImage::OnImageDecoded,
weak_factory_.GetWeakPtr()));
}
bool is_managed =
user.user_account_manager ||
user.basic_user_info.type == user_manager::USER_TYPE_PUBLIC_ACCOUNT;
enterprise_icon_->SetVisible(is_managed);
}
void SetAnimationEnabled(bool enable) {
animation_enabled_ = enable;
image_->SetAnimationPlayback(
animation_enabled_
? AnimatedRoundedImageView::Playback::kRepeat
: AnimatedRoundedImageView::Playback::kFirstFrameOnly);
}
private:
void OnImageDecoded(AnimationFrames animation) {
// If there is only a single frame to display, show the existing avatar.
if (animation.size() <= 1) {
LOG_IF(ERROR, animation.empty()) << "Decoding user avatar failed";
return;
}
image_->SetAnimationDecoder(
std::make_unique<PassthroughAnimationDecoder>(animation),
animation_enabled_
? AnimatedRoundedImageView::Playback::kRepeat
: AnimatedRoundedImageView::Playback::kFirstFrameOnly);
}
static int GetImageSize(LoginDisplayStyle style) {
switch (style) {
case LoginDisplayStyle::kLarge:
return kLargeUserImageSizeDp;
case LoginDisplayStyle::kSmall:
return kSmallUserImageSizeDp;
case LoginDisplayStyle::kExtraSmall:
return kExtraSmallUserImageSizeDp;
}
}
static int GetIconSize(LoginDisplayStyle style) {
switch (style) {
case LoginDisplayStyle::kLarge:
return kLargeUserIconSizeDp;
case LoginDisplayStyle::kSmall:
return kSmallUserIconSizeDp;
case LoginDisplayStyle::kExtraSmall:
return kExtraSmallUserIconSizeDp;
}
}
AnimatedRoundedImageView* image_ = nullptr;
IconRoundedView* enterprise_icon_ = nullptr;
bool animation_enabled_ = false;
base::WeakPtrFactory<UserImage> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(UserImage);
};
// Shows the user's name.
class LoginUserView::UserLabel : public NonAccessibleView {
public:
UserLabel(LoginDisplayStyle style, int label_width)
: NonAccessibleView(kLoginUserLabelClassName), label_width_(label_width) {
SetLayoutManager(std::make_unique<views::FillLayout>());
user_name_ = new views::Label();
user_name_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
user_name_->SetSubpixelRenderingEnabled(false);
user_name_->SetAutoColorReadabilityEnabled(false);
const gfx::FontList& base_font_list = views::Label::GetDefaultFontList();
const gfx::FontList font_list(
{kAccountNameFontFamily}, base_font_list.GetFontStyle(),
base_font_list.GetFontSize(), base_font_list.GetFontWeight());
switch (style) {
case LoginDisplayStyle::kLarge:
user_name_->SetFontList(font_list.Derive(
12, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
break;
case LoginDisplayStyle::kSmall:
user_name_->SetFontList(font_list.Derive(
8, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
break;
case LoginDisplayStyle::kExtraSmall:
// TODO(jdufault): match font against spec.
user_name_->SetFontList(font_list.Derive(
6, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
break;
}
AddChildView(user_name_);
}
~UserLabel() override = default;
void UpdateForUser(const LoginUserInfo& user) {
std::string display_name = user.basic_user_info.display_name;
// display_name can be empty in debug builds with stub users.
if (display_name.empty())
display_name = user.basic_user_info.display_email;
user_name_->SetText(gfx::ElideText(base::UTF8ToUTF16(display_name),
user_name_->font_list(), label_width_,
gfx::ElideBehavior::ELIDE_TAIL));
}
const base::string16& displayed_name() const { return user_name_->GetText(); }
private:
views::Label* user_name_ = nullptr;
const int label_width_;
DISALLOW_COPY_AND_ASSIGN(UserLabel);
};
// A button embedded inside of LoginUserView, which is activated whenever the
// user taps anywhere in the LoginUserView. Previously, LoginUserView was a
// views::Button, but this breaks ChromeVox as it does not expect buttons to
// have any children (ie, the dropdown button).
class LoginUserView::TapButton : public views::Button {
public:
TapButton(PressedCallback callback, LoginUserView* parent)
: views::Button(std::move(callback)), parent_(parent) {}
~TapButton() override = default;
// views::Button:
void OnFocus() override {
views::Button::OnFocus();
parent_->UpdateOpacity();
}
void OnBlur() override {
views::Button::OnBlur();
parent_->UpdateOpacity();
}
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
// TODO(https://crbug.com/1065516): Define the button name.
node_data->SetNameExplicitlyEmpty();
Button::GetAccessibleNodeData(node_data);
}
private:
LoginUserView* const parent_;
DISALLOW_COPY_AND_ASSIGN(TapButton);
};
// LoginUserView is defined after LoginUserView::UserLabel so it can access the
// class members.
LoginUserView::TestApi::TestApi(LoginUserView* view) : view_(view) {}
LoginUserView::TestApi::~TestApi() = default;
LoginDisplayStyle LoginUserView::TestApi::display_style() const {
return view_->display_style_;
}
const base::string16& LoginUserView::TestApi::displayed_name() const {
return view_->user_label_->displayed_name();
}
views::View* LoginUserView::TestApi::user_label() const {
return view_->user_label_;
}
views::View* LoginUserView::TestApi::tap_button() const {
return view_->tap_button_;
}
views::View* LoginUserView::TestApi::dropdown() const {
return view_->dropdown_;
}
LoginUserMenuView* LoginUserView::TestApi::menu() const {
return view_->menu_;
}
views::View* LoginUserView::TestApi::enterprise_icon() const {
return LoginUserView::UserImage::TestApi(view_->user_image_)
.enterprise_icon();
}
void LoginUserView::TestApi::OnTap() const {
view_->on_tap_.Run();
}
bool LoginUserView::TestApi::is_opaque() const {
return view_->is_opaque_;
}
// static
int LoginUserView::WidthForLayoutStyle(LoginDisplayStyle style) {
switch (style) {
case LoginDisplayStyle::kLarge:
return kLargeUserViewWidthDp;
case LoginDisplayStyle::kSmall:
return kSmallUserViewWidthDp;
case LoginDisplayStyle::kExtraSmall:
return kExtraSmallUserViewWidthDp;
}
}
LoginUserView::LoginUserView(
LoginDisplayStyle style,
bool show_dropdown,
const OnTap& on_tap,
const OnRemoveWarningShown& on_remove_warning_shown,
const OnRemove& on_remove)
: on_tap_(on_tap),
on_remove_warning_shown_(on_remove_warning_shown),
on_remove_(on_remove),
display_style_(style) {
// show_dropdown can only be true when the user view is rendering in large
// mode.
DCHECK(!show_dropdown || style == LoginDisplayStyle::kLarge);
// |on_remove_warning_shown| and |on_remove| is only available iff
// |show_dropdown| is true.
DCHECK(show_dropdown == !!on_remove_warning_shown);
DCHECK(show_dropdown == !!on_remove);
user_image_ = new UserImage(style);
int label_width =
WidthForLayoutStyle(style) -
2 * (kDistanceBetweenUsernameAndDropdownDp + kDropdownIconSizeDp);
user_label_ = new UserLabel(style, label_width);
if (show_dropdown) {
dropdown_ = new LoginButton(base::BindRepeating(
&LoginUserView::DropdownButtonPressed, base::Unretained(this)));
dropdown_->SetHasInkDropActionOnClick(false);
dropdown_->SetPreferredSize(
gfx::Size(kDropdownIconSizeDp, kDropdownIconSizeDp));
dropdown_->SetImage(
views::Button::STATE_NORMAL,
gfx::CreateVectorIcon(
kLockScreenDropdownIcon,
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary)));
dropdown_->SetFocusBehavior(FocusBehavior::ALWAYS);
}
tap_button_ = new TapButton(on_tap_, this);
SetTapEnabled(true);
switch (style) {
case LoginDisplayStyle::kLarge:
SetLargeLayout();
break;
case LoginDisplayStyle::kSmall:
case LoginDisplayStyle::kExtraSmall:
SetSmallishLayout();
break;
}
// Layer rendering is needed for animation. We apply animations to child views
// separately to reduce overdraw.
auto setup_layer = [](views::View* view) {
view->SetPaintToLayer();
view->layer()->SetFillsBoundsOpaquely(false);
view->layer()->SetOpacity(kTransparentUserViewOpacity);
view->layer()->GetAnimator()->set_preemption_strategy(
ui::LayerAnimator::PreemptionStrategy::
IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
};
setup_layer(user_image_);
setup_layer(user_label_);
if (dropdown_)
setup_layer(dropdown_);
hover_notifier_ = std::make_unique<HoverNotifier>(
this,
base::BindRepeating(&LoginUserView::OnHover, base::Unretained(this)));
if (ash::Shell::HasInstance())
display_observation_.Observe(ash::Shell::Get()->display_configurator());
}
LoginUserView::~LoginUserView() = default;
void LoginUserView::UpdateForUser(const LoginUserInfo& user, bool animate) {
current_user_ = user;
if (menu_ && menu_->parent()) {
menu_->parent()->RemoveChildView(menu_);
delete menu_;
}
menu_ = new LoginUserMenuView(current_user_, dropdown_ /*anchor_view*/,
dropdown_ /*bubble_opener*/,
on_remove_warning_shown_, on_remove_);
menu_->SetVisible(false);
if (animate) {
// Stop any existing animation.
user_image_->layer()->GetAnimator()->StopAnimating();
// Create the image flip animation.
auto image_transition = std::make_unique<UserSwitchFlipAnimation>(
user_image_->width(), 0 /*start_degrees*/, 90 /*midpoint_degrees*/,
180 /*end_degrees*/,
base::TimeDelta::FromMilliseconds(
login_constants::kChangeUserAnimationDurationMs),
gfx::Tween::Type::EASE_OUT,
base::BindOnce(&LoginUserView::UpdateCurrentUserState,
base::Unretained(this)));
auto* image_sequence =
new ui::LayerAnimationSequence(std::move(image_transition));
user_image_->layer()->GetAnimator()->StartAnimation(image_sequence);
// Create opacity fade animation, which applies to the entire element.
bool is_opaque = this->is_opaque_;
auto make_opacity_sequence = [is_opaque]() {
auto make_opacity_element = [](float target_opacity) {
auto element = ui::LayerAnimationElement::CreateOpacityElement(
target_opacity,
base::TimeDelta::FromMilliseconds(
login_constants::kChangeUserAnimationDurationMs / 2.0f));
element->set_tween_type(gfx::Tween::Type::EASE_OUT);
return element;
};
auto* opacity_sequence = new ui::LayerAnimationSequence();
opacity_sequence->AddElement(make_opacity_element(0 /*target_opacity*/));
opacity_sequence->AddElement(make_opacity_element(
is_opaque ? kOpaqueUserViewOpacity
: kTransparentUserViewOpacity /*target_opacity*/));
return opacity_sequence;
};
user_image_->layer()->GetAnimator()->StartAnimation(
make_opacity_sequence());
user_label_->layer()->GetAnimator()->StartAnimation(
make_opacity_sequence());
if (dropdown_) {
dropdown_->layer()->GetAnimator()->StartAnimation(
make_opacity_sequence());
}
} else {
// Do not animate, so directly update to the current user.
UpdateCurrentUserState();
}
}
void LoginUserView::SetForceOpaque(bool force_opaque) {
force_opaque_ = force_opaque;
UpdateOpacity();
}
void LoginUserView::SetTapEnabled(bool enabled) {
tap_button_->SetFocusBehavior(enabled ? FocusBehavior::ALWAYS
: FocusBehavior::NEVER);
}
void LoginUserView::OnPowerStateChanged(
chromeos::DisplayPowerState power_state) {
bool is_display_on = power_state != chromeos::DISPLAY_POWER_ALL_OFF;
user_image_->SetAnimationEnabled(is_display_on && is_opaque_);
}
const char* LoginUserView::GetClassName() const {
return kUserViewClassName;
}
gfx::Size LoginUserView::CalculatePreferredSize() const {
switch (display_style_) {
case LoginDisplayStyle::kLarge:
return gfx::Size(kLargeUserViewWidthDp, kLargeUserViewHeightDp);
case LoginDisplayStyle::kSmall:
return gfx::Size(kSmallUserViewWidthDp, kSmallUserImageSizeDp);
case LoginDisplayStyle::kExtraSmall:
return gfx::Size(kExtraSmallUserViewWidthDp, kExtraSmallUserImageSizeDp);
}
}
void LoginUserView::Layout() {
views::View::Layout();
tap_button_->SetBoundsRect(GetLocalBounds());
}
void LoginUserView::RequestFocus() {
tap_button_->RequestFocus();
}
void LoginUserView::OnHover(bool has_hover) {
UpdateOpacity();
}
void LoginUserView::DropdownButtonPressed() {
DCHECK(dropdown_);
DCHECK(menu_);
// If menu is showing, just close it
if (menu_->GetVisible()) {
menu_->Hide();
return;
}
bool opener_focused =
menu_->GetBubbleOpener() && menu_->GetBubbleOpener()->HasFocus();
if (!menu_->parent())
login_views_utils::GetBubbleContainer(this)->AddChildView(menu_);
// Reset state in case the remove-user button was clicked once previously.
menu_->ResetState();
menu_->Show();
// If the menu was opened by pressing Enter on the focused dropdown, focus
// should automatically go to the remove-user button (for keyboard
// accessibility).
if (opener_focused)
menu_->RequestFocus();
}
void LoginUserView::UpdateCurrentUserState() {
base::string16 accessible_name;
auto email = base::UTF8ToUTF16(current_user_.basic_user_info.display_email);
if (current_user_.user_account_manager) {
accessible_name = l10n_util::GetStringFUTF16(
IDS_ASH_LOGIN_POD_MANAGED_ACCESSIBLE_NAME, email);
} else if (current_user_.basic_user_info.type ==
user_manager::USER_TYPE_PUBLIC_ACCOUNT) {
accessible_name = l10n_util::GetStringFUTF16(
IDS_ASH_LOGIN_POD_MANAGED_ACCESSIBLE_NAME,
base::UTF8ToUTF16(current_user_.basic_user_info.display_name));
} else {
accessible_name = email;
}
tap_button_->SetAccessibleName(accessible_name);
if (dropdown_) {
// The accessible name for the dropdown depends on whether it also contains
// the remove user button for the user in question.
accessible_name = l10n_util::GetStringFUTF16(
current_user_.can_remove
? IDS_ASH_LOGIN_POD_REMOVE_ACCOUNT_DIALOG_BUTTON_ACCESSIBLE_NAME
: IDS_ASH_LOGIN_POD_ACCOUNT_DIALOG_BUTTON_ACCESSIBLE_NAME,
email);
dropdown_->SetAccessibleName(accessible_name);
}
user_image_->UpdateForUser(current_user_);
user_label_->UpdateForUser(current_user_);
Layout();
}
void LoginUserView::UpdateOpacity() {
bool was_opaque = is_opaque_;
is_opaque_ =
force_opaque_ || tap_button_->IsMouseHovered() || tap_button_->HasFocus();
if (was_opaque == is_opaque_)
return;
// Animate to new opacity.
auto build_settings = [](views::View* view)
-> std::unique_ptr<ui::ScopedLayerAnimationSettings> {
auto settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
view->layer()->GetAnimator());
settings->SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kUserFadeAnimationDurationMs));
settings->SetTweenType(gfx::Tween::Type::EASE_IN_OUT);
return settings;
};
std::unique_ptr<ui::ScopedLayerAnimationSettings> user_image_settings =
build_settings(user_image_);
std::unique_ptr<ui::ScopedLayerAnimationSettings> user_label_settings =
build_settings(user_label_);
float target_opacity =
is_opaque_ ? kOpaqueUserViewOpacity : kTransparentUserViewOpacity;
user_image_->layer()->SetOpacity(target_opacity);
user_label_->layer()->SetOpacity(target_opacity);
if (dropdown_) {
std::unique_ptr<ui::ScopedLayerAnimationSettings> dropdown_settings =
build_settings(dropdown_);
dropdown_->layer()->SetOpacity(target_opacity);
}
// Animate avatar only if we are opaque.
user_image_->SetAnimationEnabled(is_opaque_);
}
void LoginUserView::SetLargeLayout() {
// Add views in tabbing order; they are rendered in a different order below.
AddChildView(user_image_);
AddChildView(user_label_);
AddChildView(tap_button_);
if (dropdown_)
AddChildView(dropdown_);
// Use views::GridLayout instead of views::BoxLayout because views::BoxLayout
// lays out children according to the view->children order.
views::GridLayout* layout =
SetLayoutManager(std::make_unique<views::GridLayout>());
constexpr int kImageColumnId = 0;
constexpr int kLabelDropdownColumnId = 1;
constexpr int kLabelDomainColumnId = 2;
{
views::ColumnSet* image = layout->AddColumnSet(kImageColumnId);
image->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
1 /*resize_percent*/,
views::GridLayout::ColumnSize::kUsePreferred,
0 /*fixed_width*/, 0 /*min_width*/);
}
{
views::ColumnSet* label_dropdown =
layout->AddColumnSet(kLabelDropdownColumnId);
label_dropdown->AddPaddingColumn(1.0f /*resize_percent*/, 0 /*width*/);
if (dropdown_) {
label_dropdown->AddPaddingColumn(
0 /*resize_percent*/, dropdown_->GetPreferredSize().width() +
kDistanceBetweenUsernameAndDropdownDp);
}
label_dropdown->AddColumn(views::GridLayout::CENTER,
views::GridLayout::CENTER, 0 /*resize_percent*/,
views::GridLayout::ColumnSize::kUsePreferred,
0 /*fixed_width*/, 0 /*min_width*/);
if (dropdown_) {
label_dropdown->AddPaddingColumn(0 /*resize_percent*/,
kDistanceBetweenUsernameAndDropdownDp);
label_dropdown->AddColumn(views::GridLayout::CENTER,
views::GridLayout::CENTER, 0 /*resize_percent*/,
views::GridLayout::ColumnSize::kUsePreferred,
0 /*fixed_width*/, 0 /*min_width*/);
}
label_dropdown->AddPaddingColumn(1.0f /*resize_percent*/, 0 /*width*/);
}
{
views::ColumnSet* label_domain = layout->AddColumnSet(kLabelDomainColumnId);
label_domain->AddColumn(views::GridLayout::CENTER,
views::GridLayout::CENTER, 1 /*resize_percent*/,
views::GridLayout::ColumnSize::kUsePreferred,
0 /*fixed_width*/, 0 /*min_width*/);
}
auto add_padding = [&](int amount) {
layout->AddPaddingRow(0 /*vertical_resize*/, amount /*size*/);
};
// Add views in rendering order.
// Image
layout->StartRow(0 /*vertical_resize*/, kImageColumnId);
layout->AddExistingView(user_image_);
add_padding(kVerticalSpacingBetweenEntriesDp);
// Label/dropdown.
layout->StartRow(0 /*vertical_resize*/, kLabelDropdownColumnId);
layout->AddExistingView(user_label_);
if (dropdown_)
layout->AddExistingView(dropdown_);
}
void LoginUserView::SetSmallishLayout() {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
kSmallManyDistanceFromUserIconToUserLabelDp));
AddChildView(user_image_);
AddChildView(user_label_);
AddChildView(tap_button_);
}
} // namespace ash