0

Update arrow of nudges that are anchored to shelf

Set the arrow based on the current shelf alignment of the display the
nudge lives in. Also update the arrow if the shelf alignment changes.

This is done by setting a nudge data param `anchored_to_shelf` to true.

Updated existing usages of nudges that are anchored to the shelf (VC and
PhoneHub nudges) so they adopt this new behavior.

Example screenshots
left shelf: https://screenshot.googleplex.com/7Rar7Gsb2aJ6BQN
right shelf: https://screenshot.googleplex.com/9fNM5EowWRUCg7E

Bug: b:287687331, b:280497218
Change-Id: I2cffff647c2cdbbd8a773996dd1a78a49f13e487
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4623012
Reviewed-by: Alex Newcomer <newcomer@chromium.org>
Commit-Queue: Kevin Radtke <kradtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1160846}
This commit is contained in:
Kevin Radtke
2023-06-21 20:51:13 +00:00
committed by Chromium LUCI CQ
parent 91d285042c
commit e3b46cc84e
6 changed files with 143 additions and 1 deletions

@ -81,6 +81,10 @@ struct ASH_PUBLIC_EXPORT AnchoredNudgeData {
// anchor view is deleted, user locks session, etc.)
bool has_infinite_duration = false;
// If true, `arrow` will be set based on the current shelf alignment, and the
// nudge will listen to shelf alignment changes to readjust its `arrow`.
bool anchored_to_shelf = false;
// Nudge action callbacks.
HoverStateChangeCallback hover_state_change_callback;
AnchoredNudgeClickCallback nudge_click_callback;

@ -27,6 +27,7 @@ void PhoneHubNudgeController::ShowNudge(views::View* anchor_view,
}
AnchoredNudgeData nudge_data = {kPhoneHubNudgeId, NudgeCatalogName::kPhoneHub,
text, anchor_view};
nudge_data.anchored_to_shelf = true;
AnchoredNudgeManager::Get()->Show(nudge_data);
}

@ -8,6 +8,8 @@
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/system/toast/system_nudge_view.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
@ -28,6 +30,7 @@ AnchoredNudge::AnchoredNudge(const AnchoredNudgeData& nudge_data)
nudge_data.arrow,
views::BubbleBorder::NO_SHADOW),
id_(nudge_data.id),
anchored_to_shelf_(nudge_data.anchored_to_shelf),
nudge_click_callback_(std::move(nudge_data.nudge_click_callback)),
nudge_dismiss_callback_(std::move(nudge_data.nudge_dimiss_callback)) {
DCHECK(features::IsSystemNudgeV2Enabled());
@ -39,12 +42,20 @@ AnchoredNudge::AnchoredNudge(const AnchoredNudgeData& nudge_data)
SetLayoutManager(std::make_unique<views::FlexLayout>());
system_nudge_view_ =
AddChildView(std::make_unique<SystemNudgeView>(nudge_data));
if (anchored_to_shelf_) {
Shell::Get()->AddShellObserver(this);
}
}
AnchoredNudge::~AnchoredNudge() {
if (!nudge_dismiss_callback_.is_null()) {
std::move(nudge_dismiss_callback_).Run();
}
if (anchored_to_shelf_) {
Shell::Get()->RemoveShellObserver(this);
}
}
views::ImageView* AnchoredNudge::GetImageView() {
@ -89,6 +100,12 @@ AnchoredNudge::CreateNonClientFrameView(views::Widget* widget) {
return frame;
}
void AnchoredNudge::AddedToWidget() {
if (anchored_to_shelf_) {
SetArrowFromShelf();
}
}
bool AnchoredNudge::OnMousePressed(const ui::MouseEvent& event) {
return true;
}
@ -118,6 +135,31 @@ void AnchoredNudge::OnGestureEvent(ui::GestureEvent* event) {
}
}
void AnchoredNudge::OnShelfAlignmentChanged(aura::Window* root_window,
ShelfAlignment old_alignment) {
if (!GetWidget()) {
return;
}
if (Shelf::ForWindow(GetWidget()->GetNativeWindow()) ==
Shelf::ForWindow(root_window)) {
SetArrowFromShelf();
}
}
void AnchoredNudge::SetArrowFromShelf() {
if (!GetWidget()) {
return;
}
auto arrow =
Shelf::ForWindow(GetWidget()->GetNativeWindow())
->SelectValueForShelfAlignment(views::BubbleBorder::BOTTOM_CENTER,
views::BubbleBorder::LEFT_CENTER,
views::BubbleBorder::RIGHT_CENTER);
SetArrow(arrow);
}
BEGIN_METADATA(AnchoredNudge, views::BubbleDialogDelegateView)
END_METADATA

@ -9,6 +9,7 @@
#include "ash/ash_export.h"
#include "ash/public/cpp/system/anchored_nudge_data.h"
#include "ash/shell_observer.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
@ -21,6 +22,10 @@ class GestureEvent;
class MouseEvent;
} // namespace ui
namespace aura {
class Window;
}
namespace ash {
class SystemNudgeView;
@ -28,7 +33,8 @@ class SystemNudgeView;
// Creates and manages the widget and contents view for an anchored nudge.
// TODO(b/285988235): `AnchoredNudge` will replace the existing `SystemNudge`
// and take over its name.
class ASH_EXPORT AnchoredNudge : public views::BubbleDialogDelegateView {
class ASH_EXPORT AnchoredNudge : public ShellObserver,
public views::BubbleDialogDelegateView {
public:
METADATA_HEADER(AnchoredNudge);
@ -49,17 +55,28 @@ class ASH_EXPORT AnchoredNudge : public views::BubbleDialogDelegateView {
views::Widget* widget) override;
// views::View:
void AddedToWidget() override;
bool OnMousePressed(const ui::MouseEvent& event) override;
bool OnMouseDragged(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
// ShellObserver:
void OnShelfAlignmentChanged(aura::Window* root_window,
ShelfAlignment old_alignment) override;
// Sets the arrow of the nudge based on the current shelf's alignment.
void SetArrowFromShelf();
const std::string& id() { return id_; }
private:
// Unique id used to find and dismiss the nudge through the manager.
const std::string id_;
// Whether the nudge should set its arrow based on shelf alignment.
const bool anchored_to_shelf_;
// Owned by the views hierarchy. Contents view of the anchored nudge.
raw_ptr<SystemNudgeView> system_nudge_view_ = nullptr;

@ -18,6 +18,7 @@
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/label.h"
#include "ui/views/widget/widget.h"
@ -297,6 +298,80 @@ TEST_F(AnchoredNudgeManagerImplTest, ShowNudge_AnchorViewWithoutWidget) {
EXPECT_FALSE(GetShownNudges()[id]);
}
// Tests that a nudge sets the appropriate arrow when it's set to be anchored to
// the shelf, and updates its arrow whenever the shelf alignment changes.
TEST_F(AnchoredNudgeManagerImplTest, NudgeAnchoredToShelf) {
std::unique_ptr<views::Widget> widget = CreateFramelessTestWidget();
// Set up nudge data contents.
const std::string id = "id";
auto* anchor_view = widget->SetContentsView(std::make_unique<views::View>());
auto nudge_data = CreateBaseNudgeData(id, anchor_view);
// Make the nudge set its arrow based on the shelf's position.
nudge_data.anchored_to_shelf = true;
// Set shelf alignment to the left.
Shelf* shelf = GetPrimaryShelf();
EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
shelf->SetAlignment(ShelfAlignment::kLeft);
// Show a nudge, expect its arrow to be aligned with left shelf.
anchored_nudge_manager()->Show(nudge_data);
EXPECT_TRUE(GetShownNudges()[id]);
EXPECT_EQ(views::BubbleBorder::Arrow::LEFT_CENTER,
GetShownNudges()[id]->arrow());
// Cancel the nudge, and show a new nudge with bottom shelf alignment.
anchored_nudge_manager()->Cancel(id);
shelf->SetAlignment(ShelfAlignment::kBottom);
anchored_nudge_manager()->Show(nudge_data);
EXPECT_EQ(views::BubbleBorder::Arrow::BOTTOM_CENTER,
GetShownNudges()[id]->arrow());
// Change the shelf alignment to the right while the nudge is still open,
// nudge arrow should be updated.
shelf->SetAlignment(ShelfAlignment::kRight);
EXPECT_EQ(views::BubbleBorder::Arrow::RIGHT_CENTER,
GetShownNudges()[id]->arrow());
}
// Tests that a nudge that is anchored to the shelf is not affected by shelf
// alignment changes of a display where the nudge does not exist.
TEST_F(AnchoredNudgeManagerImplTest,
NudgeAnchoredToShelf_WithASecondaryDisplay) {
// Add a secondary display.
UpdateDisplay("800x700,800x700");
RootWindowController* const secondary_root_window_controller =
Shell::GetRootWindowControllerWithDisplayId(GetSecondaryDisplay().id());
Shelf* shelf = GetPrimaryShelf();
// Set up nudge data contents.
const std::string id = "id";
auto* anchor_view = shelf->status_area_widget()->unified_system_tray();
auto nudge_data = CreateBaseNudgeData(id, anchor_view);
// Make the nudge set its arrow based on the shelf's position.
nudge_data.anchored_to_shelf = true;
// Set shelf alignment to the left.
shelf->SetAlignment(ShelfAlignment::kLeft);
// Show a nudge, expect its arrow to be aligned with left shelf.
anchored_nudge_manager()->Show(nudge_data);
EXPECT_TRUE(GetShownNudges()[id]);
EXPECT_EQ(views::BubbleBorder::Arrow::LEFT_CENTER,
GetShownNudges()[id]->arrow());
// Test that changing the shelf alignment on the secondary display does not
// affect the nudge's arrow, since the nudge lives in the primary display.
secondary_root_window_controller->shelf()->SetAlignment(
ShelfAlignment::kBottom);
EXPECT_EQ(views::BubbleBorder::Arrow::LEFT_CENTER,
GetShownNudges()[id]->arrow());
}
// Tests that a nudge closes if its anchor view is made invisible.
TEST_F(AnchoredNudgeManagerImplTest, NudgeCloses_WhenAnchorViewIsHiding) {
std::unique_ptr<views::Widget> widget = CreateFramelessTestWidget();

@ -213,6 +213,7 @@ void VideoConferenceTrayController::MaybeShowSpeakOnMuteOptInNudge(
weak_ptr_factory_.GetWeakPtr());
nudge_data.has_infinite_duration = true;
nudge_data.anchored_to_shelf = true;
AnchoredNudgeManager::Get()->Show(nudge_data);
@ -479,6 +480,7 @@ void VideoConferenceTrayController::OnSpeakOnMuteDetected() {
->client()
->ShowSpeakOnMuteDetectionSettings();
});
nudge_data.anchored_to_shelf = true;
AnchoredNudgeManager::Get()->Show(nudge_data);
last_speak_on_mute_notification_time_.emplace(current_time);
@ -655,6 +657,7 @@ void VideoConferenceTrayController::HandleDeviceUsedWhileDisabled(
AnchoredNudgeData nudge_data(
nudge_id, catalog_name,
l10n_util::GetStringFUTF16(text_id, app_name, device_name), anchor_view);
nudge_data.anchored_to_shelf = true;
AnchoredNudgeManager::Get()->Show(nudge_data);
}