0

gd: Add Welcome Dialog

Adds support to display a welcome dialog for 4 seconds on every game
window opened.

Bug: b:304817405
Test: Added unit tests
Demo: b/304817405#comment9
Change-Id: I1b9dfb3278f9f5dad1a4e7af0423edbc09b73523
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5106463
Commit-Queue: Gina Domergue <gdomergue@google.com>
Reviewed-by: Prameet Shah <phshah@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1250993}
This commit is contained in:
Gina Domergue
2024-01-23 19:55:22 +00:00
committed by Chromium LUCI CQ
parent 16673b7e34
commit 51e76e01a2
13 changed files with 524 additions and 54 deletions

@ -659,6 +659,7 @@ component("ash") {
"frame_throttler/frame_throttling_observer.h", "frame_throttler/frame_throttling_observer.h",
"game_dashboard/game_dashboard_button.cc", "game_dashboard/game_dashboard_button.cc",
"game_dashboard/game_dashboard_button.h", "game_dashboard/game_dashboard_button.h",
"game_dashboard/game_dashboard_constants.h",
"game_dashboard/game_dashboard_context.cc", "game_dashboard/game_dashboard_context.cc",
"game_dashboard/game_dashboard_context.h", "game_dashboard/game_dashboard_context.h",
"game_dashboard/game_dashboard_controller.cc", "game_dashboard/game_dashboard_controller.cc",
@ -670,6 +671,8 @@ component("ash") {
"game_dashboard/game_dashboard_toolbar_view.h", "game_dashboard/game_dashboard_toolbar_view.h",
"game_dashboard/game_dashboard_utils.cc", "game_dashboard/game_dashboard_utils.cc",
"game_dashboard/game_dashboard_utils.h", "game_dashboard/game_dashboard_utils.h",
"game_dashboard/game_dashboard_welcome_dialog.cc",
"game_dashboard/game_dashboard_welcome_dialog.h",
"game_dashboard/game_dashboard_widget.cc", "game_dashboard/game_dashboard_widget.cc",
"game_dashboard/game_dashboard_widget.h", "game_dashboard/game_dashboard_widget.h",
"glanceables/classroom/glanceables_classroom_client.h", "glanceables/classroom/glanceables_classroom_client.h",

@ -7315,6 +7315,12 @@ To shut down the device, press and hold the power button on the device again.
<message name="IDS_ASH_GAME_DASHBOARD_VISIBLE_STATUS" translateable="false" desc="The visible state for compact Game Dashboard tile sub-labels."> <message name="IDS_ASH_GAME_DASHBOARD_VISIBLE_STATUS" translateable="false" desc="The visible state for compact Game Dashboard tile sub-labels.">
Visible Visible
</message> </message>
<message name="IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SHORTCUT" translateable="false" desc="The text displayed in the welcome dialog to show how to toggle the Game Dashboard.">
Press <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> + g at anytime
</message>
<message name="IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SUB_LABEL" translateable="false" desc="The additional context added to the welcome dialog about the Game Dashboard.">
Customize your gaming experience
</message>
<!-- Game Dashboard / Game Controls strings --> <!-- Game Dashboard / Game Controls strings -->
<message name="IDS_ASH_GAME_DASHBOARD_GC_TILE_VISIBLE" desc="The sub-label for Game Controls tile when input mapping hint is visible."> <message name="IDS_ASH_GAME_DASHBOARD_GC_TILE_VISIBLE" desc="The sub-label for Game Controls tile when input mapping hint is visible.">

@ -0,0 +1 @@
35df49495339e436b0a61bcf783e089300ac7ddf

@ -0,0 +1 @@
8801a2612e169a159f77f7aee64e0b1f8218d08c

@ -757,6 +757,9 @@ TEST_F(GameDashboardCaptureModeTest, CursorAndClickBehaviorWhenAnchored) {
// The game window should be the top most active window. // The game window should be the top most active window.
wm::ActivateWindow(game_window()); wm::ActivateWindow(game_window());
// TODO(b/316141148): Remove this call once the welcome dialog is disabled by
// default for tests.
WaitForSeconds(/*seconds=*/4);
auto* controller = StartGameCaptureModeSession(); auto* controller = StartGameCaptureModeSession();
// Hover over empty space where there is no window. // Hover over empty space where there is no window.

@ -0,0 +1,22 @@
// 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.
#ifndef ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONSTANTS_H_
#define ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONSTANTS_H_
namespace ash::game_dashboard {
// Toolbar padding from the border of the game window.
inline constexpr int kToolbarEdgePadding = 10;
// Interior margin padding around the game window for the
// `GameDashboardWelcomeDialog`.
inline constexpr int kWelcomeDialogEdgePadding = 8;
// Welcome dialog fixed width.
inline constexpr int kWelcomeDialogFixedWidth = 360;
} // namespace ash::game_dashboard
#endif // ASH_GAME_DASHBOARD_GAME_DASHBOARD_CONSTANTS_H_

@ -8,10 +8,12 @@
#include <string> #include <string>
#include "ash/game_dashboard/game_dashboard_button.h" #include "ash/game_dashboard/game_dashboard_button.h"
#include "ash/game_dashboard/game_dashboard_constants.h"
#include "ash/game_dashboard/game_dashboard_controller.h" #include "ash/game_dashboard/game_dashboard_controller.h"
#include "ash/game_dashboard/game_dashboard_main_menu_view.h" #include "ash/game_dashboard/game_dashboard_main_menu_view.h"
#include "ash/game_dashboard/game_dashboard_toolbar_view.h" #include "ash/game_dashboard/game_dashboard_toolbar_view.h"
#include "ash/game_dashboard/game_dashboard_utils.h" #include "ash/game_dashboard/game_dashboard_utils.h"
#include "ash/game_dashboard/game_dashboard_welcome_dialog.h"
#include "ash/game_dashboard/game_dashboard_widget.h" #include "ash/game_dashboard/game_dashboard_widget.h"
#include "ash/public/cpp/app_types_util.h" #include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/arc_game_controls_flag.h" #include "ash/public/cpp/arc_game_controls_flag.h"
@ -24,6 +26,7 @@
#include "ui/base/l10n/time_format.h" #include "ui/base/l10n/time_format.h"
#include "ui/compositor/layer.h" #include "ui/compositor/layer.h"
#include "ui/events/types/event_type.h" #include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/transform.h" #include "ui/gfx/geometry/transform.h"
#include "ui/views/animation/animation_builder.h" #include "ui/views/animation/animation_builder.h"
@ -39,13 +42,15 @@ constexpr base::TimeDelta kCountUpTimerRefreshInterval = base::Seconds(1);
// Number of pixels to add to the top and bottom of the Game Dashboard button so // Number of pixels to add to the top and bottom of the Game Dashboard button so
// that it's centered within the frame header. // that it's centered within the frame header.
static const int kGameDashboardButtonVerticalPaddingDp = 3; constexpr int kGameDashboardButtonVerticalPaddingDp = 3;
// Toolbar padding from the border of the game window. // Maximum width of the game window that centers the welcome dialog in the
static const int kToolbarEdgePadding = 10; // window instead of right aligned.
constexpr int kMaxCenteredWelcomeDialogWidth =
1.5 * game_dashboard::kWelcomeDialogFixedWidth;
// The animation duration for the bounds change operation on the toolbar widget. // The animation duration for the bounds change operation on the toolbar widget.
static constexpr base::TimeDelta kToolbarBoundsChangeAnimationDuration = constexpr base::TimeDelta kToolbarBoundsChangeAnimationDuration =
base::Milliseconds(150); base::Milliseconds(150);
std::unique_ptr<GameDashboardWidget> CreateTransientChildWidget( std::unique_ptr<GameDashboardWidget> CreateTransientChildWidget(
@ -63,6 +68,7 @@ std::unique_ptr<GameDashboardWidget> CreateTransientChildWidget(
params.parent = game_window; params.parent = game_window;
params.name = widget_name; params.name = widget_name;
params.activatable = activatable; params.activatable = activatable;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
auto widget = std::make_unique<GameDashboardWidget>(); auto widget = std::make_unique<GameDashboardWidget>();
widget->Init(std::move(params)); widget->Init(std::move(params));
@ -80,7 +86,15 @@ GameDashboardContext::GameDashboardContext(aura::Window* game_window)
: game_window_(game_window), : game_window_(game_window),
toolbar_snap_location_(ToolbarSnapLocation::kTopRight) { toolbar_snap_location_(ToolbarSnapLocation::kTopRight) {
DCHECK(game_window_); DCHECK(game_window_);
// TODO(b/316141148): Update `show_welcome_dialog_` to reflect the welcome
// dialog state in the settings.
show_welcome_dialog_ = true;
CreateAndAddGameDashboardButtonWidget(); CreateAndAddGameDashboardButtonWidget();
// ARC windows handle displaying the welcome dialog once the
// `game_dashboard_button_` becomes available.
if (!IsArcWindow(game_window_)) {
MaybeShowWelcomeDialog();
}
} }
GameDashboardContext::~GameDashboardContext() { GameDashboardContext::~GameDashboardContext() {
@ -88,6 +102,7 @@ GameDashboardContext::~GameDashboardContext() {
if (main_menu_widget_) { if (main_menu_widget_) {
main_menu_widget_->CloseNow(); main_menu_widget_->CloseNow();
} }
CloseWelcomeDialog();
} }
void GameDashboardContext::SetToolbarSnapLocation( void GameDashboardContext::SetToolbarSnapLocation(
@ -99,13 +114,20 @@ void GameDashboardContext::SetToolbarSnapLocation(
void GameDashboardContext::OnWindowBoundsChanged() { void GameDashboardContext::OnWindowBoundsChanged() {
UpdateGameDashboardButtonWidgetBounds(); UpdateGameDashboardButtonWidgetBounds();
MaybeUpdateToolbarWidgetBounds(); MaybeUpdateToolbarWidgetBounds();
MaybeUpdateWelcomeDialogBounds();
} }
void GameDashboardContext::UpdateForGameControlsFlags() { void GameDashboardContext::UpdateForGameControlsFlags() {
CHECK(IsArcWindow(game_window_)); CHECK(IsArcWindow(game_window_));
game_dashboard_button_->SetEnabled( const bool should_enable_button =
game_dashboard_utils::ShouldEnableGameDashboardButton(game_window_)); game_dashboard_utils::ShouldEnableGameDashboardButton(game_window_);
game_dashboard_button_->SetEnabled(should_enable_button);
if (should_enable_button) {
// ARC windows handle displaying the welcome dialog once the
// `game_dashboard_button_` becomes available.
MaybeShowWelcomeDialog();
}
if (toolbar_view_) { if (toolbar_view_) {
toolbar_view_->UpdateViewForGameControls( toolbar_view_->UpdateViewForGameControls(
@ -227,6 +249,7 @@ void GameDashboardContext::OnViewPreferredSizeChanged(
views::View* observed_view) { views::View* observed_view) {
CHECK_EQ(game_dashboard_button_, observed_view); CHECK_EQ(game_dashboard_button_, observed_view);
UpdateGameDashboardButtonWidgetBounds(); UpdateGameDashboardButtonWidgetBounds();
MaybeUpdateWelcomeDialogBounds();
} }
void GameDashboardContext::OnWidgetDestroying(views::Widget* widget) { void GameDashboardContext::OnWidgetDestroying(views::Widget* widget) {
@ -278,47 +301,105 @@ void GameDashboardContext::UpdateGameDashboardButtonWidgetBounds() {
void GameDashboardContext::OnGameDashboardButtonPressed() { void GameDashboardContext::OnGameDashboardButtonPressed() {
// TODO(b/273640775): Add metrics to know when the Game Dashboard button was // TODO(b/273640775): Add metrics to know when the Game Dashboard button was
// physically pressed. // physically pressed.
// Close the welcome dialog if it's open when a user opens the main menu view.
CloseWelcomeDialog();
ToggleMainMenu(); ToggleMainMenu();
} }
void GameDashboardContext::MaybeShowWelcomeDialog() {
if (!show_welcome_dialog_) {
return;
}
DCHECK(!welcome_dialog_widget_);
show_welcome_dialog_ = false;
auto view = std::make_unique<GameDashboardWelcomeDialog>();
GameDashboardWelcomeDialog* welcome_dialog_view = view.get();
welcome_dialog_widget_ = CreateTransientChildWidget(
game_window_, "GameDashboardWelcomeDialog", std::move(view),
/*activatable=*/views::Widget::InitParams::Activatable::kNo);
welcome_dialog_widget_->AddObserver(this);
MaybeUpdateWelcomeDialogBounds();
welcome_dialog_widget_->Show();
welcome_dialog_view->StartTimer(base::BindRepeating(
&GameDashboardContext::CloseWelcomeDialog, base::Unretained(this)));
}
void GameDashboardContext::MaybeUpdateWelcomeDialogBounds() {
if (!welcome_dialog_widget_) {
return;
}
const gfx::Rect game_bounds = game_window_->GetBoundsInScreen();
const gfx::Size preferred_size =
welcome_dialog_widget_->GetContentsView()->GetPreferredSize();
const int frame_header_height = GetFrameHeaderHeight();
int origin_x;
if (game_bounds.width() > kMaxCenteredWelcomeDialogWidth) {
// Place welcome dialog right aligned in the game window.
origin_x = game_bounds.right() - game_dashboard::kWelcomeDialogEdgePadding -
preferred_size.width();
} else {
// Place welcome dialog centered in the game window.
origin_x =
game_bounds.x() + (game_bounds.width() - preferred_size.width()) / 2;
}
welcome_dialog_widget_->SetBounds(gfx::Rect(
gfx::Point(origin_x, game_bounds.y() +
game_dashboard::kWelcomeDialogEdgePadding +
frame_header_height),
preferred_size));
}
const gfx::Rect GameDashboardContext::CalculateToolbarWidgetBounds() { const gfx::Rect GameDashboardContext::CalculateToolbarWidgetBounds() {
const gfx::Rect game_bounds = game_window_->GetBoundsInScreen(); const gfx::Rect game_bounds = game_window_->GetBoundsInScreen();
const gfx::Size preferred_size = const gfx::Size preferred_size =
toolbar_widget_->GetContentsView()->GetPreferredSize(); toolbar_widget_->GetContentsView()->GetPreferredSize();
auto* frame_header = chromeos::FrameHeader::Get( const int frame_header_height = GetFrameHeaderHeight();
views::Widget::GetWidgetForNativeWindow(game_window_));
const int frame_header_height =
(frame_header && frame_header->view()->GetVisible())
? frame_header->GetHeaderHeight()
: 0;
gfx::Point origin; gfx::Point origin;
switch (toolbar_snap_location_) { switch (toolbar_snap_location_) {
case ToolbarSnapLocation::kTopRight: case ToolbarSnapLocation::kTopRight:
origin = gfx::Point( origin =
game_bounds.right() - kToolbarEdgePadding - preferred_size.width(), gfx::Point(game_bounds.right() - game_dashboard::kToolbarEdgePadding -
game_bounds.y() + kToolbarEdgePadding + frame_header_height); preferred_size.width(),
game_bounds.y() + game_dashboard::kToolbarEdgePadding +
frame_header_height);
break; break;
case ToolbarSnapLocation::kTopLeft: case ToolbarSnapLocation::kTopLeft:
origin = gfx::Point( origin =
game_bounds.x() + kToolbarEdgePadding, gfx::Point(game_bounds.x() + game_dashboard::kToolbarEdgePadding,
game_bounds.y() + kToolbarEdgePadding + frame_header_height); game_bounds.y() + game_dashboard::kToolbarEdgePadding +
frame_header_height);
break; break;
case ToolbarSnapLocation::kBottomRight: case ToolbarSnapLocation::kBottomRight:
origin = gfx::Point( origin = gfx::Point(
game_bounds.right() - kToolbarEdgePadding - preferred_size.width(), game_bounds.right() - game_dashboard::kToolbarEdgePadding -
game_bounds.bottom() - kToolbarEdgePadding - preferred_size.height()); preferred_size.width(),
game_bounds.bottom() - game_dashboard::kToolbarEdgePadding -
preferred_size.height());
break; break;
case ToolbarSnapLocation::kBottomLeft: case ToolbarSnapLocation::kBottomLeft:
origin = gfx::Point( origin = gfx::Point(game_bounds.x() + game_dashboard::kToolbarEdgePadding,
game_bounds.x() + kToolbarEdgePadding, game_bounds.bottom() -
game_bounds.bottom() - kToolbarEdgePadding - preferred_size.height()); game_dashboard::kToolbarEdgePadding -
preferred_size.height());
break; break;
} }
return gfx::Rect(origin, preferred_size); return gfx::Rect(origin, preferred_size);
} }
int GameDashboardContext::GetFrameHeaderHeight() const {
auto* frame_header = chromeos::FrameHeader::Get(
views::Widget::GetWidgetForNativeWindow(game_window_));
return (frame_header && frame_header->view()->GetVisible())
? frame_header->GetHeaderHeight()
: 0;
}
void GameDashboardContext::AnimateToolbarWidgetBoundsChange( void GameDashboardContext::AnimateToolbarWidgetBoundsChange(
const gfx::Rect& target_screen_bounds) { const gfx::Rect& target_screen_bounds) {
DCHECK(toolbar_widget_); DCHECK(toolbar_widget_);
@ -362,4 +443,11 @@ void GameDashboardContext::OnUpdateRecordingTimer() {
} }
} }
void GameDashboardContext::CloseWelcomeDialog() {
if (welcome_dialog_widget_) {
welcome_dialog_widget_->RemoveObserver(this);
welcome_dialog_widget_.reset();
}
}
} // namespace ash } // namespace ash

@ -107,6 +107,12 @@ class ASH_EXPORT GameDashboardContext : public views::ViewObserver,
// views::WidgetObserver: // views::WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override; void OnWidgetDestroying(views::Widget* widget) override;
// TODO(b/316141148): Remove this test function once it's possible to set
// `show_welcome_dialog_` via a property.
void SetShowWelcomeDialogForTesting(bool show_dialog) {
show_welcome_dialog_ = show_dialog;
}
private: private:
friend class GameDashboardContextTestApi; friend class GameDashboardContextTestApi;
@ -121,10 +127,21 @@ class ASH_EXPORT GameDashboardContext : public views::ViewObserver,
// Called when `GameDashboardButton` is pressed, and toggles the main menu. // Called when `GameDashboardButton` is pressed, and toggles the main menu.
void OnGameDashboardButtonPressed(); void OnGameDashboardButtonPressed();
// Shows the Game Dashboard welcome dialog, if it's enabled in the Game
// Dashboard settings.
void MaybeShowWelcomeDialog();
// Updates the Game Dashboard welcome dialog's bounds and location, relative
// to the `game_window_`.
void MaybeUpdateWelcomeDialogBounds();
// Determines the toolbar's physical location on screen based on the // Determines the toolbar's physical location on screen based on the
// `toolbar_snap_location_` value. // `toolbar_snap_location_` value.
const gfx::Rect CalculateToolbarWidgetBounds(); const gfx::Rect CalculateToolbarWidgetBounds();
// Calculates the height of the app's frame header.
int GetFrameHeaderHeight() const;
// Updates the toolbar widget's bounds and location utilizing an animation as // Updates the toolbar widget's bounds and location utilizing an animation as
// it transfers from the previous location. // it transfers from the previous location.
void AnimateToolbarWidgetBoundsChange(const gfx::Rect& target_screen_bounds); void AnimateToolbarWidgetBoundsChange(const gfx::Rect& target_screen_bounds);
@ -133,6 +150,10 @@ class ASH_EXPORT GameDashboardContext : public views::ViewObserver,
// recording session duration. // recording session duration.
void OnUpdateRecordingTimer(); void OnUpdateRecordingTimer();
// Closes and deletes the Game Dashboard welcome dialog once it's no longer
// needed.
void CloseWelcomeDialog();
const raw_ptr<aura::Window> game_window_; const raw_ptr<aura::Window> game_window_;
// Game Dashboard button widget for the Game Dashboard. // Game Dashboard button widget for the Game Dashboard.
@ -144,6 +165,9 @@ class ASH_EXPORT GameDashboardContext : public views::ViewObserver,
// The toolbar for the Game Dashboard. // The toolbar for the Game Dashboard.
std::unique_ptr<GameDashboardWidget> toolbar_widget_; std::unique_ptr<GameDashboardWidget> toolbar_widget_;
// The dialog displayed when the game window first opens.
std::unique_ptr<GameDashboardWidget> welcome_dialog_widget_;
// The indicator of the current corner that the toolbar is placed. // The indicator of the current corner that the toolbar is placed.
ToolbarSnapLocation toolbar_snap_location_; ToolbarSnapLocation toolbar_snap_location_;
@ -170,6 +194,11 @@ class ASH_EXPORT GameDashboardContext : public views::ViewObserver,
// Duration since `recording_timer_` started. // Duration since `recording_timer_` started.
std::u16string recording_duration_; std::u16string recording_duration_;
// Indicates whether the Game Dashboard welcome dialog should be shown. This
// param ensures the welcome dialog is only shown once per game window
// startup.
bool show_welcome_dialog_ = false;
base::WeakPtrFactory<GameDashboardContext> weak_ptr_factory_{this}; base::WeakPtrFactory<GameDashboardContext> weak_ptr_factory_{this};
}; };

@ -137,6 +137,10 @@ AnchoredNudge* GameDashboardContextTestApi::GetGameControlsSetupNudge() {
return nullptr; return nullptr;
} }
views::Widget* GameDashboardContextTestApi::GetWelcomeDialogWidget() {
return context_->welcome_dialog_widget_.get();
}
void GameDashboardContextTestApi::OpenTheMainMenu() { void GameDashboardContextTestApi::OpenTheMainMenu() {
ASSERT_FALSE(GetMainMenuView()) << "The main menu view is already open."; ASSERT_FALSE(GetMainMenuView()) << "The main menu view is already open.";
ASSERT_FALSE(GetMainMenuWidget()) << "The main menu widget is already open."; ASSERT_FALSE(GetMainMenuWidget()) << "The main menu widget is already open.";

@ -75,6 +75,9 @@ class GameDashboardContextTestApi {
// Returns the Game Controls setup nudge. // Returns the Game Controls setup nudge.
AnchoredNudge* GetGameControlsSetupNudge(); AnchoredNudge* GetGameControlsSetupNudge();
// Returns the Game Dashboard welcome dialog widget.
views::Widget* GetWelcomeDialogWidget();
// Opens the main menu. // Opens the main menu.
// Before opening the main menu, verifies that the main menu is closed. // Before opening the main menu, verifies that the main menu is closed.
// After opening the main menu, verifies it opened and waits for the thread to // After opening the main menu, verifies it opened and waits for the thread to

@ -12,6 +12,7 @@
#include "ash/capture_mode/capture_mode_types.h" #include "ash/capture_mode/capture_mode_types.h"
#include "ash/constants/ash_features.h" #include "ash/constants/ash_features.h"
#include "ash/game_dashboard/game_dashboard_button.h" #include "ash/game_dashboard/game_dashboard_button.h"
#include "ash/game_dashboard/game_dashboard_constants.h"
#include "ash/game_dashboard/game_dashboard_context_test_api.h" #include "ash/game_dashboard/game_dashboard_context_test_api.h"
#include "ash/game_dashboard/game_dashboard_controller.h" #include "ash/game_dashboard/game_dashboard_controller.h"
#include "ash/game_dashboard/game_dashboard_main_menu_view.h" #include "ash/game_dashboard/game_dashboard_main_menu_view.h"
@ -51,10 +52,6 @@ namespace ash {
using ToolbarSnapLocation = GameDashboardContext::ToolbarSnapLocation; using ToolbarSnapLocation = GameDashboardContext::ToolbarSnapLocation;
// Toolbar padding copied from `GameDashboardContext`.
static const int kToolbarEdgePadding = 10;
static constexpr gfx::Rect kAppBounds = gfx::Rect(50, 50, 800, 400);
// Sub-label strings. // Sub-label strings.
const std::u16string& hidden_label = u"Hidden"; const std::u16string& hidden_label = u"Hidden";
const std::u16string& visible_label = u"Visible"; const std::u16string& visible_label = u"Visible";
@ -69,9 +66,22 @@ class GameDashboardContextTest : public GameDashboardTestBase {
~GameDashboardContextTest() override = default; ~GameDashboardContextTest() override = default;
void TearDown() override { void TearDown() override {
CloseGameWindow();
GameDashboardTestBase::TearDown();
}
void CloseGameWindow() {
game_window_.reset(); game_window_.reset();
test_api_.reset(); test_api_.reset();
GameDashboardTestBase::TearDown(); }
const gfx::Rect app_bounds() const { return app_bounds_; }
void SetAppBounds(gfx::Rect app_bounds) {
CHECK(!game_window_)
<< "App bounds cannot be changed after creating window. To set the app "
"bounds, call CloseWindow() and re-call this function.";
app_bounds_ = app_bounds;
} }
int GetToolbarHeight() { int GetToolbarHeight() {
@ -94,17 +104,23 @@ class GameDashboardContextTest : public GameDashboardTestBase {
// game window. Otherwise, it creates the window as a GeForceNow window. // game window. Otherwise, it creates the window as a GeForceNow window.
// For ARC game windows, if `set_arc_game_controls_flags_prop` is true, then // For ARC game windows, if `set_arc_game_controls_flags_prop` is true, then
// the `kArcGameControlsFlagsKey` window property will be set to // the `kArcGameControlsFlagsKey` window property will be set to
// `ArcGameControlsFlag::kKnown`, otherwise the property will not be set. // `ArcGameControlsFlag::kKnown`, otherwise the property will not be set. If
// `show_welcome_dialog` is true, the welcome dialog displays when the Game
// Window first opens.
void CreateGameWindow(bool is_arc_window, void CreateGameWindow(bool is_arc_window,
bool set_arc_game_controls_flags_prop = true) { bool set_arc_game_controls_flags_prop = true,
bool show_welcome_dialog = false) {
ASSERT_FALSE(game_window_); ASSERT_FALSE(game_window_);
ASSERT_FALSE(test_api_); ASSERT_FALSE(test_api_);
game_window_ = CreateAppWindow( game_window_ = CreateAppWindow(
(is_arc_window ? TestGameDashboardDelegate::kGameAppId (is_arc_window ? TestGameDashboardDelegate::kGameAppId
: extension_misc::kGeForceNowAppId), : extension_misc::kGeForceNowAppId),
(is_arc_window ? AppType::ARC_APP : AppType::NON_APP), kAppBounds); (is_arc_window ? AppType::ARC_APP : AppType::NON_APP), app_bounds());
auto* context = GameDashboardController::Get()->GetGameDashboardContext( auto* context = GameDashboardController::Get()->GetGameDashboardContext(
game_window_.get()); game_window_.get());
// TODO(b/316141148): Update test logic to set `show_welcome_dialog_` to
// false via a property instead of directly.
context->SetShowWelcomeDialogForTesting(show_welcome_dialog);
ASSERT_TRUE(context); ASSERT_TRUE(context);
test_api_ = std::make_unique<GameDashboardContextTestApi>( test_api_ = std::make_unique<GameDashboardContextTestApi>(
context, GetEventGenerator()); context, GetEventGenerator());
@ -436,6 +452,9 @@ class GameDashboardContextTest : public GameDashboardTestBase {
// completes before proceeding. // completes before proceeding.
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
private:
gfx::Rect app_bounds_ = gfx::Rect(50, 50, 800, 400);
}; };
// Verifies Game Controls tile state. // Verifies Game Controls tile state.
@ -796,6 +815,64 @@ TEST_F(GameDashboardContextTest, RecordingTimerStringFormat) {
EXPECT_EQ(u"24:00:30", test_api_->GetRecordingDuration()); EXPECT_EQ(u"24:00:30", test_api_->GetRecordingDuration());
} }
// Verifies the welcome dialog displays when the game window first opens and
// disappears after 4 seconds.
TEST_F(GameDashboardContextTest, WelcomeDialogAutoDismisses) {
// Open the game window with the welcome dialog enabled.
CreateGameWindow(/*is_arc_window=*/true,
/*set_arc_game_controls_flags_prop=*/true,
/*show_welcome_dialog=*/true);
// Verify the welcome dialog is initially shown and is right aligned in the
// app window.
ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
gfx::Rect welcome_dialog_bounds =
test_api_->GetWelcomeDialogWidget()->GetWindowBoundsInScreen();
EXPECT_EQ(welcome_dialog_bounds.x(),
(game_window_->GetBoundsInScreen().right() -
game_dashboard::kWelcomeDialogEdgePadding -
game_dashboard::kWelcomeDialogFixedWidth));
// Dismiss welcome dialog after 4 seconds and verify the dialog is no longer
// visible.
task_environment()->FastForwardBy(base::Seconds(4));
EXPECT_FALSE(test_api_->GetWelcomeDialogWidget());
}
// Verifies the welcome dialog disappears when the main menu view is opened.
TEST_F(GameDashboardContextTest, WelcomeDialogDismissOnMainMenuOpening) {
// Open the game window with the welcome dialog enabled.
// CloseGameWindow();
CreateGameWindow(/*is_arc_window=*/true,
/*set_arc_game_controls_flags_prop=*/true,
/*show_welcome_dialog=*/true);
ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
// Open the main menu and verify the welcome dialog dismisses.
test_api_->OpenTheMainMenu();
EXPECT_FALSE(test_api_->GetWelcomeDialogWidget());
}
// Verifies the welcome dialog is centered when the app window width is small
// enough.
TEST_F(GameDashboardContextTest, WelcomeDialogWithSmallWindow) {
// Open a new game window with a width of 450.
SetAppBounds(gfx::Rect(50, 50, 450, 400));
CreateGameWindow(/*is_arc_window=*/true,
/*set_arc_game_controls_flags_prop=*/true,
/*show_welcome_dialog=*/true);
ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
// Verify the welcome dialog is centered.
gfx::Rect welcome_dialog_bounds =
test_api_->GetWelcomeDialogWidget()->GetWindowBoundsInScreen();
EXPECT_EQ(welcome_dialog_bounds.x(),
(game_window_->GetBoundsInScreen().x() +
(game_window_->GetBoundsInScreen().width() -
game_dashboard::kWelcomeDialogFixedWidth) /
2));
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// GameTypeGameDashboardContextTest: // GameTypeGameDashboardContextTest:
// Test fixture to test both ARC and GeForceNow game window depending on the // Test fixture to test both ARC and GeForceNow game window depending on the
@ -825,7 +902,7 @@ TEST_P(GameTypeGameDashboardContextTest,
GameDashboardButtonWidget_InitialLocation) { GameDashboardButtonWidget_InitialLocation) {
const gfx::Point expected_button_center_point( const gfx::Point expected_button_center_point(
game_window_->GetBoundsInScreen().top_center().x(), game_window_->GetBoundsInScreen().top_center().x(),
kAppBounds.y() + frame_header_->GetHeaderHeight() / 2); app_bounds().y() + frame_header_->GetHeaderHeight() / 2);
EXPECT_EQ(expected_button_center_point, EXPECT_EQ(expected_button_center_point,
test_api_->GetGameDashboardButtonWidget() test_api_->GetGameDashboardButtonWidget()
->GetNativeWindow() ->GetNativeWindow()
@ -853,8 +930,7 @@ TEST_P(GameTypeGameDashboardContextTest,
TEST_P(GameTypeGameDashboardContextTest, OpenGameDashboardButtonWidget) { TEST_P(GameTypeGameDashboardContextTest, OpenGameDashboardButtonWidget) {
// Close the window and create a new game window without setting the // Close the window and create a new game window without setting the
// `kArcGameControlsFlagsKey` property. // `kArcGameControlsFlagsKey` property.
game_window_.reset(); CloseGameWindow();
test_api_.reset();
CreateGameWindow(IsArcGame(), /*set_arc_game_controls_flags_prop=*/false); CreateGameWindow(IsArcGame(), /*set_arc_game_controls_flags_prop=*/false);
// Verifies the main menu is closed. // Verifies the main menu is closed.
@ -913,8 +989,9 @@ TEST_P(GameTypeGameDashboardContextTest, CloseMainMenuOutsideButtonWidget) {
// Close the main menu dialog by clicking outside the main menu view bounds. // Close the main menu dialog by clicking outside the main menu view bounds.
auto* event_generator = GetEventGenerator(); auto* event_generator = GetEventGenerator();
const gfx::Point& new_location = {kAppBounds.x() + kAppBounds.width(), gfx::Rect game_bounds = app_bounds();
kAppBounds.y() + kAppBounds.height()}; const gfx::Point& new_location = {game_bounds.x() + game_bounds.width(),
game_bounds.y() + game_bounds.height()};
event_generator->set_current_screen_location(new_location); event_generator->set_current_screen_location(new_location);
event_generator->ClickLeftButton(); event_generator->ClickLeftButton();
@ -1185,10 +1262,11 @@ TEST_P(GameTypeGameDashboardContextTest, MoveToolbarOutOfBounds) {
const int screen_point_bottom = screen_point_y + kScreenBounds.height(); const int screen_point_bottom = screen_point_y + kScreenBounds.height();
// Verify the screen bounds are larger than the game bounds. // Verify the screen bounds are larger than the game bounds.
ASSERT_LT(screen_point_x, kAppBounds.x()); auto game_bounds = app_bounds();
ASSERT_LT(screen_point_y, kAppBounds.y()); ASSERT_LT(screen_point_x, game_bounds.x());
ASSERT_GT(screen_point_right, kAppBounds.x() + kAppBounds.width()); ASSERT_LT(screen_point_y, game_bounds.y());
ASSERT_GT(screen_point_bottom, kAppBounds.y() + kAppBounds.height()); ASSERT_GT(screen_point_right, game_bounds.x() + game_bounds.width());
ASSERT_GT(screen_point_bottom, game_bounds.y() + game_bounds.height());
// Drag toolbar, moving the mouse past the game window to the top right corner // Drag toolbar, moving the mouse past the game window to the top right corner
// of the screen bounds, and verify the toolbar doesn't go past the game // of the screen bounds, and verify the toolbar doesn't go past the game
@ -1296,6 +1374,7 @@ TEST_P(GameTypeGameDashboardContextTest, VerifyToolbarPlacementInQuadrants) {
const int y_offset = window_bounds.height() / 4; const int y_offset = window_bounds.height() / 4;
// Verify initial placement in top right quadrant. // Verify initial placement in top right quadrant.
auto game_bounds = app_bounds();
const auto* native_window = test_api_->GetToolbarWidget()->GetNativeWindow(); const auto* native_window = test_api_->GetToolbarWidget()->GetNativeWindow();
auto toolbar_bounds = native_window->GetBoundsInScreen(); auto toolbar_bounds = native_window->GetBoundsInScreen();
const auto toolbar_size = const auto toolbar_size =
@ -1303,36 +1382,44 @@ TEST_P(GameTypeGameDashboardContextTest, VerifyToolbarPlacementInQuadrants) {
const int frame_header_height = frame_header_->GetHeaderHeight(); const int frame_header_height = frame_header_->GetHeaderHeight();
EXPECT_EQ(test_api_->GetToolbarSnapLocation(), EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
ToolbarSnapLocation::kTopRight); ToolbarSnapLocation::kTopRight);
EXPECT_EQ(toolbar_bounds.x(), EXPECT_EQ(toolbar_bounds.x(), game_bounds.right() -
kAppBounds.right() - kToolbarEdgePadding - toolbar_size.width()); game_dashboard::kToolbarEdgePadding -
EXPECT_EQ(toolbar_bounds.y(), toolbar_size.width());
kAppBounds.y() + kToolbarEdgePadding + frame_header_height); EXPECT_EQ(toolbar_bounds.y(), game_bounds.y() +
game_dashboard::kToolbarEdgePadding +
frame_header_height);
// Move toolbar to top left quadrant and verify toolbar placement. // Move toolbar to top left quadrant and verify toolbar placement.
DragToolbarToPoint(Movement::kMouse, {window_center_point.x() - x_offset, DragToolbarToPoint(Movement::kMouse, {window_center_point.x() - x_offset,
window_center_point.y() - y_offset}); window_center_point.y() - y_offset});
EXPECT_EQ(test_api_->GetToolbarSnapLocation(), ToolbarSnapLocation::kTopLeft); EXPECT_EQ(test_api_->GetToolbarSnapLocation(), ToolbarSnapLocation::kTopLeft);
toolbar_bounds = native_window->GetBoundsInScreen(); toolbar_bounds = native_window->GetBoundsInScreen();
EXPECT_EQ(toolbar_bounds.x(), kAppBounds.x() + kToolbarEdgePadding); EXPECT_EQ(toolbar_bounds.x(),
EXPECT_EQ(toolbar_bounds.y(), game_bounds.x() + game_dashboard::kToolbarEdgePadding);
kAppBounds.y() + kToolbarEdgePadding + frame_header_height); EXPECT_EQ(toolbar_bounds.y(), game_bounds.y() +
game_dashboard::kToolbarEdgePadding +
frame_header_height);
// Move toolbar to bottom right quadrant and verify toolbar placement. // Move toolbar to bottom right quadrant and verify toolbar placement.
DragToolbarToPoint(Movement::kMouse, {window_center_point.x() + x_offset, DragToolbarToPoint(Movement::kMouse, {window_center_point.x() + x_offset,
window_center_point.y() + y_offset}); window_center_point.y() + y_offset});
toolbar_bounds = native_window->GetBoundsInScreen(); toolbar_bounds = native_window->GetBoundsInScreen();
EXPECT_EQ(toolbar_bounds.x(), EXPECT_EQ(toolbar_bounds.x(), game_bounds.right() -
kAppBounds.right() - kToolbarEdgePadding - toolbar_size.width()); game_dashboard::kToolbarEdgePadding -
EXPECT_EQ(toolbar_bounds.y(), toolbar_size.width());
kAppBounds.bottom() - kToolbarEdgePadding - toolbar_size.height()); EXPECT_EQ(toolbar_bounds.y(), game_bounds.bottom() -
game_dashboard::kToolbarEdgePadding -
toolbar_size.height());
// Move toolbar to bottom left quadrant and verify toolbar placement. // Move toolbar to bottom left quadrant and verify toolbar placement.
DragToolbarToPoint(Movement::kMouse, {window_center_point.x() - x_offset, DragToolbarToPoint(Movement::kMouse, {window_center_point.x() - x_offset,
window_center_point.y() + y_offset}); window_center_point.y() + y_offset});
toolbar_bounds = native_window->GetBoundsInScreen(); toolbar_bounds = native_window->GetBoundsInScreen();
EXPECT_EQ(toolbar_bounds.x(), kAppBounds.x() + kToolbarEdgePadding); EXPECT_EQ(toolbar_bounds.x(),
EXPECT_EQ(toolbar_bounds.y(), game_bounds.x() + game_dashboard::kToolbarEdgePadding);
kAppBounds.bottom() - kToolbarEdgePadding - toolbar_size.height()); EXPECT_EQ(toolbar_bounds.y(), game_bounds.bottom() -
game_dashboard::kToolbarEdgePadding -
toolbar_size.height());
} }
// Verifies the toolbar's snap location is preserved even after the visibility // Verifies the toolbar's snap location is preserved even after the visibility

@ -0,0 +1,179 @@
// 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/shell.h"
#include "ash/strings/grit/ash_strings.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/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
namespace ash {
namespace {
// Corner radius of the welcome dialog.
constexpr float kDialogCornerRadius = 24.0f;
// Fixed width of the welcome dialog.
static 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;
// Radius of the container of the shortcut text.
constexpr float kShortcutCornerRadius = 16.0f;
// Padding surrounding the shortcut info text.
constexpr int kShortcutTextBorder = 16;
// 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));
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));
}
// 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->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:
//
// +----------------------------------------------------+
// | shortcut_hint |
// +----------------------------------------------------+
void GameDashboardWelcomeDialog::AddShortcutInfoRow() {
const std::u16string shortcut_key = l10n_util::GetStringUTF16(
Shell::Get()->keyboard_capability()->HasLauncherButtonOnAnyKeyboard()
? IDS_ASH_SHORTCUT_MODIFIER_LAUNCHER
: IDS_ASH_SHORTCUT_MODIFIER_SEARCH);
auto* shortcut_hint = AddChildView(bubble_utils::CreateLabel(
TypographyToken::kCrosButton2,
l10n_util::GetStringFUTF16(IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SHORTCUT,
shortcut_key),
cros_tokens::kCrosSysPrimary));
shortcut_hint->SetMultiLine(true);
// TODO(b/316138331): Update max width to ensure it matches specs.
shortcut_hint->SizeToFit(kTitleTextMaxWidth);
shortcut_hint->SetHorizontalAlignment(gfx::ALIGN_LEFT);
shortcut_hint->SetBackground(views::CreateThemedRoundedRectBackground(
cros_tokens::kCrosSysSystemOnBase, kShortcutCornerRadius));
shortcut_hint->SetBorder(views::CreateEmptyBorder(
gfx::Insets::VH(kShortcutTextBorder, kShortcutTextBorder)));
}
BEGIN_METADATA(GameDashboardWelcomeDialog, views::FlexLayoutView)
END_METADATA
} // namespace ash

@ -0,0 +1,44 @@
// 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.
#ifndef ASH_GAME_DASHBOARD_GAME_DASHBOARD_WELCOME_DIALOG_H_
#define ASH_GAME_DASHBOARD_GAME_DASHBOARD_WELCOME_DIALOG_H_
#include "ash/ash_export.h"
#include "base/timer/timer.h"
#include "ui/views/layout/flex_layout_view.h"
namespace ash {
// `GameDashboardWelcomeDialog` is a View displayed for a set duration of time
// when first opening any game. It can be disabled via the Game Dashboard
// Settings.
class ASH_EXPORT GameDashboardWelcomeDialog : public views::FlexLayoutView {
public:
METADATA_HEADER(GameDashboardWelcomeDialog);
GameDashboardWelcomeDialog();
GameDashboardWelcomeDialog(const GameDashboardWelcomeDialog&) = delete;
GameDashboardWelcomeDialog& operator=(const GameDashboardWelcomeDialog) =
delete;
~GameDashboardWelcomeDialog() override;
// Starts the `timer_`, which will run the given `on_complete` once the time
// specified by `kDialogDuration` has elapsed.
void StartTimer(base::OnceClosure on_complete);
private:
// Adds a stacked title/sub-label and an icon as a row to the welcome dialog.
void AddTitleAndIconRow();
// Adds a row displaying how to open the dashboard.
void AddShortcutInfoRow();
// Timer for how long to show the welcome dialog.
base::OneShotTimer timer_;
};
} // namespace ash
#endif // ASH_GAME_DASHBOARD_GAME_DASHBOARD_WELCOME_DIALOG_H_