chromium/ash/game_dashboard/game_dashboard_main_menu_view.cc

// Copyright 2023 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_main_menu_view.h"

#include <memory>

#include "ash/bubble/bubble_utils.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/game_dashboard/game_dashboard_context.h"
#include "ash/game_dashboard/game_dashboard_controller.h"
#include "ash/game_dashboard/game_dashboard_metrics.h"
#include "ash/game_dashboard/game_dashboard_utils.h"
#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/arc_compat_mode_util.h"
#include "ash/public/cpp/arc_game_controls_flag.h"
#include "ash/public/cpp/arc_resize_lock_type.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/public/cpp/system/anchored_nudge_data.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/pill_button.h"
#include "ash/style/style_util.h"
#include "ash/style/switch.h"
#include "ash/style/typography.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/time_view.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/system/unified/feature_pod_button.h"
#include "base/functional/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
#include "chromeos/ui/frame/frame_header.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/accessibility/ax_enums.mojom-shared.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/highlight_path_generator.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/box_layout_view.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

// Corner radius for the main menu.
constexpr int kBubbleCornerRadius = 24;
// Horizontal padding for the border around the main menu.
constexpr int kPaddingWidth = 20;
// Vertical padding for the border around the main menu.
constexpr int kPaddingHeight = 20;
// Padding between children in a row or column.
constexpr int kCenterPadding = 8;
// Main Menu fixed width.
constexpr int kMainMenuFixedWidth = 416;
// Corner radius for the detail row container.
constexpr float kDetailRowCornerRadius = 16.0f;
// Corner radius for feature tiles.
constexpr int kTileCornerRadius = 20;
// Line height for feature tiles with sub-labels
constexpr int kTileLabelLineHeight = 16;
// Feature Tile default padding when there are less than 4 Feature Tiles in the
// Shortcut Tiles Row.
constexpr gfx::Insets kDefaultTilePadding = gfx::Insets::TLBR(0, 24, 10, 24);
// Feature Tile padding when there are 4 Feature Tiles in the Shortcut Tiles
// Row.
constexpr gfx::Insets kFourTilePadding = gfx::Insets::TLBR(0, 10, 10, 10);
// Feature Tile Icon Padding.
constexpr gfx::Insets kTileIconPadding = gfx::Insets::TLBR(12, 8, 4, 8);
// Primary Feature Tile Label Padding.
constexpr gfx::Insets kPrimaryTileLabelPadding = gfx::Insets::TLBR(0, 0, 0, 15);
// Clock View Padding.
constexpr gfx::Insets kClockViewPadding = gfx::Insets::TLBR(10, 10, 10, 10);

// Row corners used for the top row of a multi-feature row collection.
constexpr gfx::RoundedCornersF kTopMultiRowCorners =
    gfx::RoundedCornersF(/*upper_left=*/kDetailRowCornerRadius,
                         /*upper_right=*/kDetailRowCornerRadius,
                         /*lower_right=*/2.0f,
                         /*lower_left=*/2.0f);
// Row corners used for the bottom row of a multi-featue row collection.
constexpr gfx::RoundedCornersF kBottomMultiRowCorners =
    gfx::RoundedCornersF(/*upper_left=*/2.0f,
                         /*upper_right=*/2.0f,
                         /*lower_right=*/kDetailRowCornerRadius,
                         /*lower_left=*/kDetailRowCornerRadius);
// Row corners used for a single feature row collection.
constexpr gfx::RoundedCornersF kSingleRowCorners =
    gfx::RoundedCornersF(kDetailRowCornerRadius);

// For setup button pulse animation.
constexpr int kSetupPulseExtraHalfSize = 32;
constexpr int kSetupPulseTimes = 3;
constexpr base::TimeDelta kSetupPulseDuration = base::Seconds(2);

constexpr char kSetupNudgeId[] = "SetupNudgeId";
constexpr char kHelpUrl[] =
    "https://support.google.com/chromebook/?p=game-dashboard-help";

// Creates an individual Game Dashboard Tile.
std::unique_ptr<FeatureTile> CreateFeatureTile(
    base::RepeatingClosure callback,
    bool is_togglable,
    FeatureTile::TileType type,
    int id,
    const gfx::VectorIcon& icon,
    const std::u16string& text,
    const std::optional<std::u16string>& sub_label) {
  auto tile =
      std::make_unique<FeatureTile>(std::move(callback), is_togglable, type);

  views::Label* tile_sub_label = tile->sub_label();
  if (sub_label) {
    tile->SetSubLabel(sub_label.value());
    tile->SetSubLabelVisibility(true);
    tile_sub_label->SetLineHeight(kTileLabelLineHeight);
  }

  tile->SetID(id);
  tile->SetVectorIcon(icon);
  tile->SetLabel(text);
  tile->SetTooltipText(text);
  tile->SetButtonCornerRadius(kTileCornerRadius);
  tile->SetTitleContainerMargins(kDefaultTilePadding);

  // Default state colors.
  tile->SetBackgroundColorId(cros_tokens::kCrosSysSystemOnBase);
  tile->SetForegroundColorId(cros_tokens::kCrosSysOnSurface);
  tile->SetForegroundOptionalColorId(cros_tokens::kCrosSysOnSurface);

  // Toggled state colors.
  tile->SetBackgroundToggledColorId(
      cros_tokens::kCrosSysSystemPrimaryContainer);
  tile->SetForegroundToggledColorId(
      cros_tokens::kCrosSysSystemOnPrimaryContainer);
  tile->SetForegroundOptionalToggledColorId(
      cros_tokens::kCrosSysSystemOnPrimaryContainer);

  // Disabled state colors.
  tile->SetBackgroundDisabledColorId(cros_tokens::kCrosSysSystemOnBaseOpaque);

  views::Label* tile_label = tile->label();

  tile_label->SetLineHeight(kTileLabelLineHeight);

  // Readjust Compact Tiles.
  views::ImageButton* tile_icon = tile->icon_button();
  if (type == FeatureTile::TileType::kCompact) {
    // Adjust internal spacing.
    tile_icon->SetProperty(views::kMarginsKey, kTileIconPadding);

    // Adjust line and text specifications.
    tile_label->SetFontList(
        TypographyProvider::Get()
            ->ResolveTypographyToken(TypographyToken::kCrosAnnotation2)
            .DeriveWithSizeDelta(1)
            .DeriveWithHeightUpperBound(16));
    tile_sub_label->SetFontList(
        TypographyProvider::Get()->ResolveTypographyToken(
            TypographyToken::kCrosAnnotation2));

  } else {
    // Resize the icon and its margins.
    tile_icon->SetPreferredSize(
        gfx::Size(20, tile_icon->GetPreferredSize().height()));
    tile_icon->SetProperty(views::kMarginsKey, kTileIconPadding);

    // Adjust line specifications and enable text wrapping.
    tile_label->SetProperty(views::kMarginsKey, kPrimaryTileLabelPadding);
    tile_label->SetMultiLine(true);
  }

  // Setup focus ring.
  views::FocusRing::Get(tile.get())->SetColorId(cros_tokens::kCrosSysPrimary);
  return tile;
}

std::unique_ptr<FeaturePodIconButton> CreateIconButton(
    base::RepeatingClosure callback,
    int id,
    const gfx::VectorIcon& icon,
    const std::u16string& text) {
  auto icon_button = std::make_unique<FeaturePodIconButton>(
      std::move(callback), /*is_togglable=*/false);
  icon_button->SetID(id);
  icon_button->SetVectorIcon(icon);
  icon_button->SetTooltipText(text);
  return icon_button;
}

bool IsGameControlsFeatureEnabled(ArcGameControlsFlag flags) {
  return game_dashboard_utils::IsFlagSet(flags, ArcGameControlsFlag::kEnabled);
}

// Helper function to configure the feature row button designs and return the
// layout manager.
views::BoxLayout* ConfigureFeatureRowLayout(views::Button* button,
                                            const gfx::RoundedCornersF& corners,
                                            bool enabled) {
  auto* layout = button->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kHorizontal,
      /*inside_border_insets=*/gfx::Insets::VH(16, 16)));
  layout->set_cross_axis_alignment(
      views::BoxLayout::CrossAxisAlignment::kCenter);
  button->SetNotifyEnterExitOnChild(true);
  button->SetEnabled(enabled);
  button->SetBackground(views::CreateThemedRoundedRectBackground(
      enabled ? cros_tokens::kCrosSysSystemOnBase
              : cros_tokens::kCrosSysSystemOnBaseOpaque,
      corners));

  // Set up highlight ink drop and focus ring.
  views::HighlightPathGenerator::Install(
      button, std::make_unique<views::RoundRectHighlightPathGenerator>(
                  gfx::Insets(), corners));

  // Set up press ripple.
  auto* ink_drop = views::InkDrop::Get(button);
  ink_drop->SetMode(views::InkDropHost::InkDropMode::ON);
  ink_drop->GetInkDrop()->SetShowHighlightOnHover(false);
  ink_drop->GetInkDrop()->SetShowHighlightOnFocus(false);
  ink_drop->SetVisibleOpacity(1.0f);
  ink_drop->SetBaseColorId(cros_tokens::kCrosSysRippleNeutralOnSubtle);

  // Set up focus ring.
  auto* focus_ring = views::FocusRing::Get(button);
  focus_ring->SetHaloInset(-5);
  focus_ring->SetHaloThickness(2);
  focus_ring->SetColorId(cros_tokens::kCrosSysPrimary);

  return layout;
}

// -----------------------------------------------------------------------------
// FeatureHeader:

// `FeatureHeader` includes icon, title and sub-title.
// +---------------------+
// | |icon|  |title|     |
// |         |sub-title| |
// +---------------------+
class FeatureHeader : public views::View {
  METADATA_HEADER(FeatureHeader, views::View)

 public:
  FeatureHeader(bool is_enabled,
                const gfx::VectorIcon& icon,
                const std::u16string& title)
      : vector_icon_(&icon) {
    auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>());
    layout->set_cross_axis_alignment(
        views::BoxLayout::CrossAxisAlignment::kCenter);

    // Add icon.
    icon_view_ = AddChildView(std::make_unique<views::ImageView>());
    icon_view_->SetBackground(views::CreateThemedRoundedRectBackground(
        cros_tokens::kCrosSysSystemOnBase,
        /*radius=*/12.0f));
    icon_view_->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(6, 6)));
    icon_view_->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(0, 0, 0, 16));

    // Add title and sub-title.
    auto* tag_container =
        AddChildView(std::make_unique<views::BoxLayoutView>());
    tag_container->SetOrientation(views::BoxLayout::Orientation::kVertical);
    tag_container->SetCrossAxisAlignment(
        views::BoxLayout::CrossAxisAlignment::kStart);
    // Flex `tag_container` to fill empty space.
    layout->SetFlexForView(tag_container, /*flex=*/1);

    // Add title.
    title_ = tag_container->AddChildView(std::make_unique<views::Label>(title));
    title_->SetAutoColorReadabilityEnabled(false);
    title_->SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
        TypographyToken::kCrosTitle2));
    title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    title_->SetMultiLine(true);
    // Add sub-title.
    sub_title_ = tag_container->AddChildView(std::make_unique<views::Label>());
    sub_title_->SetAutoColorReadabilityEnabled(false);
    sub_title_->SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
        TypographyToken::kCrosAnnotation2));
    sub_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    sub_title_->SetMultiLine(true);

    UpdateColors(is_enabled);
  }

  FeatureHeader(const FeatureHeader&) = delete;
  FeatureHeader& operator=(const FeatureHeader) = delete;
  ~FeatureHeader() override = default;

  const views::Label* GetSubtitle() { return sub_title_.get(); }

  void UpdateColors(bool is_enabled) {
    const auto color_id = is_enabled ? cros_tokens::kCrosSysOnSurface
                                     : cros_tokens::kCrosSysDisabled;
    icon_view_->SetImage(ui::ImageModel::FromVectorIcon(*vector_icon_, color_id,
                                                        /*icon_size=*/20));
    title_->SetEnabledColorId(color_id);
    sub_title_->SetEnabledColorId(is_enabled
                                      ? cros_tokens::kCrosSysOnSurfaceVariant
                                      : cros_tokens::kCrosSysDisabled);
  }

  void UpdateSubtitle(const std::u16string& text) {
    // For multiline label, if the fixed width is not set, the preferred size is
    // re-calcuated based on previous label size as available size instead of
    // its real available size when changing the text. For `sub_title_`, it
    // takes the whole width of its parent's width as fixed width after layout.
    if (!sub_title_->GetFixedWidth()) {
      if (int width = sub_title_->parent()->size().width(); width != 0) {
        sub_title_->SizeToFit(width);
      }
    }
    sub_title_->SetText(text);
  }

 private:
  const raw_ptr<const gfx::VectorIcon> vector_icon_;

  raw_ptr<views::ImageView> icon_view_ = nullptr;
  raw_ptr<views::Label> title_ = nullptr;
  raw_ptr<views::Label> sub_title_ = nullptr;
};

BEGIN_METADATA(FeatureHeader)
END_METADATA

}  // namespace

// -----------------------------------------------------------------------------
// ScreenSizeRow:

// ScreenSizeRow includes `FeatureHeader` and right arrow icon.
// +------------------------------------------------+
// | |feature header|                           |>| |
// +------------------------------------------------+
class GameDashboardMainMenuView::ScreenSizeRow : public views::Button {
  METADATA_HEADER(ScreenSizeRow, views::Button)

 public:
  ScreenSizeRow(GameDashboardMainMenuView* main_menu,
                PressedCallback callback,
                ResizeCompatMode resize_mode,
                ArcResizeLockType resize_lock_type)
      : views::Button(std::move(callback)) {
    SetID(VIEW_ID_GD_SCREEN_SIZE_TILE);

    bool enabled = false;
    int tooltip = 0;
    std::u16string subtitle;
    switch (resize_lock_type) {
      case ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE:
      case ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
        enabled = true;
        subtitle = compat_mode_util::GetText(resize_mode);
        break;
      case ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
        enabled = false;
        tooltip =
            IDS_ASH_ARC_APP_COMPAT_DISABLED_COMPAT_MODE_BUTTON_TOOLTIP_PHONE;
        DCHECK_NE(resize_mode, ResizeCompatMode::kResizable)
            << "The resize mode should never be resizable with an "
               "ArcResizeLockType of RESIZE_DISABLED_NONTOGGLABLE.";
        if (resize_mode == ResizeCompatMode::kPhone) {
          subtitle = l10n_util::GetStringUTF16(
              IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_ONLY_PORTRAIT);
        } else {
          subtitle = l10n_util::GetStringUTF16(
              IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_ONLY_LANDSCAPE);
        }
        break;
      case ArcResizeLockType::NONE:
        enabled = false;
        tooltip = IDS_ASH_GAME_DASHBOARD_FEATURE_NOT_AVAILABLE_TOOLTIP;

        // Set the subtitle text based on whether the size button in the frame
        // header is present.
        auto* frame_header =
            chromeos::FrameHeader::Get(views::Widget::GetWidgetForNativeWindow(
                main_menu->context_->game_window()));
        views::FrameCaptionButton* size_button =
            frame_header->caption_button_container()->size_button();
        if (size_button && size_button->GetVisible()) {
          subtitle = l10n_util::GetStringUTF16(
              IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_EXIT_FULLSCREEN);
        } else {
          subtitle = l10n_util::GetStringUTF16(
              IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_ONLY_FULLSCREEN);
        }
        break;
    }

    const std::u16string title = l10n_util::GetStringUTF16(
        IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_SETTINGS_TITLE);
    SetTooltipText(tooltip ? l10n_util::GetStringUTF16(tooltip) : title);
    GetViewAccessibility().SetName(l10n_util::GetStringUTF16(
        IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_SETTINGS_BUTTON_A11Y_LABEL));

    auto* layout =
        ConfigureFeatureRowLayout(this, kBottomMultiRowCorners, enabled);
    // Add header.
    feature_header_ = AddChildView(std::make_unique<FeatureHeader>(
        enabled, compat_mode_util::GetIcon(resize_mode), title));
    layout->SetFlexForView(feature_header_, /*flex=*/1);
    feature_header_->UpdateSubtitle(subtitle);
    // Add arrow icon.
    AddChildView(
        std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
            kQuickSettingsRightArrowIcon,
            enabled ? cros_tokens::kCrosSysOnSurface
                    : cros_tokens::kCrosSysDisabled)));
  }

  ScreenSizeRow(const ScreenSizeRow&) = delete;
  ScreenSizeRow& operator=(const ScreenSizeRow) = delete;
  ~ScreenSizeRow() override = default;

  FeatureHeader* feature_header() { return feature_header_; }

 private:
  raw_ptr<FeatureHeader> feature_header_;
};

BEGIN_METADATA(GameDashboardMainMenuView, ScreenSizeRow)
END_METADATA

// -----------------------------------------------------------------------------
// GameDashboardMainMenuView::GameControlsDetailsRow:

// `GameControlsDetailsRow` includes `FeatureHeader`, set up button or switch
// button with drill in arrow icon. If there is no Game Controls set up, it
// shows as:
// +------------------------------------------------+
// | |feature header|                |set_up button||
// +------------------------------------------------+
// Otherwise, it shows as:
// +------------------------------------------------+
// | |feature header|      |switch| |drill in arrow||
// +------------------------------------------------+
class GameDashboardMainMenuView::GameControlsDetailsRow : public views::Button {
  METADATA_HEADER(GameControlsDetailsRow, views::Button)

 public:
  explicit GameControlsDetailsRow(GameDashboardMainMenuView* main_menu,
                                  const gfx::RoundedCornersF& row_corners)
      : views::Button(
            base::BindRepeating(&GameControlsDetailsRow::OnButtonPressed,
                                base::Unretained(this))),
        main_menu_(main_menu) {
    CacheAppName();
    SetID(VIEW_ID_GD_CONTROLS_DETAILS_ROW);

    const auto flags =
        game_dashboard_utils::GetGameControlsFlag(GetGameWindow());
    CHECK(flags);

    SetTooltipText(l10n_util::GetStringUTF16(
        IDS_ASH_GAME_DASHBOARD_GC_CONTROLS_DETAILS_BUTTON_TOOLTIP));

    const bool is_available = game_dashboard_utils::IsFlagSet(
        *flags, ArcGameControlsFlag::kAvailable);
    auto* layout = ConfigureFeatureRowLayout(this, row_corners, is_available);

    // Add header.
    header_ = AddChildView(std::make_unique<FeatureHeader>(
        /*is_enabled=*/is_available, kGdGameControlsIcon,
        l10n_util::GetStringUTF16(
            IDS_ASH_GAME_DASHBOARD_CONTROLS_TILE_BUTTON_TITLE)));
    // Flex `header_` to fill the empty space.
    layout->SetFlexForView(header_, /*flex=*/1);

    // Add setup button, or feature switch and drill-in arrow.
    if (!is_available ||
        game_dashboard_utils::IsFlagSet(*flags, ArcGameControlsFlag::kEmpty)) {
      // Add setup button.
      header_->UpdateSubtitle(l10n_util::GetStringUTF16(
          IDS_ASH_GAME_DASHBOARD_GC_SET_UP_SUB_TITLE));
      setup_button_ = AddChildView(std::make_unique<PillButton>(
          base::BindRepeating(&GameControlsDetailsRow::OnSetUpButtonPressed,
                              base::Unretained(this)),
          l10n_util::GetStringUTF16(
              IDS_ASH_GAME_DASHBOARD_GC_SET_UP_BUTTON_LABEL),
          PillButton::Type::kPrimaryWithoutIcon,
          /*icon=*/nullptr));
      setup_button_->SetProperty(views::kMarginsKey,
                                 gfx::Insets::TLBR(0, 20, 0, 0));
      setup_button_->SetEnabled(is_available);
      if (!is_available) {
        setup_button_->SetTooltipText(l10n_util::GetStringUTF16(
            IDS_ASH_GAME_DASHBOARD_FEATURE_NOT_AVAILABLE_TOOLTIP));
      }
    } else {
      // Add switch_button to enable or disable game controls.
      feature_switch_ =
          AddChildView(std::make_unique<Switch>(base::BindRepeating(
              &GameControlsDetailsRow::OnFeatureSwitchButtonPressed,
              base::Unretained(this))));
      feature_switch_->SetProperty(views::kMarginsKey,
                                   gfx::Insets::TLBR(0, 8, 0, 18));
      const bool is_feature_enabled = IsGameControlsFeatureEnabled(*flags);
      feature_switch_->SetIsOn(is_feature_enabled);
      feature_switch_->SetTooltipText(l10n_util::GetStringUTF16(
          feature_switch_->GetIsOn()
              ? IDS_ASH_GAME_DASHBOARD_GC_FEATURE_SWITCH_TOOLTIPS_OFF
              : IDS_ASH_GAME_DASHBOARD_GC_FEATURE_SWITCH_TOOLTIPS_ON));
      // Add arrow icon.
      arrow_icon_ = AddChildView(std::make_unique<views::ImageView>());

      UpdateColors(is_feature_enabled);
      SetFocusBehavior(is_feature_enabled ? FocusBehavior::ALWAYS
                                          : FocusBehavior::ACCESSIBLE_ONLY);

      UpdateSubtitle(/*is_game_controls_enabled=*/is_feature_enabled);
    }
  }

  GameControlsDetailsRow(const GameControlsDetailsRow&) = delete;
  GameControlsDetailsRow& operator=(const GameControlsDetailsRow) = delete;
  ~GameControlsDetailsRow() override = default;

  // views::View:
  void VisibilityChanged(views::View* starting_from, bool is_visible) override {
    if (is_visible) {
      MaybeDecorateSetupButton();
    } else {
      RemoveSetupButtonDecorationIfAny();
    }
  }

  PillButton* setup_button() { return setup_button_; }
  Switch* feature_switch() { return feature_switch_; }

 private:
  void OnButtonPressed() {
    const auto flags =
        game_dashboard_utils::GetGameControlsFlag(GetGameWindow());
    DCHECK(flags && game_dashboard_utils::IsFlagSet(
                        *flags, ArcGameControlsFlag::kAvailable));

    // Do nothing if Game Controls is disabled.
    if (!IsGameControlsFeatureEnabled(*flags)) {
      return;
    }

    EnableEditMode();
  }

  void OnSetUpButtonPressed() { EnableEditMode(); }

  void OnFeatureSwitchButtonPressed() {
    const bool is_switch_on = feature_switch_->GetIsOn();
    // When `feature_switch_` toggles on or off, it updates the colors but does
    // not enable or disable this button.
    UpdateColors(is_switch_on);
    SetFocusBehavior(is_switch_on ? FocusBehavior::ALWAYS
                                  : FocusBehavior::ACCESSIBLE_ONLY);

    auto* game_window = GetGameWindow();
    game_window->SetProperty(
        kArcGameControlsFlagsKey,
        game_dashboard_utils::UpdateFlag(
            game_window->GetProperty(kArcGameControlsFlagsKey),
            static_cast<ArcGameControlsFlag>(
                /*enable_flag=*/ArcGameControlsFlag::kEnabled |
                ArcGameControlsFlag::kHint),
            is_switch_on));
    feature_switch_->SetTooltipText(l10n_util::GetStringUTF16(
        is_switch_on ? IDS_ASH_GAME_DASHBOARD_GC_FEATURE_SWITCH_TOOLTIPS_OFF
                     : IDS_ASH_GAME_DASHBOARD_GC_FEATURE_SWITCH_TOOLTIPS_ON));

    main_menu_->UpdateGameControlsTile();
    UpdateSubtitle(/*is_game_controls_enabled=*/is_switch_on);

    RecordGameDashboardControlsFeatureToggleState(
        main_menu_->context_->app_id(), is_switch_on);
  }

  void UpdateColors(bool enabled) {
    SetBackground(views::CreateThemedRoundedRectBackground(
        enabled ? cros_tokens::kCrosSysSystemOnBase
                : cros_tokens::kCrosSysSystemOnBaseOpaque,
        kTopMultiRowCorners));
    header_->UpdateColors(enabled);
    CHECK(arrow_icon_);
    arrow_icon_->SetImage(ui::ImageModel::FromVectorIcon(
        kQuickSettingsRightArrowIcon, enabled ? cros_tokens::kCrosSysOnSurface
                                              : cros_tokens::kCrosSysDisabled));
  }

  void UpdateSubtitle(bool is_feature_enabled) {
    const auto string_id =
        is_feature_enabled
            ? IDS_ASH_GAME_DASHBOARD_GC_DETAILS_SUB_TITLE_ON_TEMPLATE
            : IDS_ASH_GAME_DASHBOARD_GC_DETAILS_SUB_TITLE_OFF_TEMPLATE;
    header_->UpdateSubtitle(
        l10n_util::GetStringFUTF16(string_id, base::UTF8ToUTF16(app_name_)));

    // In case the sub-title turns to two lines from one line.
    if (GetWidget()) {
      main_menu_->SizeToContents();
    }
  }

  void CacheAppName() {
    if (std::string* app_id = GetGameWindow()->GetProperty(kAppIDKey)) {
      app_name_ = GameDashboardController::Get()->GetArcAppName(*app_id);
    }
  }

  void EnableEditMode() {
    auto* game_window = GetGameWindow();
    const auto flags = game_dashboard_utils::GetGameControlsFlag(game_window);
    CHECK(flags);
    game_window->SetProperty(
        kArcGameControlsFlagsKey,
        game_dashboard_utils::UpdateFlag(*flags, ArcGameControlsFlag::kEdit,
                                         /*enable_flag=*/true));
    const auto& app_id = main_menu_->context_->app_id();
    RecordGameDashboardEditControlsWithEmptyState(
        app_id,
        game_dashboard_utils::IsFlagSet(*flags, ArcGameControlsFlag::kEmpty));
    RecordGameDashboardFunctionTriggered(
        app_id, GameDashboardFunction::kGameControlsSetupOrEdit);

    // Always close the main menu in the end in case of the race condition that
    // this instance is destroyed before the following calls.
    main_menu_->context_->CloseMainMenu(
        GameDashboardMainMenuToggleMethod::kActivateNewFeature);
  }

  aura::Window* GetGameWindow() { return main_menu_->context_->game_window(); }

  // Adds pulse animation and an education nudge for
  // `game_controls_setup_button_` if it exists, is enabled and not optimized
  // for ChromeOS.
  void MaybeDecorateSetupButton() {
    const auto flags =
        game_dashboard_utils::GetGameControlsFlag(GetGameWindow());
    CHECK(flags);

    if (!setup_button_ || !setup_button_->GetEnabled() ||
        game_dashboard_utils::IsFlagSet(*flags, ArcGameControlsFlag::kO4C)) {
      return;
    }

    ShowNudgeForSetupButton();
    PerformPulseAnimationForSetupButton(/*pulse_count=*/0);
  }

  // Performs pulse animation for `game_controls_setup_button_`.
  void PerformPulseAnimationForSetupButton(int pulse_count) {
    DCHECK(setup_button_);

    // Destroy the pulse layer if it pulses after `kSetupPulseTimes` times.
    if (pulse_count >= kSetupPulseTimes) {
      gc_setup_button_pulse_layer_.reset();
      return;
    }

    auto* widget = GetWidget();
    DCHECK(widget);

    // Initiate pulse layer if it starts to pulse for the first time.
    if (pulse_count == 0) {
      gc_setup_button_pulse_layer_ =
          std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
      widget->GetLayer()->Add(gc_setup_button_pulse_layer_.get());
      gc_setup_button_pulse_layer_->SetColor(
          widget->GetColorProvider()->GetColor(
              cros_tokens::kCrosSysHighlightText));
    }

    DCHECK(gc_setup_button_pulse_layer_);

    // Initial setup button bounds in its widget coordinate.
    const auto setup_bounds =
        setup_button_->ConvertRectToWidget(gfx::Rect(setup_button_->size()));

    // Set initial properties.
    const float initial_corner_radius = setup_bounds.height() / 2.0f;
    gc_setup_button_pulse_layer_->SetBounds(setup_bounds);
    gc_setup_button_pulse_layer_->SetOpacity(1.0f);
    gc_setup_button_pulse_layer_->SetRoundedCornerRadius(
        gfx::RoundedCornersF(initial_corner_radius));

    // Animate to target bounds, opacity and rounded corner radius.
    auto target_bounds = setup_bounds;
    target_bounds.Outset(kSetupPulseExtraHalfSize);
    views::AnimationBuilder()
        .SetPreemptionStrategy(
            ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
        .OnEnded(base::BindOnce(
            &GameControlsDetailsRow::PerformPulseAnimationForSetupButton,
            base::Unretained(this), pulse_count + 1))
        .Once()
        .SetDuration(kSetupPulseDuration)
        .SetBounds(gc_setup_button_pulse_layer_.get(), target_bounds,
                   gfx::Tween::ACCEL_0_40_DECEL_100)
        .SetOpacity(gc_setup_button_pulse_layer_.get(), 0.0f,
                    gfx::Tween::ACCEL_0_80_DECEL_80)
        .SetRoundedCorners(gc_setup_button_pulse_layer_.get(),
                           gfx::RoundedCornersF(initial_corner_radius +
                                                kSetupPulseExtraHalfSize),
                           gfx::Tween::ACCEL_0_40_DECEL_100);
  }

  // Shows education nudge for `game_controls_setup_button_`.
  void ShowNudgeForSetupButton() {
    DCHECK(setup_button_);

    auto nudge_data = AnchoredNudgeData(
        kSetupNudgeId, NudgeCatalogName::kGameDashboardControlsNudge,
        l10n_util::GetStringUTF16(
            IDS_ASH_GAME_DASHBOARD_GC_KEYBOARD_SETUP_NUDGE_SUB_TITLE),
        this);
    nudge_data.image_model =
        ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
            IDR_GAME_DASHBOARD_CONTROLS_SETUP_NUDGE);
    nudge_data.title_text = l10n_util::GetStringUTF16(
        IDS_ASH_GAME_DASHBOARD_GC_KEYBOARD_SETUP_NUDGE_TITLE);
    nudge_data.arrow = views::BubbleBorder::LEFT_CENTER;
    nudge_data.background_color_id = cros_tokens::kCrosSysBaseHighlight;
    nudge_data.image_background_color_id = cros_tokens::kCrosSysOnBaseHighlight;
    nudge_data.duration = NudgeDuration::kMediumDuration;
    nudge_data.highlight_anchor_button = false;

    Shell::Get()->anchored_nudge_manager()->Show(nudge_data);
  }

  // Removes the setup button pulse animation and nudge if there is any.
  void RemoveSetupButtonDecorationIfAny() {
    gc_setup_button_pulse_layer_.reset();
    Shell::Get()->anchored_nudge_manager()->Cancel(kSetupNudgeId);
  }

  const raw_ptr<GameDashboardMainMenuView> main_menu_;

  raw_ptr<FeatureHeader> header_ = nullptr;
  raw_ptr<PillButton> setup_button_ = nullptr;
  raw_ptr<Switch> feature_switch_ = nullptr;
  raw_ptr<views::ImageView> arrow_icon_ = nullptr;

  // App name from the app where this view is anchored.
  std::string app_name_;

  // Layer for setup button pulse animation.
  std::unique_ptr<ui::Layer> gc_setup_button_pulse_layer_;
};

BEGIN_METADATA(GameDashboardMainMenuView, GameControlsDetailsRow)
END_METADATA

// -----------------------------------------------------------------------------
// GameDashboardMainMenuView:

GameDashboardMainMenuView::GameDashboardMainMenuView(
    GameDashboardContext* context)
    : context_(context) {
  DCHECK(context_);
  DCHECK(context_->game_dashboard_button_widget());

  SetBorder(views::CreateRoundedRectBorder(
      /*thickness=*/1, kBubbleCornerRadius,
      cros_tokens::kCrosSysSystemHighlight1));
  set_corner_radius(kBubbleCornerRadius);
  // Closing on deactivation is manually handled by the `GameDashboardContext`
  // in order to support tabbing between sibling widgets.
  set_close_on_deactivate(false);
  set_internal_name("GameDashboardMainMenuView");
  set_margins(gfx::Insets());
  set_parent_window(
      context_->game_dashboard_button_widget()->GetNativeWindow());
  set_fixed_width(kMainMenuFixedWidth);
  SetAnchorView(context_->game_dashboard_button_widget()->GetContentsView());
  SetArrow(views::BubbleBorder::Arrow::NONE);
  SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical,
      gfx::Insets::VH(kPaddingHeight, kPaddingWidth),
      /*between_child_spacing=*/16));

  // TODO(b/326259321): Move the main menu view and settings view panels into
  // separate class containers and show/hide the view containers
  AddMainMenuViews();

  SizeToPreferredSize();

  // We set the dialog role because views::BubbleDialogDelegate defaults this to
  // an alert dialog. This would make screen readers announce every view in the
  // main menu, which is undesirable.
  SetAccessibleWindowRole(ax::mojom::Role::kDialog);
  SetAccessibleTitle(l10n_util::GetStringUTF16(
      IDS_ASH_GAME_DASHBOARD_GAME_DASHBOARD_BUTTON_TITLE));
}

GameDashboardMainMenuView::~GameDashboardMainMenuView() = default;

void GameDashboardMainMenuView::OnRecordingStarted(
    bool is_recording_game_window) {
  UpdateRecordGameTile(is_recording_game_window);
}

void GameDashboardMainMenuView::OnRecordingEnded() {
  UpdateRecordGameTile(/*is_recording_game_window=*/false);
}

void GameDashboardMainMenuView::UpdateRecordingDuration(
    const std::u16string& duration) {
  record_game_tile_->SetSubLabel(duration);
}

void GameDashboardMainMenuView::OnToolbarTilePressed() {
  bool toolbar_visible = context_->ToggleToolbar();
  game_dashboard_utils::SetShowToolbar(toolbar_visible);
  toolbar_tile_->SetSubLabel(
      toolbar_visible
          ? l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_VISIBLE_STATUS)
          : l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_HIDDEN_STATUS));
  toolbar_tile_->SetToggled(toolbar_visible);
  toolbar_tile_->SetTooltipText(l10n_util::GetStringUTF16(
      toolbar_tile_->IsToggled()
          ? IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_TOOLTIPS_HIDE_TOOLBAR
          : IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_TOOLTIPS_SHOW_TOOLBAR));
}

void GameDashboardMainMenuView::OnRecordGameTilePressed() {
  context_->set_recording_from_main_menu(true);

  if (record_game_tile_->IsToggled()) {
    CaptureModeController::Get()->EndVideoRecording(
        EndRecordingReason::kGameDashboardStopRecordingButton);
  } else {
    // Post a task to start a capture session, after the main menu widget
    // closes. When the main menu opens, `GameDashboardContext` registers
    // `GameDashboardMainMenuCursorHandler` as a pretarget handler to always
    // show the mouse cursor. `GameDashboardMainMenuCursorHandler` gets the
    // `wm::CursorManager`, makes the mouse cursor visible, and locks it. This
    // is to prevent other components from changing it.
    // `CaptureModeController::StartForGameDashboard()` also locks the mouse
    // cursor in a similar fashion. The nested locking/unlocking has an
    // undesirable behavior. Starting the capture session in a different task
    // makes the lock/unlock behavior in `wm::CursorManager` occur serially.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(
                       [](base::WeakPtr<GameDashboardContext> context) {
                         if (context) {
                           GameDashboardController::Get()->StartCaptureSession(
                               context.get());
                         }
                       },
                       context_->GetWeakPtr()));

    // Always close the main menu in the end in case of the race condition that
    // this instance is destroyed before the following calls.
    context_->CloseMainMenu(
        GameDashboardMainMenuToggleMethod::kActivateNewFeature);
  }
}

void GameDashboardMainMenuView::OnScreenshotTilePressed() {
  auto* game_window = context_->game_window();
  CaptureModeController::Get()->CaptureScreenshotOfGivenWindow(game_window);

  RecordGameDashboardScreenshotTakeSource(context_->app_id(),
                                          GameDashboardMenu::kMainMenu);

  // Always close the main menu in the end in case of the race condition that
  // this instance is destroyed before the following calls.
  context_->CloseMainMenu(
      GameDashboardMainMenuToggleMethod::kActivateNewFeature);
}

void GameDashboardMainMenuView::OnSettingsBackButtonPressed() {
  DCHECK(settings_view_container_ && main_menu_container_);
  DCHECK(settings_view_container_->GetVisible() &&
         !main_menu_container_->GetVisible());
  settings_view_container_->SetVisible(false);
  main_menu_container_->SetVisible(true);
  SizeToContents();
  RecordGameDashboardFunctionTriggered(context_->app_id(),
                                       GameDashboardFunction::kSettingBack);
}

void GameDashboardMainMenuView::OnWelcomeDialogSwitchPressed() {
  const bool new_state = welcome_dialog_settings_switch_->GetIsOn();
  game_dashboard_utils::SetShowWelcomeDialog(new_state);
  OnWelcomeDialogSwitchStateChanged(new_state);
  RecordGameDashboardWelcomeDialogNotificationToggleState(context_->app_id(),
                                                          new_state);
}

void GameDashboardMainMenuView::OnGameControlsTilePressed() {
  auto* game_window = context_->game_window();
  const bool was_toggled = game_controls_tile_->IsToggled();
  game_window->SetProperty(
      kArcGameControlsFlagsKey,
      game_dashboard_utils::UpdateFlag(
          game_window->GetProperty(kArcGameControlsFlagsKey),
          ArcGameControlsFlag::kHint,
          /*enable_flag=*/!was_toggled));
  UpdateGameControlsTile();
  RecordGameDashboardControlsHintToggleSource(
      context_->app_id(), GameDashboardMenu::kMainMenu, !was_toggled);
}

void GameDashboardMainMenuView::UpdateGameControlsTile() {
  DCHECK(game_controls_tile_);

  const auto flags =
      game_dashboard_utils::GetGameControlsFlag(context_->game_window());
  CHECK(flags);

  game_dashboard_utils::UpdateGameControlsHintButton(game_controls_tile_,
                                                     *flags);
}

void GameDashboardMainMenuView::OnScreenSizeSettingsButtonPressed() {
  GameDashboardController::Get()->ShowResizeToggleMenu(context_->game_window());
  RecordGameDashboardFunctionTriggered(context_->app_id(),
                                       GameDashboardFunction::kScreenSize);

  // Always close the main menu in the end in case of the race condition that
  // this instance is destroyed before the following calls.
  context_->CloseMainMenu(
      GameDashboardMainMenuToggleMethod::kActivateNewFeature);
}

void GameDashboardMainMenuView::OnFeedbackButtonPressed() {
  Shell::Get()->shell_delegate()->OpenFeedbackDialog(
      ShellDelegate::FeedbackSource::kGameDashboard,
      /*description_template=*/"#GameDashboard\n\n",
      /*category_tag=*/std::string());
  RecordGameDashboardFunctionTriggered(context_->app_id(),
                                       GameDashboardFunction::kFeedback);
}

void GameDashboardMainMenuView::OnHelpButtonPressed() {
  NewWindowDelegate::GetPrimary()->OpenUrl(
      GURL(kHelpUrl), NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      NewWindowDelegate::Disposition::kNewForegroundTab);
  RecordGameDashboardFunctionTriggered(context_->app_id(),
                                       GameDashboardFunction::kHelp);
}

void GameDashboardMainMenuView::OnSettingsButtonPressed() {
  DCHECK(main_menu_container_ && main_menu_container_->GetVisible());
  main_menu_container_->SetVisible(false);
  if (settings_view_container_) {
    settings_view_container_->SetVisible(true);
  } else {
    AddSettingsViews();
  }
  SizeToContents();
  RecordGameDashboardFunctionTriggered(context_->app_id(),
                                       GameDashboardFunction::kSetting);
}

void GameDashboardMainMenuView::AddMainMenuViews() {
  DCHECK(!main_menu_container_);
  main_menu_container_ = AddChildView(std::make_unique<views::BoxLayoutView>());
  main_menu_container_->SetOrientation(
      views::BoxLayout::Orientation::kVertical);
  main_menu_container_->SetBetweenChildSpacing(16);

  AddShortcutTilesRow();
  MaybeAddArcFeatureRows();
  AddUtilityClusterRow();
}

void GameDashboardMainMenuView::AddShortcutTilesRow() {
  DCHECK(main_menu_container_);
  views::BoxLayoutView* container = main_menu_container_->AddChildView(
      std::make_unique<views::BoxLayoutView>());
  container->SetOrientation(views::BoxLayout::Orientation::kHorizontal);
  container->SetBetweenChildSpacing(kCenterPadding);

  std::optional<ArcGameControlsFlag> game_controls_flags =
      game_dashboard_utils::GetGameControlsFlag(context_->game_window());
  const bool record_feature_enabled = base::FeatureList::IsEnabled(
      features::kFeatureManagementGameDashboardRecordGame);

  // Determines the tile type to assign to all Feature Tiles. There will be at
  // least 2 tiles. In cases that there are more, the tile type is set to
  // 'FeatureTile::TileType::kCompact', and if not,
  // 'FeatureTile::TileType::kPrimary'.
  const FeatureTile::TileType tile_type =
      (game_controls_flags || record_feature_enabled)
          ? FeatureTile::TileType::kCompact
          : FeatureTile::TileType::kPrimary;

  const bool toolbar_visible = context_->IsToolbarVisible();
  toolbar_tile_ = container->AddChildView(CreateFeatureTile(
      base::BindRepeating(&GameDashboardMainMenuView::OnToolbarTilePressed,
                          base::Unretained(this)),
      /*is_togglable=*/true, tile_type, VIEW_ID_GD_TOOLBAR_TILE, kGdToolbarIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_BUTTON_TITLE),
      toolbar_visible
          ? l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_VISIBLE_STATUS)
          : l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_HIDDEN_STATUS)));
  toolbar_tile_->SetToggled(toolbar_visible);
  toolbar_tile_->SetTooltipText(l10n_util::GetStringUTF16(
      toolbar_tile_->IsToggled()
          ? IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_TOOLTIPS_HIDE_TOOLBAR
          : IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_TOOLTIPS_SHOW_TOOLBAR));

  if (game_controls_flags) {
    AddGameControlsTile(container, tile_type);
  }

  if (record_feature_enabled) {
    AddRecordGameTile(container, tile_type);
  }

  auto* screenshot_tile = container->AddChildView(CreateFeatureTile(
      base::BindRepeating(&GameDashboardMainMenuView::OnScreenshotTilePressed,
                          base::Unretained(this)),
      /*is_togglable=*/true, tile_type, VIEW_ID_GD_SCREENSHOT_TILE,
      kGdScreenshotIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_GAME_DASHBOARD_SCREENSHOT_TILE_BUTTON_TITLE),
      /*sub_label=*/std::nullopt));
  // `screenshot_tile` is treated as a button instead of toggle button here.
  screenshot_tile->GetViewAccessibility().SetRole(ax::mojom::Role::kButton);

  // Remove the sub-label view from Screenshot Feature Tile.
  if (tile_type == FeatureTile::TileType::kPrimary) {
    screenshot_tile->sub_label()->SetVisible(false);
  }

  // Shortcut tiles row holds up to 4 tiles, and always contains the
  // 'toolbar_tile' and the 'screenshot_tile'. If there are 4 tiles in the row,
  // the padding is set to 'kFourTilePadding', otherwise, the padding is set to
  // 'kDefaultTilePadding'.
  const auto title_container_margin = (game_controls_tile_ && record_game_tile_)
                                          ? kFourTilePadding
                                          : kDefaultTilePadding;
  for (auto tile : container->children()) {
    // Ensure that the Feature Tiles stretch out to equal width and height in
    // the Feature Tile row.
    tile->SetPreferredSize(gfx::Size(1, tile->GetPreferredSize().height()));
    // Adjust padding for depending on tile quantity to prevent clipping.
    views::AsViewClass<FeatureTile>(tile)->SetTitleContainerMargins(
        title_container_margin);
  }

  container->SetDefaultFlex(1);
}

void GameDashboardMainMenuView::MaybeAddArcFeatureRows() {
  const aura::Window* game_window = context_->game_window();
  if (!IsArcWindow(game_window)) {
    return;
  }
  DCHECK(main_menu_container_);
  auto* feature_details_container =
      main_menu_container_->AddChildView(std::make_unique<views::View>());
  feature_details_container->SetLayoutManager(
      std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kVertical,
          /*inside_border_insets=*/gfx::Insets(),
          /*between_child_spacing=*/2));
  const std::optional<ArcGameControlsFlag> flags =
      game_dashboard_utils::GetGameControlsFlag(game_window);
  const bool has_multi_rows =
      !game_dashboard_utils::IsFlagSet(*flags, ArcGameControlsFlag::kO4C);
  AddGameControlsDetailsRow(feature_details_container, has_multi_rows
                                                           ? kTopMultiRowCorners
                                                           : kSingleRowCorners);
  if (has_multi_rows) {
    // Only add the Screen Size row if an app is NOT O4C.
    AddScreenSizeSettingsRow(feature_details_container);
  }
}

void GameDashboardMainMenuView::AddGameControlsTile(
    views::View* container,
    FeatureTile::TileType tile_type) {
  DCHECK(game_dashboard_utils::GetGameControlsFlag(context_->game_window()));

  // Add the game controls tile which shows and hides the game controls
  // mapping hint.
  game_controls_tile_ = container->AddChildView(CreateFeatureTile(
      base::BindRepeating(&GameDashboardMainMenuView::OnGameControlsTilePressed,
                          base::Unretained(this)),
      /*is_togglable=*/true, tile_type, VIEW_ID_GD_CONTROLS_TILE,
      kGdGameControlsIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_GAME_DASHBOARD_CONTROLS_TILE_BUTTON_TITLE),
      /*sub_label=*/std::nullopt));
  UpdateGameControlsTile();

  // Call `SetSubLabelVisibility` after the sub-label is set.
  game_controls_tile_->SetSubLabelVisibility(true);
}

void GameDashboardMainMenuView::AddRecordGameTile(
    views::View* container,
    FeatureTile::TileType tile_type) {
  DCHECK(base::FeatureList::IsEnabled(
      features::kFeatureManagementGameDashboardRecordGame));

  record_game_tile_ = container->AddChildView(CreateFeatureTile(
      base::BindRepeating(&GameDashboardMainMenuView::OnRecordGameTilePressed,
                          base::Unretained(this)),
      /*is_togglable=*/true, tile_type, VIEW_ID_GD_RECORD_GAME_TILE,
      kGdRecordGameIcon,
      l10n_util::GetStringUTF16(
          IDS_ASH_GAME_DASHBOARD_RECORD_GAME_TILE_BUTTON_TITLE),
      /*sub_label=*/std::nullopt));
  // Set toggled background color.
  record_game_tile_->SetBackgroundToggledColorId(
      cros_tokens::kCrosSysSystemNegativeContainer);

  // Set the label's foreground toggled colors.
  record_game_tile_->SetForegroundToggledColorId(
      cros_tokens::kCrosSysSystemOnNegativeContainer);
  // Set the sub-label's foreground toggled colors.
  record_game_tile_->SetForegroundOptionalToggledColorId(
      cros_tokens::kCrosSysSystemOnNegativeContainer);

  // Set toggled ink drop color.
  record_game_tile_->SetInkDropToggledBaseColorId(
      cros_tokens::kCrosSysRippleNeutralOnProminent);
  UpdateRecordGameTile(
      GameDashboardController::Get()->active_recording_context() == context_);
}

void GameDashboardMainMenuView::AddGameControlsDetailsRow(
    views::View* container,
    const gfx::RoundedCornersF& row_corners) {
  DCHECK(IsArcWindow(context_->game_window()));
  game_controls_details_ = container->AddChildView(
      std::make_unique<GameControlsDetailsRow>(this, row_corners));
}

void GameDashboardMainMenuView::AddScreenSizeSettingsRow(
    views::View* container) {
  aura::Window* game_window = context_->game_window();
  DCHECK(IsArcWindow(game_window));
  screen_size_row_ = container->AddChildView(std::make_unique<ScreenSizeRow>(
      this,
      base::BindRepeating(
          &GameDashboardMainMenuView::OnScreenSizeSettingsButtonPressed,
          base::Unretained(this)),
      /*resize_mode=*/compat_mode_util::PredictCurrentMode(game_window),
      /*resize_lock_type=*/game_window->GetProperty(kArcResizeLockTypeKey)));
}

void GameDashboardMainMenuView::AddUtilityFeatureViews(views::View* container) {
  // Add clock view.
  clock_view_ = container->AddChildView(std::make_unique<TimeView>(
      TimeView::ClockLayout::HORIZONTAL_CLOCK,
      Shell::Get()->system_tray_model()->clock(), TimeView::kTime));
  clock_view_->SetAmPmClockType(base::AmPmClockType::kKeepAmPm);
  clock_view_->SetProperty(views::kMarginsKey, kClockViewPadding);
}

void GameDashboardMainMenuView::AddUtilityClusterRow() {
  DCHECK(main_menu_container_);
  auto* container =
      main_menu_container_->AddChildView(std::make_unique<views::View>());
  auto* layout = container->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kHorizontal,
      /*inside_border_insets=*/gfx::Insets(),
      /*between_child_spacing=*/16));

  auto* feedback_button =
      container->AddChildView(std::make_unique<ash::PillButton>(
          base::BindRepeating(
              &GameDashboardMainMenuView::OnFeedbackButtonPressed,
              base::Unretained(this)),
          l10n_util::GetStringUTF16(
              IDS_ASH_GAME_DASHBOARD_SEND_FEEDBACK_TITLE)));
  feedback_button->SetID(VIEW_ID_GD_FEEDBACK_BUTTON);

  // `feedback_button` should be left aligned. Help button and setting button
  // should be right aligned. So add an empty view to fill the empty space.
  auto* empty_view = container->AddChildView(std::make_unique<views::View>());
  layout->SetFlexForView(empty_view, /*flex=*/1);

  if (features::AreGameDashboardUtilitiesEnabled()) {
    AddUtilityFeatureViews(
        container->AddChildView(std::make_unique<views::BoxLayoutView>()));
  }

  auto* help_button = container->AddChildView(CreateIconButton(
      base::BindRepeating(&GameDashboardMainMenuView::OnHelpButtonPressed,
                          base::Unretained(this)),
      VIEW_ID_GD_HELP_BUTTON, kGdHelpIcon,
      l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_HELP_TOOLTIP)));
  help_button->GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_HELP_BUTTON_A11Y_LABEL));
  container->AddChildView(CreateIconButton(
      base::BindRepeating(&GameDashboardMainMenuView::OnSettingsButtonPressed,
                          base::Unretained(this)),
      VIEW_ID_GD_GENERAL_SETTINGS_BUTTON, kGdSettingsIcon,
      l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_SETTINGS_TOOLTIP)));
}

void GameDashboardMainMenuView::VisibilityChanged(views::View* starting_from,
                                                  bool is_visible) {
  // When the menu shows up, Game Controls shouldn't rewrite events. So Game
  // Controls needs to know when the menu is open or closed.
  auto flags =
      game_dashboard_utils::GetGameControlsFlag(context_->game_window());
  if (!flags || !game_dashboard_utils::IsFlagSet(
                    *flags, ArcGameControlsFlag::kAvailable)) {
    return;
  }

  context_->game_window()->SetProperty(
      kArcGameControlsFlagsKey,
      game_dashboard_utils::UpdateFlag(*flags, ArcGameControlsFlag::kMenu,
                                       /*enable_flag=*/is_visible));
}

void GameDashboardMainMenuView::UpdateRecordGameTile(
    bool is_recording_game_window) {
  if (!record_game_tile_) {
    return;
  }

  record_game_tile_->SetEnabled(
      is_recording_game_window ||
      CaptureModeController::Get()->can_start_new_recording());

  record_game_tile_->SetVectorIcon(is_recording_game_window
                                       ? kCaptureModeCircleStopIcon
                                       : kGdRecordGameIcon);
  record_game_tile_->SetLabel(l10n_util::GetStringUTF16(
      is_recording_game_window
          ? IDS_ASH_GAME_DASHBOARD_RECORD_GAME_TILE_BUTTON_RECORDING_TITLE
          : IDS_ASH_GAME_DASHBOARD_RECORD_GAME_TILE_BUTTON_TITLE));
  if (is_recording_game_window) {
    record_game_tile_->SetSubLabel(context_->GetRecordingDuration());
  }
  record_game_tile_->SetSubLabelVisibility(is_recording_game_window);
  record_game_tile_->SetToggled(is_recording_game_window);
  record_game_tile_->SetTooltipText(l10n_util::GetStringUTF16(
      record_game_tile_->IsToggled()
          ? IDS_ASH_GAME_DASHBOARD_RECORD_GAME_TILE_TOOLTIPS_RECORD_STOP
          : IDS_ASH_GAME_DASHBOARD_RECORD_GAME_TILE_TOOLTIPS_RECORD_START));
}

void GameDashboardMainMenuView::AddSettingsViews() {
  DCHECK(!settings_view_container_);
  settings_view_container_ =
      AddChildView(std::make_unique<views::BoxLayoutView>());
  settings_view_container_->SetOrientation(
      views::BoxLayout::Orientation::kVertical);
  settings_view_container_->SetBetweenChildSpacing(16);

  AddSettingsTitleRow();
  AddWelcomeDialogSettingsRow();
}

void GameDashboardMainMenuView::AddSettingsTitleRow() {
  auto* title_container = settings_view_container_->AddChildView(
      std::make_unique<views::BoxLayoutView>());
  title_container->SetOrientation(views::BoxLayout::Orientation::kHorizontal);
  title_container->SetInsideBorderInsets(
      gfx::Insets::TLBR(0, 0, 0, /*padding to offset back button size=*/32));

  // Add back button to the title container.
  settings_view_back_button_ =
      title_container->AddChildView(std::make_unique<IconButton>(
          base::BindRepeating(
              &GameDashboardMainMenuView::OnSettingsBackButtonPressed,
              base::Unretained(this)),
          IconButton::Type::kMedium, &kQuickSettingsLeftArrowIcon,
          IDS_ASH_GAME_DASHBOARD_BACK_TOOLTIP));

  // Add title label to the title container.
  auto* title = title_container->AddChildView(bubble_utils::CreateLabel(
      TypographyToken::kCrosTitle1,
      l10n_util::GetStringUTF16(IDS_ASH_GAME_DASHBOARD_SETTINGS_TITLE),
      cros_tokens::kCrosSysOnSurface));
  title->SetMultiLine(true);
  title->SetHorizontalAlignment(gfx::ALIGN_CENTER);
  // Flex `title` to fill empty space in row.
  title_container->SetFlexForView(title, /*flex=*/1);
}

void GameDashboardMainMenuView::AddWelcomeDialogSettingsRow() {
  auto* welcome_settings_container = settings_view_container_->AddChildView(
      std::make_unique<views::BoxLayoutView>());
  welcome_settings_container->SetOrientation(
      views::BoxLayout::Orientation::kHorizontal);
  welcome_settings_container->SetInsideBorderInsets(gfx::Insets::VH(16, 16));
  welcome_settings_container->SetBackground(
      views::CreateThemedRoundedRectBackground(
          cros_tokens::kCrosSysSystemOnBase, kBubbleCornerRadius));

  // Add icon.
  auto* icon_container = welcome_settings_container->AddChildView(
      std::make_unique<views::FlexLayoutView>());
  icon_container->SetBackground(views::CreateThemedRoundedRectBackground(
      cros_tokens::kCrosSysSystemOnBase,
      /*radius=*/12.0f));
  icon_container->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(6, 6)));
  icon_container->SetProperty(views::kMarginsKey,
                              gfx::Insets::TLBR(0, 0, 0, 16));
  icon_container->AddChildView(
      std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
          kGdNotificationIcon, cros_tokens::kCrosSysOnSurface,
          /*icon_size=*/20)));

  // Add title.
  auto* feature_title = welcome_settings_container->AddChildView(
      std::make_unique<views::Label>(l10n_util::GetStringUTF16(
          IDS_ASH_GAME_DASHBOARD_SETTINGS_WELCOME_DIALOG_TITLE)));
  feature_title->SetAutoColorReadabilityEnabled(false);
  feature_title->SetEnabledColorId(cros_tokens::kCrosSysOnSurface);
  feature_title->SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
      TypographyToken::kCrosTitle2));
  feature_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  feature_title->SetMultiLine(true);
  feature_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  // Flex `feature_title` to fill empty space in row.
  welcome_settings_container->SetFlexForView(feature_title, /*flex=*/1);

  // Add welcome dialog switch.
  welcome_dialog_settings_switch_ = welcome_settings_container->AddChildView(
      std::make_unique<Switch>(base::BindRepeating(
          &GameDashboardMainMenuView::OnWelcomeDialogSwitchPressed,
          base::Unretained(this))));
  const bool is_enabled = game_dashboard_utils::ShouldShowWelcomeDialog();
  OnWelcomeDialogSwitchStateChanged(is_enabled);
  welcome_dialog_settings_switch_->SetProperty(views::kMarginsKey,
                                               gfx::Insets::TLBR(0, 8, 0, 0));
  welcome_dialog_settings_switch_->SetIsOn(is_enabled);
}

void GameDashboardMainMenuView::OnWelcomeDialogSwitchStateChanged(
    bool is_enabled) {
  welcome_dialog_settings_switch_->GetViewAccessibility().SetName(
      l10n_util::GetStringFUTF16(
          IDS_ASH_GAME_DASHBOARD_SETTINGS_WELCOME_DIALOG_A11Y_LABEL,
          l10n_util::GetStringUTF16(is_enabled
                                        ? IDS_ASH_GAME_DASHBOARD_TILE_ON
                                        : IDS_ASH_GAME_DASHBOARD_GC_TILE_OFF)));
}

PillButton* GameDashboardMainMenuView::GetGameControlsSetupButton() {
  return game_controls_details_ ? game_controls_details_->setup_button()
                                : nullptr;
}

Switch* GameDashboardMainMenuView::GetGameControlsFeatureSwitch() {
  return game_controls_details_ ? game_controls_details_->feature_switch()
                                : nullptr;
}

AnchoredNudge*
GameDashboardMainMenuView::GetGameControlsSetupNudgeForTesting() {
  if (Shell::Get()->anchored_nudge_manager()->IsNudgeShown(kSetupNudgeId)) {
    return Shell::Get()
        ->anchored_nudge_manager()
        ->GetShownNudgeForTest(  // IN-TEST
            kSetupNudgeId);
  }
  return nullptr;
}

const views::Label* GameDashboardMainMenuView::GetScreenSizeRowSubtitle() {
  return screen_size_row_ ? screen_size_row_->feature_header()->GetSubtitle()
                          : nullptr;
}

void GameDashboardMainMenuView::OnThemeChanged() {
  views::View::OnThemeChanged();
  set_color(GetColorProvider()->GetColor(
      cros_tokens::kCrosSysSystemBaseElevatedOpaque));
}

BEGIN_METADATA(GameDashboardMainMenuView)
END_METADATA

}  // namespace ash