// Copyright 2022 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/system/channel_indicator/channel_indicator.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/system/channel_indicator/channel_indicator_utils.h"
#include "ash/system/tray/tray_constants.h"
#include "base/check.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/session_manager/session_manager_types.h"
#include "components/version_info/channel.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"
namespace ash {
namespace {
// Background rounded rectangle corner radius.
constexpr int kIndicatorBgCornerRadius = 50;
// Size of padding area between the border and icon or text.
constexpr int kBorderInset = 6;
// Size of the vector icon.
constexpr int kVectorIconSize = 16;
// Insets in the layout manager.
constexpr int kLayoutManagerInset = 2;
} // namespace
ChannelIndicatorView::ChannelIndicatorView(Shelf* shelf,
version_info::Channel channel)
: TrayItemView(shelf),
channel_(channel),
box_layout_(SetLayoutManager(std::make_unique<views::BoxLayout>())),
session_observer_(this) {
shell_observer_.Observe(Shell::Get());
SetVisible(false);
// Set role before calling the `Update` method to ensure
// `AccessibilityPaintChecks` pass.
GetViewAccessibility().SetRole(ax::mojom::Role::kLabelText);
Update();
}
ChannelIndicatorView::~ChannelIndicatorView() = default;
views::View* ChannelIndicatorView::GetTooltipHandlerForPoint(
const gfx::Point& point) {
return GetLocalBounds().Contains(point) ? this : nullptr;
}
std::u16string ChannelIndicatorView::GetTooltipText(const gfx::Point& p) const {
return tooltip_;
}
void ChannelIndicatorView::OnThemeChanged() {
TrayItemView::OnThemeChanged();
if (Shell::Get()->session_controller()->GetSessionState() ==
session_manager::SessionState::ACTIVE) {
// User is logged in, set image view colors.
if (image_view()) {
SetBackground(views::CreateThemedRoundedRectBackground(
channel_indicator_utils::GetBgColorJelly(channel_),
(IsHorizontalAlignment() ? GetLocalBounds().width()
: GetLocalBounds().height()) /
2.0f));
image_view()->SetImage(ui::ImageModel::FromVectorIcon(
channel_indicator_utils::GetVectorIcon(channel_),
channel_indicator_utils::GetFgColorJelly(channel_), kVectorIconSize));
}
return;
}
// User is not logged in, set label colors.
if (label()) {
label()->SetBackground(views::CreateThemedRoundedRectBackground(
channel_indicator_utils::GetBgColorJelly(channel_),
kIndicatorBgCornerRadius));
label()->SetEnabledColorId(
channel_indicator_utils::GetFgColorJelly(channel_));
}
}
void ChannelIndicatorView::HandleLocaleChange() {
Update();
}
void ChannelIndicatorView::Update() {
if (!channel_indicator_utils::IsDisplayableChannel(channel_))
return;
SetImageOrText();
SetVisible(true);
SetTooltip();
DCHECK(channel_indicator_utils::IsDisplayableChannel(channel_));
GetViewAccessibility().SetName(l10n_util::GetStringUTF16(
channel_indicator_utils::GetChannelNameStringResourceID(
channel_, /*append_channel=*/true)));
}
void ChannelIndicatorView::SetImageOrText() {
DCHECK(channel_indicator_utils::IsDisplayableChannel(channel_));
if (Shell::Get()->session_controller()->GetSessionState() ==
session_manager::SessionState::ACTIVE) {
// User is logged in, show the icon.
if (image_view())
return;
DestroyLabel();
CreateImageView();
// Parent's border insets depend on shelf horizontal alignment. Note that
// this modifies the circular background (created below), and can cause
// clipping if incorrectly positioned/sized.
SetBorder(views::CreateEmptyBorder(IsHorizontalAlignment()
? gfx::Insets::VH(kBorderInset, 0)
: gfx::Insets::VH(0, kBorderInset)));
box_layout_->set_inside_border_insets(
gfx::Insets::VH(kLayoutManagerInset, kLayoutManagerInset));
SetBackground(views::CreateThemedRoundedRectBackground(
channel_indicator_utils::GetBgColorJelly(channel_),
(IsHorizontalAlignment() ? GetLocalBounds().width()
: GetLocalBounds().height()) /
2.0f));
image_view()->SetImage(ui::ImageModel::FromVectorIcon(
channel_indicator_utils::GetVectorIcon(channel_),
channel_indicator_utils::GetFgColorJelly(channel_), kVectorIconSize));
PreferredSizeChanged();
return;
}
// User is not logged in, show the channel name.
if (label())
return;
DestroyImageView();
CreateLabel();
// Label is only displayed if the user is in a non-active `SessionState`,
// where side-shelf isn't possible (for now at least!), so nothing here is
// adjusted for shelf alignment.
DCHECK(IsHorizontalAlignment());
SetBackground(nullptr);
box_layout_->set_inside_border_insets(gfx::Insets());
SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(kBorderInset, 0)));
label()->SetBorder(
views::CreateEmptyBorder(gfx::Insets::VH(0, kBorderInset)));
label()->SetBackground(views::CreateThemedRoundedRectBackground(
channel_indicator_utils::GetBgColorJelly(channel_),
kIndicatorBgCornerRadius));
label()->SetEnabledColorId(
channel_indicator_utils::GetFgColorJelly(channel_));
label()->SetText(l10n_util::GetStringUTF16(
channel_indicator_utils::GetChannelNameStringResourceID(
channel_,
/*append_channel=*/false)));
label()->SetFontList(
gfx::FontList().DeriveWithWeight(gfx::Font::Weight::MEDIUM));
PreferredSizeChanged();
}
void ChannelIndicatorView::OnAccessibleNameChanged(
const std::u16string& new_name) {
// If icon is showing, set it on the image view.
if (image_view()) {
DCHECK(!label());
image_view()->GetViewAccessibility().SetName(new_name);
return;
}
// Otherwise set it on the label.
if (label())
label()->GetViewAccessibility().SetName(new_name);
}
void ChannelIndicatorView::SetTooltip() {
DCHECK(channel_indicator_utils::IsDisplayableChannel(channel_));
tooltip_ = l10n_util::GetStringUTF16(
channel_indicator_utils::GetChannelNameStringResourceID(
channel_, /*append_channel=*/true));
}
void ChannelIndicatorView::OnSessionStateChanged(
session_manager::SessionState state) {
Update();
}
void ChannelIndicatorView::OnShelfAlignmentChanged(
aura::Window* root_window,
ShelfAlignment old_alignment) {
if (image_view()) {
// Parent's border insets depend on shelf horizontal alignment. Note that
// this modifies the circular background, and can cause clipping if
// incorrectly positioned/sized.
SetBorder(views::CreateEmptyBorder(IsHorizontalAlignment()
? gfx::Insets::VH(kBorderInset, 0)
: gfx::Insets::VH(0, kBorderInset)));
}
}
bool ChannelIndicatorView::IsLabelVisibleForTesting() {
return label() && label()->GetVisible();
}
bool ChannelIndicatorView::IsImageViewVisibleForTesting() {
return image_view() && image_view()->GetVisible();
}
std::u16string ChannelIndicatorView::GetAccessibleNameString() const {
if (image_view())
return image_view()->GetViewAccessibility().GetCachedName();
if (label())
return label()->GetViewAccessibility().GetCachedName();
return std::u16string();
}
BEGIN_METADATA(ChannelIndicatorView)
END_METADATA
} // namespace ash