// Copyright 2024 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/game_dashboard/game_dashboard_welcome_dialog.h"
#include "ash/bubble/bubble_utils.h"
#include "ash/game_dashboard/game_dashboard_constants.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/system_shadow.h"
#include "ash/style/typography.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/events/ash/keyboard_capability.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/controls/label.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout_view.h"
namespace ash {
namespace {
// Corner radius of the welcome dialog.
constexpr float kDialogCornerRadius = 24.0f;
// Fixed width of the welcome dialog.
constexpr int kDialogWidth = 360;
// Radius of the icon and its background displayed in the dialog.
constexpr float kIconBackgroundRadius = 40.0f;
// The height and width of the dialog's icon.
constexpr int kIconSize = 20;
// Additional padding for the top, left, and right title container border.
constexpr int kPrimaryContainerBorder = 12;
// Border padding surrounding the inside of the entire welcome dialog.
constexpr int kPrimaryLayoutInsideBorder = 8;
// Padding between the `primary_container` and `shortcut_hint` rows.
constexpr int kRowPadding = 20;
// Padding between the `title_container` and `icon_container`.
constexpr int kTitleContainerPadding = 20;
// Width of the container containing the text title and sub-label.
constexpr int kTitleTextMaxWidth =
kDialogWidth - kIconBackgroundRadius - kTitleContainerPadding -
/*left and right dialog insets*/ 2 * kPrimaryLayoutInsideBorder -
/*additional `primary_container` left and right padding*/
2 * kPrimaryContainerBorder;
// Maximum duration that the dialog should be displayed.
constexpr base::TimeDelta kDialogDuration = base::Seconds(4);
} // namespace
GameDashboardWelcomeDialog::GameDashboardWelcomeDialog() {
SetOrientation(views::LayoutOrientation::kVertical);
SetIgnoreDefaultMainAxisMargins(true);
SetDefault(views::kMarginsKey, gfx::Insets::TLBR(kRowPadding, 0, 0, 0));
SetInteriorMargin(
gfx::Insets::VH(kPrimaryLayoutInsideBorder, kPrimaryLayoutInsideBorder));
SetBackground(views::CreateThemedRoundedRectBackground(
cros_tokens::kCrosSysSystemBaseElevatedOpaque, kDialogCornerRadius));
SetBorder(views::CreateThemedRoundedRectBorder(
game_dashboard::kWelcomeDialogBorderThickness, kDialogCornerRadius,
ui::ColorIds::kColorHighlightBorderHighlight1));
shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForView(
this, SystemShadow::Type::kElevation12);
shadow_->SetRoundedCornerRadius(kDialogCornerRadius);
GetViewAccessibility().SetProperties(
ax::mojom::Role::kDialog,
l10n_util::GetStringUTF16(
IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_A11Y_LABEL));
AddTitleAndIconRow();
AddShortcutInfoRow();
}
GameDashboardWelcomeDialog::~GameDashboardWelcomeDialog() = default;
void GameDashboardWelcomeDialog::StartTimer(base::OnceClosure on_complete) {
DCHECK(on_complete) << "OnceClosure must be passed to determine what to do "
"when the timer completes.";
timer_.Start(FROM_HERE, kDialogDuration, std::move(on_complete));
}
void GameDashboardWelcomeDialog::AnnounceForAccessibility() {
GetViewAccessibility().AnnounceAlert(l10n_util::GetStringFUTF16(
IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_A11Y_ANNOUNCEMENT,
l10n_util::GetStringUTF16(
Shell::Get()->keyboard_capability()->HasLauncherButtonOnAnyKeyboard()
? IDS_ASH_SHORTCUT_MODIFIER_LAUNCHER
: IDS_ASH_SHORTCUT_MODIFIER_SEARCH)));
}
// Creates a primary container that holds separate sub-containers for the text
// and icon.
// Note: When using `views::FlexLayoutView` it's common to wrap objects in
// additional containers that need a separate alignment than the rest of the
// elements. This creates the following:
//
// +----------------------------------------------------+
// | primary_container |
// | +--------------------------+-------------------+ |
// | | title_container | icon_container | |
// | | +--------------------+ | +--------+ | |
// | | | title | | | | | |
// | | +--------------------+ | | icon | | |
// | | | sub_label | | | | | |
// | | +--------------------+ | +--------+ | |
// | +--------------------------+-------------------+ |
// +----------------------------------------------------+
void GameDashboardWelcomeDialog::AddTitleAndIconRow() {
auto* primary_container =
AddChildView(std::make_unique<views::FlexLayoutView>());
primary_container->SetIgnoreDefaultMainAxisMargins(true);
primary_container->SetDefault(views::kMarginsKey, gfx::Insets::VH(0, 0));
primary_container->SetInteriorMargin(
gfx::Insets::TLBR(kPrimaryContainerBorder, kPrimaryContainerBorder, 0,
kPrimaryContainerBorder));
primary_container->SetOrientation(views::LayoutOrientation::kHorizontal);
// Create title container as a child of the primary container.
auto* title_container = primary_container->AddChildView(
std::make_unique<views::FlexLayoutView>());
title_container->SetOrientation(views::LayoutOrientation::kVertical);
title_container->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
title_container->SetCrossAxisAlignment(views::LayoutAlignment::kStart);
title_container->SetInteriorMargin(
gfx::Insets::TLBR(0, 0, 0, kTitleContainerPadding));
// Add title label to the title container.
auto* title = title_container->AddChildView(bubble_utils::CreateLabel(
TypographyToken::kCrosButton1,
l10n_util::GetStringUTF16(
IDS_ASH_GAME_DASHBOARD_GAME_DASHBOARD_BUTTON_TITLE),
cros_tokens::kCrosSysOnSurface));
title->SetMultiLine(true);
title->SizeToFit(kTitleTextMaxWidth);
title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
// Add sub-label to the title container.
auto* sub_label = title_container->AddChildView(bubble_utils::CreateLabel(
TypographyToken::kCrosAnnotation2,
l10n_util::GetStringUTF16(
IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SUB_LABEL),
cros_tokens::kCrosSysOnSurfaceVariant));
// TODO(b/316138331): Investigate why multi-line support isn't working
// properly.
sub_label->SetMultiLine(true);
sub_label->SizeToFit(kTitleTextMaxWidth);
sub_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
// Create icon container as a child of the primary container.
auto* icon_container = primary_container->AddChildView(
std::make_unique<views::FlexLayoutView>());
icon_container->SetCrossAxisAlignment(views::LayoutAlignment::kEnd);
// Add icon to the icon container.
auto* icon = icon_container->AddChildView(
std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
chromeos::kGameDashboardGamepadIcon, cros_tokens::kCrosSysOnPrimary,
kIconSize)));
icon->SetPreferredSize(
gfx::Size(kIconBackgroundRadius, kIconBackgroundRadius));
icon->SetBackground(views::CreateThemedRoundedRectBackground(
cros_tokens::kCrosSysPrimary, kIconBackgroundRadius));
}
// Creates a stylized label that holds the hint indicating how open the Game
// Dashboard shortcut. This creates the following:
//
// +----------------------------------------------------+
// | |"Press" | |Launcher icon| |"+ g at anytime"| |
// +----------------------------------------------------+
void GameDashboardWelcomeDialog::AddShortcutInfoRow() {
// Styled label to include the inline icon.
auto* styled_label = AddChildView(std::make_unique<views::StyledLabel>());
size_t inline_icon_offset;
styled_label->SetText(
l10n_util::GetStringFUTF16(IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SHORTCUT,
u"", &inline_icon_offset));
styled_label->SetBackground(views::CreateThemedRoundedRectBackground(
cros_tokens::kCrosSysSystemOnBase, /*radius=*/16.0f));
styled_label->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(14, 18)));
const bool has_launcher_keyboard_button =
Shell::Get()->keyboard_capability()->HasLauncherButtonOnAnyKeyboard();
styled_label->GetViewAccessibility().SetName(l10n_util::GetStringFUTF16(
IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SHORTCUT,
l10n_util::GetStringUTF16(has_launcher_keyboard_button
? IDS_ASH_SHORTCUT_MODIFIER_LAUNCHER
: IDS_ASH_SHORTCUT_MODIFIER_SEARCH)));
// Text style.
views::StyledLabel::RangeStyleInfo text_style;
text_style.custom_font = TypographyProvider::Get()->ResolveTypographyToken(
TypographyToken::kCrosButton2);
text_style.override_color_id = cros_tokens::kCrosSysPrimary;
styled_label->AddStyleRange(gfx::Range(0u, inline_icon_offset), text_style);
styled_label->AddStyleRange(
gfx::Range(inline_icon_offset + 1u, styled_label->GetText().size()),
std::move(text_style));
// Inline icon.
auto icon = std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
has_launcher_keyboard_button ? kGdLauncherIcon : kGdSearchIcon,
cros_tokens::kCrosSysPrimary, /*icon_size=*/16));
icon->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(0, 1, 0, 4)));
views::StyledLabel::RangeStyleInfo inline_icon_style;
inline_icon_style.custom_view = icon.get();
styled_label->AddStyleRange(
gfx::Range(inline_icon_offset, inline_icon_offset + 1u),
std::move(inline_icon_style));
// Add the icon into the styled label.
styled_label->AddCustomView(std::move(icon));
}
BEGIN_METADATA(GameDashboardWelcomeDialog)
END_METADATA
} // namespace ash