Skip to content

Commit

Permalink
Add launcher nudge animation with text label.
Browse files Browse the repository at this point in the history
In this cl, a new type of animation is implemented for launcher nudge.
In particular, a label view will be shown behind the home button and
expand the clickable area during the animation.

Bug: 1308733
Change-Id: I1ac3ae15bd7029ce4986c2b0a465260a151cb013
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3584979
Reviewed-by: James Cook <jamescook@chromium.org>
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Commit-Queue: Wen-Chien Wang <wcwang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1001614}
  • Loading branch information
Wen-Chien Wang authored and Chromium LUCI CQ committed May 10, 2022
1 parent 62adc2c commit 51a5d60
Show file tree
Hide file tree
Showing 8 changed files with 601 additions and 110 deletions.
3 changes: 3 additions & 0 deletions ash/ash_strings.grd
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ This file contains the strings for ash.
<message name="IDS_SHELF_PREVIOUS" desc="Tooltip for the shelf arrow button that scrolls the shelf backward">
Previous
</message>
<message name="IDS_SHELF_LAUNCHER_NUDGE_TEXT" desc="The text shown to users beside the home button that shows the launcher UI when the device is in clamshell mode. This nudge text guides them to click on the home button to see all the apps in the launcher UI.">
See all apps
</message>

<!-- Status tray items -->
<message name="IDS_ASH_STATUS_TRAY_ACCESSIBLE_DESCRIPTION" is_accessibility_with_no_ui="true" desc="The accessible description of the status tray and the information on it.">
Expand Down
1 change: 1 addition & 0 deletions ash/ash_strings_grd/IDS_SHELF_LAUNCHER_NUDGE_TEXT.png.sha1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8f77a5e6dfd51e78a9f24cd60cc00057f66687dd
3 changes: 3 additions & 0 deletions ash/public/cpp/ash_typography.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ void ApplyAshFontStyles(int context,
switch (context) {
case CONTEXT_SEARCH_RESULT_VIEW_INLINE_ANSWER_DETAILS:
break;
case CONTEXT_LAUNCHER_NUDGE_LABEL:
details.size_delta = 1;
break;
case CONTEXT_SHARESHEET_BUBBLE_SMALL:
details.size_delta = -2;
break;
Expand Down
3 changes: 3 additions & 0 deletions ash/public/cpp/ash_typography.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ enum AshTextContext {
// A button that appears in the launcher's status area.
CONTEXT_LAUNCHER_BUTTON = ASH_TEXT_CONTEXT_START,

// Text label that used in launcher nudge label. Medium weight. 13pt size.
CONTEXT_LAUNCHER_NUDGE_LABEL,

// A button that appears within a row of the tray popup.
CONTEXT_TRAY_POPUP_BUTTON,

Expand Down
502 changes: 407 additions & 95 deletions ash/shelf/home_button.cc

Large diffs are not rendered by default.

55 changes: 50 additions & 5 deletions ash/shelf/home_button.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@
#include "ui/views/view_targeter_delegate.h"

namespace views {
class AnimationBuilder;
class CircleLayerDelegate;
class Label;
} // namespace views

namespace ui {
class Layer;
class LayerOwner;
}

namespace ash {

class Shelf;
class ShelfButtonDelegate;
class ShelfNavigationWidget;

Expand Down Expand Up @@ -63,6 +66,9 @@ class ASH_EXPORT HomeButton : public ShelfControlButton,
// Called when the nudge animation is started/ended.
virtual void NudgeAnimationStarted(HomeButton* home_button) = 0;
virtual void NudgeAnimationEnded(HomeButton* home_button) = 0;

// Called when the nudge label is animated to fully shown.
virtual void NudgeLabelShown(HomeButton* home_button) = 0;
};

static const char kViewClassName[];
Expand All @@ -74,6 +80,10 @@ class ASH_EXPORT HomeButton : public ShelfControlButton,

~HomeButton() override;

// views::View:
gfx::Size CalculatePreferredSize() const override;
void Layout() override;

// views::Button:
void OnGestureEvent(ui::GestureEvent* event) override;
const char* GetClassName() const override;
Expand Down Expand Up @@ -104,32 +114,67 @@ class ASH_EXPORT HomeButton : public ShelfControlButton,
// returned ScopedNoClipRect.
[[nodiscard]] std::unique_ptr<ScopedNoClipRect> CreateScopedNoClipRect();

// Checks if the `nudge_label_` can be shown for the launcher nudge.
// NOTE: This must be called after `CreateNudgeLabel()`, where the
// `nudge_label_` is created. This is because whether the nudge can be shown
// depends on nudge_label_'s preferred size.
bool CanShowNudgeLabel() const;

// Starts the launcher nudge animation.
void StartNudgeAnimation();

void AddNudgeAnimationObserverForTest(NudgeAnimationObserver* observer);
void RemoveNudgeAnimationObserverForTest(NudgeAnimationObserver* observer);

views::View* label_container_for_test() const { return label_container_; }

protected:
// views::Button:
void PaintButtonContents(gfx::Canvas* canvas) override;
void OnThemeChanged() override;

private:
// Creates `nudge_label_` for launcher nudge.
void CreateNudgeLabel();

// Animation functions for launcher nudge.
void AnimateNudgeRipple(views::AnimationBuilder& builder);
void AnimateNudgeBounce(views::AnimationBuilder& builder);
void AnimateNudgeLabelSlideIn(views::AnimationBuilder& builder);
void AnimateNudgeLabelSlideOut();
void AnimateNudgeLabelFadeOut();

// Callbacks for the nudge animation.
void OnNudgeAnimationStarted();
void OnNudgeAnimationEnded();
void OnLabelSlideInAnimationEnded();
void OnLabelFadeOutAnimationEnded();

// Removes the nudge label from the view hierarchy.
void RemoveNudgeLabel();

// views::ViewTargeterDelegate:
bool DoesIntersectRect(const views::View* target,
const gfx::Rect& rect) const override;

// Callback for the nudge animation.
void OnNudgeAnimationStarted();
void OnNudgeAnimationEnded();
Shelf* const shelf_;

// The controller used to determine the button's behavior.
HomeButtonController controller_;

// The ripple layer in the launcher nudge animation. Only exists during the
// nudge animation.
std::unique_ptr<ui::Layer> nudge_ripple_layer_;
ui::LayerOwner nudge_ripple_layer_;

// The label view and for launcher nudge animation.
views::Label* nudge_label_ = nullptr;

// The container of `nudge_label_`. This is also responsible for painting the
// background of the label.
views::View* label_container_ = nullptr;

// The timer that counts down to hide the nudge_label_ from showing state.
base::OneShotTimer label_nudge_timer_;

// The delegate used by |nudge_ripple_layer_|. Only exists during the
// nudge animation.
Expand Down
124 changes: 121 additions & 3 deletions ash/shelf/launcher_nudge_controller_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
#include "ash/shelf/launcher_nudge_controller.h"

#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/scrollable_shelf_view.h"
#include "ash/shelf/shelf_controller.h"
#include "ash/shelf/shelf_test_util.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/json/values_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"

namespace ash {
Expand Down Expand Up @@ -43,7 +48,23 @@ class TestNudgeAnimationObserver : public HomeButton::NudgeAnimationObserver {

DCHECK_EQ(started_animation_count_, ended_animation_count_ + 1);
++ended_animation_count_;
run_loop_.Quit();
animation_run_loop_.Quit();
}
void NudgeLabelShown(HomeButton* home_button) override {
if (home_button != home_button_)
return;

label_run_loop_.Quit();
}

void WaitUntilLabelShown() {
ASSERT_TRUE(home_button_->CanShowNudgeLabel());
DCHECK_GE(started_animation_count_, ended_animation_count_);
if (started_animation_count_ == ended_animation_count_)
return;

// Block the test to wait until the label is shown.
label_run_loop_.Run();
}

void WaitUntilAnimationEnded() {
Expand All @@ -52,14 +73,15 @@ class TestNudgeAnimationObserver : public HomeButton::NudgeAnimationObserver {
return;

// Block the test to wait until the animation ended.
run_loop_.Run();
animation_run_loop_.Run();
}

// Returns the number of finished animation on this home_button_.
int GetShownCount() const { return ended_animation_count_; }

private:
base::RunLoop run_loop_;
base::RunLoop animation_run_loop_;
base::RunLoop label_run_loop_;
HomeButton* const home_button_;

// Counts the number of started/ended animations.
Expand All @@ -86,6 +108,13 @@ class LauncherNudgeControllerTest : public AshTestBase {
nudge_controller_->SetClockForTesting(
task_environment()->GetMockClock(),
task_environment()->GetMockTickClock());

scrollable_shelf_view_ = GetPrimaryShelf()
->shelf_widget()
->hotseat_widget()
->scrollable_shelf_view();
test_api_ = std::make_unique<ShelfViewTestAPI>(
scrollable_shelf_view_->shelf_view());
}

// Advances the mock clock in the task environment and wait until it is idle.
Expand All @@ -103,8 +132,19 @@ class LauncherNudgeControllerTest : public AshTestBase {
return LauncherNudgeController::GetShownCount(pref_service);
}

void AddAppShortcut(int& id) {
ShelfItem item = ShelfTestUtil::AddAppShortcut(base::NumberToString(id++),
TYPE_PINNED_APP);

// Wait for shelf view's bounds animation to end. Otherwise the scrollable
// shelf's bounds are not updated yet.
test_api_->RunMessageLoopUntilAnimationsDone();
}

LauncherNudgeController* nudge_controller_;
std::unique_ptr<TestNudgeAnimationObserver> observer_;
ScrollableShelfView* scrollable_shelf_view_ = nullptr;
std::unique_ptr<ShelfViewTestAPI> test_api_;

private:
base::test::ScopedFeatureList scoped_feature_list_;
Expand Down Expand Up @@ -304,4 +344,82 @@ TEST_F(LauncherNudgeControllerTest,
EXPECT_EQ(2, GetNudgeShownCount());
}

TEST_F(LauncherNudgeControllerTest, NudgeLabelVisibilityTest) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(GetNudgeShownCount(), 0);

HomeButton* home_button = LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
TestNudgeAnimationObserver waiter(home_button);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));

// Wait until the label to be shown and check if the label is visible.
waiter.WaitUntilLabelShown();
views::View* label_container = home_button->label_container_for_test();
EXPECT_TRUE(label_container && label_container->GetVisible());
EXPECT_EQ(label_container->layer()->opacity(), 1);

// Wait until the label is hidden.
AdvanceClock(base::TimeDelta(base::Seconds(6)));
waiter.WaitUntilAnimationEnded();
EXPECT_FALSE(label_container->GetVisible());
EXPECT_EQ(label_container->layer()->opacity(), 0);
EXPECT_EQ(GetNudgeShownCount(), 1);

TestNudgeAnimationObserver waiter2(home_button);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/false) -
base::Seconds(6));
waiter2.WaitUntilLabelShown();
EXPECT_TRUE(label_container->GetVisible());

gfx::Point center = label_container->GetBoundsInScreen().CenterPoint();
GetEventGenerator()->MoveMouseTo(center);

// Click on the nudge label should toggle the app list.
GetEventGenerator()->ClickLeftButton();
GetAppListTestHelper()->WaitUntilIdle();
GetAppListTestHelper()->CheckVisibility(true);

// Clicking on the nudge label should animate it. Wait until the animation
// ends.
waiter2.WaitUntilAnimationEnded();

// The label is removed after it is clicked.
EXPECT_FALSE(home_button->label_container_for_test());
}

TEST_F(LauncherNudgeControllerTest, AnimationUsedDependsOnAvailableSpace) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(GetNudgeShownCount(), 0);

HomeButton* home_button = LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());

// Advance the clock to trigger the animation and create the label nudge.
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));

// Without adding anything to shelf, there should be enough space to show
// nudge label.
EXPECT_TRUE(home_button->CanShowNudgeLabel());

int id = 0;
// Add app shortcuts until the hotseat overflow.
while (scrollable_shelf_view_->layout_strategy_for_test() ==
ScrollableShelfView::kNotShowArrowButtons) {
AddAppShortcut(id);
}

// If the apps overflow in shelf, there should be no space for the label to be
// shown.
EXPECT_FALSE(home_button->CanShowNudgeLabel());
}

} // namespace ash
20 changes: 13 additions & 7 deletions ash/shelf/shelf_navigation_widget.cc
Original file line number Diff line number Diff line change
Expand Up @@ -825,18 +825,24 @@ gfx::Size ShelfNavigationWidget::CalculateIdealSize(
if (!ShelfConfig::Get()->shelf_controls_shown())
return gfx::Size();

int control_button_number;
int controls_space = 0;
const int control_size = ShelfConfig::Get()->control_size();

if (Shell::Get()->IsInTabletMode() && !only_visible_area) {
// There are home button and back button. So the maximum is 2.
control_button_number = 2;
controls_space = control_size * 2 + ShelfConfig::Get()->button_spacing();
} else {
control_button_number = CalculateButtonCount();
// Use CalculatePreferredSize here to take the launcher nudge label into
// consider.
controls_space += IsHomeButtonShown()
? GetHomeButton()->CalculatePreferredSize().width()
: 0;
controls_space +=
IsBackButtonShown(shelf_->IsHorizontalAlignment()) ? control_size : 0;
controls_space +=
(CalculateButtonCount() - 1) * ShelfConfig::Get()->button_spacing();
}

const int control_size = ShelfConfig::Get()->control_size();
int controls_space =
control_button_number * control_size +
(control_button_number - 1) * ShelfConfig::Get()->button_spacing();
const int major_axis_spacing =
2 * ShelfConfig::Get()->control_button_edge_spacing(
shelf_->IsHorizontalAlignment());
Expand Down

0 comments on commit 51a5d60

Please sign in to comment.