blob: 34f081f162abb96fa3aedb83f2199dd3823346a7 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/login/ui/scrollable_users_list_view.h"
#include <memory>
#include <optional>
#include "ash/controls/rounded_scroll_bar.h"
#include "ash/login/ui/login_constants.h"
#include "ash/login/ui/login_display_style.h"
#include "ash/login/ui/login_user_view.h"
#include "ash/login/ui/non_accessible_view.h"
#include "ash/login/ui/views_utils.h"
#include "ash/shell.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/ash_color_provider.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/paint_shader.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
// Vertical padding between user rows in the small display style.
constexpr int kSmallVerticalDistanceBetweenUsersDp = 53;
// Padding around user list.
constexpr int kSmallPaddingLeftRightOfUserListDp = 45;
constexpr int kSmallPaddingTopBottomOfUserListDp = 60;
constexpr int kExtraSmallPaddingAroundUserListLandscapeDp = 72;
constexpr int kExtraSmallPaddingLeftOfUserListPortraitDp = 46;
constexpr int kExtraSmallPaddingRightOfUserListPortraitDp = 12;
constexpr int kExtraSmallPaddingTopBottomOfUserListPortraitDp = 66;
// Vertical padding between user rows in extra small display style.
constexpr int kExtraSmallVerticalDistanceBetweenUsersDp = 32;
// Height of gradient shown at the top/bottom of the user list in the extra
// small display style.
constexpr int kExtraSmallGradientHeightDp = 112;
// Inset the scroll bar from the edges of the screen.
constexpr auto kVerticalScrollInsets = gfx::Insets::TLBR(2, 0, 2, 8);
constexpr char kScrollableUsersListContentViewName[] =
"ScrollableUsersListContent";
// A view that is at least as tall as its parent.
class EnsureMinHeightView : public NonAccessibleView {
public:
EnsureMinHeightView()
: NonAccessibleView(kScrollableUsersListContentViewName) {}
EnsureMinHeightView(const EnsureMinHeightView&) = delete;
EnsureMinHeightView& operator=(const EnsureMinHeightView&) = delete;
~EnsureMinHeightView() override = default;
// NonAccessibleView:
void Layout(PassKey) override {
// Make sure our height is at least as tall as the parent, so the layout
// manager will center us properly.
int min_height = parent()->height();
if (size().height() < min_height) {
gfx::Size new_size = size();
new_size.set_height(min_height);
SetSize(new_size);
}
LayoutSuperclass<NonAccessibleView>(this);
}
};
struct LayoutParams {
// Spacing between user entries on users list.
int between_child_spacing;
// Insets around users list used in landscape orientation.
gfx::Insets insets_landscape;
// Insets around users list used in portrait orientation.
gfx::Insets insets_portrait;
};
// static
LayoutParams BuildLayoutForStyle(LoginDisplayStyle style) {
switch (style) {
case LoginDisplayStyle::kExtraSmall: {
LayoutParams params;
params.between_child_spacing = kExtraSmallVerticalDistanceBetweenUsersDp;
params.insets_landscape =
gfx::Insets(kExtraSmallPaddingAroundUserListLandscapeDp);
params.insets_portrait =
gfx::Insets::TLBR(kExtraSmallPaddingTopBottomOfUserListPortraitDp,
kExtraSmallPaddingLeftOfUserListPortraitDp,
kExtraSmallPaddingTopBottomOfUserListPortraitDp,
kExtraSmallPaddingRightOfUserListPortraitDp);
return params;
}
case LoginDisplayStyle::kSmall: {
LayoutParams params;
params.insets_landscape =
gfx::Insets::VH(kSmallPaddingTopBottomOfUserListDp,
kSmallPaddingLeftRightOfUserListDp);
params.insets_portrait =
gfx::Insets::VH(kSmallPaddingTopBottomOfUserListDp,
kSmallPaddingLeftRightOfUserListDp);
params.between_child_spacing = kSmallVerticalDistanceBetweenUsersDp;
return params;
}
default: {
NOTREACHED();
return LayoutParams();
}
}
}
} // namespace
// static
ScrollableUsersListView::GradientParams
ScrollableUsersListView::GradientParams::BuildForStyle(LoginDisplayStyle style,
views::View* view) {
switch (style) {
case LoginDisplayStyle::kExtraSmall: {
SkColor dark_muted_color = view->GetColorProvider()->GetColor(
kColorAshLoginScrollableUserListBackground);
ui::ColorId tint_color_id =
chromeos::features::IsJellyEnabled()
? static_cast<ui::ColorId>(cros_tokens::kCrosSysScrim2)
: kColorAshShieldAndBase80;
SkColor tint_color = color_utils::GetResultingPaintColor(
view->GetColorProvider()->GetColor(tint_color_id),
SkColorSetA(dark_muted_color, SK_AlphaOPAQUE));
GradientParams params;
params.color_from = dark_muted_color;
params.color_to = tint_color;
params.height = kExtraSmallGradientHeightDp;
return params;
}
case LoginDisplayStyle::kSmall: {
GradientParams params;
params.height = 0.f;
return params;
}
default: {
NOTREACHED();
return GradientParams();
}
}
}
ScrollableUsersListView::TestApi::TestApi(ScrollableUsersListView* view)
: view_(view) {}
ScrollableUsersListView::TestApi::~TestApi() = default;
const std::vector<raw_ptr<LoginUserView, VectorExperimental>>&
ScrollableUsersListView::TestApi::user_views() const {
return view_->user_views_;
}
ScrollableUsersListView::ScrollableUsersListView(
const std::vector<LoginUserInfo>& users,
const ActionWithUser& on_tap_user,
LoginDisplayStyle display_style)
: display_style_(display_style) {
auto layout_params = BuildLayoutForStyle(display_style);
user_view_host_ = new NonAccessibleView();
user_view_host_layout_ =
user_view_host_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
layout_params.between_child_spacing));
user_view_host_layout_->set_minimum_cross_axis_size(
LoginUserView::WidthForLayoutStyle(display_style));
user_view_host_layout_->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kCenter);
user_view_host_layout_->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
for (std::size_t i = 1u; i < users.size(); ++i) {
auto* view = new LoginUserView(display_style, false /*show_dropdown*/,
base::BindRepeating(on_tap_user, i - 1),
base::RepeatingClosure());
user_views_.push_back(view);
view->UpdateForUser(users[i], false /*animate*/);
user_view_host_->AddChildView(view);
}
// |user_view_host_| is the same size as the user views, which may be shorter
// than or taller than the display height. We need the exact height of all
// user views to render a background if the wallpaper is not blurred.
//
// |user_view_host_| is a child of |ensure_min_height|, which has a layout
// manager which will ensure |user_view_host_| is vertically centered if
// |user_view_host_| is shorter than the display height.
//
// |user_view_host_| cannot be set as |contents()| directly because it needs
// to be vertically centered when non-scrollable.
auto ensure_min_height = std::make_unique<EnsureMinHeightView>();
ensure_min_height
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical))
->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
ensure_min_height->AddChildView(user_view_host_.get());
SetContents(std::move(ensure_min_height));
SetBackgroundColor(std::nullopt);
SetDrawOverflowIndicator(false);
auto vertical_scroll = std::make_unique<RoundedScrollBar>(
views::ScrollBar::Orientation::kVertical);
vertical_scroll->SetInsets(kVerticalScrollInsets);
SetVerticalScrollBar(std::move(vertical_scroll));
SetHorizontalScrollBar(std::make_unique<RoundedScrollBar>(
views::ScrollBar::Orientation::kHorizontal));
observation_.Observe(Shell::Get()->wallpaper_controller());
}
ScrollableUsersListView::~ScrollableUsersListView() = default;
LoginUserView* ScrollableUsersListView::GetUserView(
const AccountId& account_id) {
for (ash::LoginUserView* view : user_views_) {
if (view->current_user().basic_user_info.account_id == account_id) {
return view;
}
}
return nullptr;
}
void ScrollableUsersListView::UpdateUserViewHostLayoutInsets() {
DCHECK(GetWidget());
bool should_show_landscape =
login_views_utils::ShouldShowLandscape(GetWidget());
LayoutParams layout_params = BuildLayoutForStyle(display_style_);
user_view_host_layout_->set_inside_border_insets(
should_show_landscape ? layout_params.insets_landscape
: layout_params.insets_portrait);
}
void ScrollableUsersListView::Layout(PassKey) {
DCHECK(user_view_host_layout_);
// Update clipping height.
if (parent()) {
int parent_height = parent()->size().height();
ClipHeightTo(parent_height, parent_height);
if (height() != parent_height) {
PreferredSizeChanged();
}
}
UpdateUserViewHostLayoutInsets();
// Layout everything.
LayoutSuperclass<ScrollView>(this);
}
void ScrollableUsersListView::OnPaintBackground(gfx::Canvas* canvas) {
// Find the bounds of the actual contents.
gfx::RectF render_bounds(user_view_host_->GetLocalBounds());
views::View::ConvertRectToTarget(user_view_host_, this, &render_bounds);
// In extra-small, the render bounds height always match the display height.
if (display_style_ == LoginDisplayStyle::kExtraSmall) {
render_bounds.set_y(0);
render_bounds.set_height(height());
}
// Only draw a gradient if the wallpaper is blurred. Otherwise, draw a rounded
// rectangle.
if (Shell::Get()->wallpaper_controller()->IsWallpaperBlurredForLockState()) {
cc::PaintFlags flags;
// Only draw a gradient if the content can be scrolled.
if (vertical_scroll_bar()->GetVisible()) {
// Draws symmetrical linear gradient at the top and bottom of the view.
SkScalar view_height = render_bounds.height();
SkScalar gradient_height = gradient_params_.height;
if (gradient_height == 0) {
gradient_height = view_height;
}
// Start and end point of the drawing in view space.
SkPoint in_view_coordinates[2] = {SkPoint(),
SkPoint::Make(0.f, view_height)};
// Positions of colors to create gradient define in 0 to 1 range.
SkScalar top_gradient_end = gradient_height / view_height;
SkScalar bottom_gradient_start = 1.f - top_gradient_end;
SkScalar color_positions[4] = {0.f, top_gradient_end,
bottom_gradient_start, 1.f};
SkColor4f colors[4] = {SkColor4f::FromColor(gradient_params_.color_from),
SkColor4f::FromColor(gradient_params_.color_to),
SkColor4f::FromColor(gradient_params_.color_to),
SkColor4f::FromColor(gradient_params_.color_from)};
flags.setShader(cc::PaintShader::MakeLinearGradient(
in_view_coordinates, colors, color_positions, 4, SkTileMode::kClamp));
} else {
flags.setColor(gradient_params_.color_to);
}
flags.setStyle(cc::PaintFlags::kFill_Style);
canvas->DrawRect(render_bounds, flags);
} else {
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
ui::ColorId background_color_id =
chromeos::features::IsJellyEnabled()
? static_cast<ui::ColorId>(cros_tokens::kCrosSysScrim2)
: kColorAshShieldAndBase80;
flags.setColor(GetColorProvider()->GetColor(background_color_id));
canvas->DrawRoundRect(render_bounds,
login::kNonBlurredWallpaperBackgroundRadiusDp, flags);
}
}
void ScrollableUsersListView::OnThemeChanged() {
views::ScrollView::OnThemeChanged();
gradient_params_ = GradientParams::BuildForStyle(display_style_, this);
}
// When the active user is updated, the wallpaper changes. The gradient color
// should be updated in response to the new primary wallpaper color.
void ScrollableUsersListView::OnWallpaperColorsChanged() {
gradient_params_ = GradientParams::BuildForStyle(display_style_, this);
SchedulePaint();
}
void ScrollableUsersListView::OnWallpaperBlurChanged() {
gradient_params_ = GradientParams::BuildForStyle(display_style_, this);
SchedulePaint();
}
BEGIN_METADATA(ScrollableUsersListView)
END_METADATA
} // namespace ash