diff --git a/ash/public/cpp/system/anchored_nudge_data.h b/ash/public/cpp/system/anchored_nudge_data.h index 0faeeb0a6eeea..20af1132c5ed4 100644 --- a/ash/public/cpp/system/anchored_nudge_data.h +++ b/ash/public/cpp/system/anchored_nudge_data.h @@ -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; diff --git a/ash/system/phonehub/phone_hub_nudge_controller.cc b/ash/system/phonehub/phone_hub_nudge_controller.cc index 8ddf96ff496ae..8551b6e243ce2 100644 --- a/ash/system/phonehub/phone_hub_nudge_controller.cc +++ b/ash/system/phonehub/phone_hub_nudge_controller.cc @@ -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); } diff --git a/ash/system/toast/anchored_nudge.cc b/ash/system/toast/anchored_nudge.cc index c469f074dce33..6bf5e6d731b79 100644 --- a/ash/system/toast/anchored_nudge.cc +++ b/ash/system/toast/anchored_nudge.cc @@ -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 diff --git a/ash/system/toast/anchored_nudge.h b/ash/system/toast/anchored_nudge.h index 45c9bca778f3b..8da91e5a66289 100644 --- a/ash/system/toast/anchored_nudge.h +++ b/ash/system/toast/anchored_nudge.h @@ -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; diff --git a/ash/system/toast/anchored_nudge_manager_impl_unittest.cc b/ash/system/toast/anchored_nudge_manager_impl_unittest.cc index e7431df5725c7..ef79a01cb2897 100644 --- a/ash/system/toast/anchored_nudge_manager_impl_unittest.cc +++ b/ash/system/toast/anchored_nudge_manager_impl_unittest.cc @@ -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(); diff --git a/ash/system/video_conference/video_conference_tray_controller.cc b/ash/system/video_conference/video_conference_tray_controller.cc index 4064d96cfa5ee..e39b313857a6c 100644 --- a/ash/system/video_conference/video_conference_tray_controller.cc +++ b/ash/system/video_conference/video_conference_tray_controller.cc @@ -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); }