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:

committed by
Chromium LUCI CQ

parent
16673b7e34
commit
51e76e01a2
ash
BUILD.gnash_strings.grd
ash_strings_grd
IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SHORTCUT.png.sha1IDS_ASH_GAME_DASHBOARD_WELCOME_DIALOG_SUB_LABEL.png.sha1
capture_mode
game_dashboard
@ -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.
|
||||||
|
22
ash/game_dashboard/game_dashboard_constants.h
Normal file
22
ash/game_dashboard/game_dashboard_constants.h
Normal file
@ -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
|
||||||
|
179
ash/game_dashboard/game_dashboard_welcome_dialog.cc
Normal file
179
ash/game_dashboard/game_dashboard_welcome_dialog.cc
Normal file
@ -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
|
44
ash/game_dashboard/game_dashboard_welcome_dialog.h
Normal file
44
ash/game_dashboard/game_dashboard_welcome_dialog.h
Normal file
@ -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_
|
Reference in New Issue
Block a user