0

Update launcher "hide continue section" feature to new UX spec

This CL updates the clamshell mode launcher:
- The "Continue where you left off" label now always shows (even when
  the privacy toast is showing).
- A new "continue label container" is added to AppListBubbleAppsPage
  to hold both the continue label and the toggle continue section
  button.
- IconButton is updated to support "tiny" buttons (circle size 24x24)
  and custom vector icon sizes (the chevrons are supposed to be 16x16).

Screenshots:
https://screenshot.googleplex.com/7RXbf4GQxKJhNFd
https://screenshot.googleplex.com/5vNnVdQKaU5A75Y
https://screenshot.googleplex.com/3BcBJgAnq2DBANK

TODO: Update the feature for tablet mode
TODO: Animations

Bug: 1334337
Test: updated ash_unittests
Change-Id: Ic888ea00e8be92ec115d2311c7f9c255810987cf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3699110
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Commit-Queue: James Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1014199}
This commit is contained in:
James Cook
2022-06-14 23:29:15 +00:00
committed by Chromium LUCI CQ
parent 7294e4d782
commit b5d2be9a9b
12 changed files with 215 additions and 61 deletions

@ -30,7 +30,7 @@
#include "ash/public/cpp/style/color_provider.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/pill_button.h"
#include "ash/style/icon_button.h"
#include "base/bind.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
@ -55,6 +55,7 @@
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
using views::BoxLayout;
@ -72,6 +73,9 @@ constexpr auto kVerticalScrollInsets = gfx::Insets::TLBR(1, 0, 1, 1);
// interior apps page container margin.
constexpr int kVerticalPaddingBetweenSections = 16;
// Label container padding in DIPs.
constexpr auto kContinueLabelContainerPadding = gfx::Insets::TLBR(0, 16, 0, 16);
// The horizontal interior margin for the apps page container - i.e. the margin
// between the apps page bounds and the page content.
constexpr int kHorizontalInteriorMargin = 16;
@ -160,25 +164,11 @@ AppListBubbleAppsPage::AppListBubbleAppsPage(
kVerticalPaddingBetweenSections));
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kStretch);
// Add the button to show the continue section, wrapped in a view to center it
// horizontally.
{
auto* container =
scroll_contents->AddChildView(std::make_unique<views::View>());
auto* layout = container->SetLayoutManager(
std::make_unique<BoxLayout>(BoxLayout::Orientation::kVertical));
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kCenter);
show_continue_section_button_ =
container->AddChildView(std::make_unique<PillButton>(
base::BindRepeating(
&AppListBubbleAppsPage::OnPressShowContinueSection,
base::Unretained(this)),
l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_SHOW_CONTINUE_SECTION),
PillButton::Type::kIcon, &kExpandAllIcon));
show_continue_section_button_->SetUseDefaultLabelFont();
// Put the icon on the right.
show_continue_section_button_->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
}
// When feature LauncherHideContinueSection is enabled, the "Continue where
// you left off" label is in a container that is a child of this view.
// Otherwise the label is a child of the ContinueSectionView.
if (features::IsLauncherHideContinueSectionEnabled())
InitContinueLabelContainer(scroll_contents.get());
// Continue section row.
continue_section_ =
@ -188,6 +178,12 @@ AppListBubbleAppsPage::AppListBubbleAppsPage(
continue_section_->SetBorder(
views::CreateEmptyBorder(kContinueSectionInsets));
continue_section_->SetNudgeController(app_list_nudge_controller_.get());
if (features::IsLauncherHideContinueSectionEnabled()) {
// Decrease the between-sections spacing so the continue label is closer to
// the continue tasks section.
continue_section_->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(-14, 0, 0, 0));
}
// Observe changes in continue section visibility, to keep separator
// visibility in sync.
@ -289,9 +285,19 @@ void AppListBubbleAppsPage::AnimateShowLauncher(bool is_side_shelf) {
// build a single animation with conditional parts. https://crbug.com/1266020
const int section_offset = is_side_shelf ? -20 : 20;
int vertical_offset = 0;
const bool animate_continue_label_container =
continue_label_container_ && continue_label_container_->GetVisible();
if (animate_continue_label_container) {
vertical_offset += section_offset;
SlideViewIntoPosition(continue_label_container_, vertical_offset,
slide_duration);
}
if (continue_section_->GetVisible() &&
continue_section_->GetTasksSuggestionsCount() > 0) {
vertical_offset += section_offset;
// Only offset if this is the top section, otherwise animate next to the
// continue label container above.
if (!animate_continue_label_container)
vertical_offset += section_offset;
SlideViewIntoPosition(continue_section_, vertical_offset, slide_duration);
}
if (recent_apps_->GetVisible() && recent_apps_->GetItemViewCount() > 0) {
@ -646,19 +652,74 @@ ui::Layer* AppListBubbleAppsPage::GetPageAnimationLayerForTest() {
return scroll_view_->contents()->layer();
}
////////////////////////////////////////////////////////////////////////////////
// private:
void AppListBubbleAppsPage::InitContinueLabelContainer(
views::View* scroll_contents) {
continue_label_container_ =
scroll_contents->AddChildView(std::make_unique<views::View>());
continue_label_container_->SetBorder(
views::CreateEmptyBorder(kContinueLabelContainerPadding));
auto* layout = continue_label_container_->SetLayoutManager(
std::make_unique<BoxLayout>(BoxLayout::Orientation::kHorizontal));
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kCenter);
continue_label_ =
continue_label_container_->AddChildView(std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_CONTINUE_SECTION_LABEL)));
bubble_utils::ApplyStyle(continue_label_,
bubble_utils::LabelStyle::kSubtitle);
continue_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
// Button should be right aligned, so flex label to fill empty space.
layout->SetFlexForView(continue_label_, 1);
toggle_continue_section_button_ =
continue_label_container_->AddChildView(std::make_unique<IconButton>(
base::BindRepeating(&AppListBubbleAppsPage::OnToggleContinueSection,
base::Unretained(this)),
IconButton::Type::kTinyFloating, &kChevronUpIcon,
/*is_togglable=*/false,
/*has_border=*/false));
// The icon is scaled down since the button is tiny.
toggle_continue_section_button_->SetIconSize(16);
}
void AppListBubbleAppsPage::UpdateContinueSectionVisibility() {
// Show the "Show continue section" button if the continue section is hidden.
bool hide_continue_section = view_delegate_->ShouldHideContinueSection();
show_continue_section_button_->SetVisible(hide_continue_section);
// The continue section view and recent apps view manage their own visibility
// internally.
continue_section_->UpdateElementsVisibility();
recent_apps_->UpdateVisibility();
UpdateContinueLabelContainer();
UpdateSeparatorVisibility();
}
void AppListBubbleAppsPage::UpdateContinueLabelContainer() {
if (!continue_label_container_)
return;
// If there are no suggested tasks and no recent apps, it doesn't make sense
// to show the continue label. This can happen for brand-new users.
continue_label_container_->SetVisible(
continue_section_->GetTasksSuggestionsCount() > 0 ||
recent_apps_->GetItemViewCount() > 0);
// Update the toggle continue section button tooltip and image.
bool is_hidden = view_delegate_->ShouldHideContinueSection();
toggle_continue_section_button_->SetTooltipText(l10n_util::GetStringUTF16(
is_hidden ? IDS_ASH_LAUNCHER_SHOW_CONTINUE_SECTION_TOOLTIP
: IDS_ASH_LAUNCHER_HIDE_CONTINUE_SECTION_TOOLTIP));
toggle_continue_section_button_->SetVectorIcon(is_hidden ? kChevronDownIcon
: kChevronUpIcon);
}
void AppListBubbleAppsPage::UpdateSeparatorVisibility() {
separator_->SetVisible(recent_apps_->GetVisible() ||
// The separator only hides if the user has the continue section shown but
// there are no suggested tasks and no apps. This is rare.
separator_->SetVisible(view_delegate_->ShouldHideContinueSection() ||
recent_apps_->GetVisible() ||
continue_section_->GetVisible());
}
@ -819,9 +880,10 @@ void AppListBubbleAppsPage::SlideViewIntoPosition(views::View* view,
kSlideAnimationTweenType, cleanup);
}
void AppListBubbleAppsPage::OnPressShowContinueSection(const ui::Event& event) {
view_delegate_->SetHideContinueSection(false);
UpdateContinueSectionVisibility();
void AppListBubbleAppsPage::OnToggleContinueSection() {
bool should_hide = !view_delegate_->ShouldHideContinueSection();
view_delegate_->SetHideContinueSection(should_hide);
// AppListControllerImpl will trigger UpdateContinueSectionVisibility().
}
BEGIN_METADATA(AppListBubbleAppsPage, views::View)

@ -28,6 +28,7 @@ class Layer;
} // namespace ui
namespace views {
class Label;
class Separator;
} // namespace views
@ -40,7 +41,7 @@ class AppListFolderController;
class AppListNudgeController;
class AppListViewDelegate;
class ContinueSectionView;
class PillButton;
class IconButton;
class RecentAppsView;
class RoundedScrollBar;
class SearchResultPageDialogController;
@ -151,8 +152,11 @@ class ASH_EXPORT AppListBubbleAppsPage
// Which layer animates is an implementation detail.
ui::Layer* GetPageAnimationLayerForTest();
PillButton* show_continue_section_button_for_test() {
return show_continue_section_button_;
views::View* continue_label_container_for_test() {
return continue_label_container_;
}
IconButton* toggle_continue_section_button_for_test() {
return toggle_continue_section_button_;
}
RecentAppsView* recent_apps_for_test() { return recent_apps_; }
views::Separator* separator_for_test() { return separator_; }
@ -171,6 +175,14 @@ class ASH_EXPORT AppListBubbleAppsPage
private:
friend class AppListTestHelper;
// Creates the `continue_label_container_` view and its contents, the
// continue label and the toggle continue section button.
void InitContinueLabelContainer(views::View* scroll_contents);
// Updates the continue label container and its child views, including the
// container visibility and the toggle button state.
void UpdateContinueLabelContainer();
void UpdateSeparatorVisibility();
// Destroys the layer for `view`. Not static so it can be used with weak
@ -206,13 +218,19 @@ class ASH_EXPORT AppListBubbleAppsPage
int vertical_offset,
base::TimeDelta duration);
// Button press callback for `show_continue_section_button_`.
void OnPressShowContinueSection(const ui::Event& event);
// Pressed callback for `toggle_continue_section_button_`.
void OnToggleContinueSection();
AppListViewDelegate* view_delegate_ = nullptr;
views::ScrollView* scroll_view_ = nullptr;
RoundedScrollBar* scroll_bar_ = nullptr;
PillButton* show_continue_section_button_ = nullptr;
// Wraps both the continue label and the toggle continue section button.
// Only exists when feature LauncherHideContinueSection is enabled.
views::View* continue_label_container_ = nullptr;
views::Label* continue_label_ = nullptr;
IconButton* toggle_continue_section_button_ = nullptr;
ContinueSectionView* continue_section_ = nullptr;
RecentAppsView* recent_apps_ = nullptr;
views::Separator* separator_ = nullptr;

@ -20,7 +20,7 @@
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_list/app_list_controller.h"
#include "ash/shell.h"
#include "ash/style/pill_button.h"
#include "ash/style/icon_button.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/layer_animation_stopped_waiter.h"
#include "base/test/bind.h"
@ -249,17 +249,27 @@ TEST_F(AppListBubbleAppsPageTest, ContinueSectionVisibleByDefault) {
helper->AddAppItems(5);
helper->ShowAppList();
// The show continue section button is hidden.
auto* apps_page = helper->GetBubbleAppsPage();
EXPECT_FALSE(
apps_page->show_continue_section_button_for_test()->GetVisible());
// The continue section and recent apps are visible.
auto* apps_page = helper->GetBubbleAppsPage();
EXPECT_TRUE(helper->GetBubbleContinueSectionView()->GetVisible());
EXPECT_TRUE(helper->GetBubbleRecentAppsView()->GetVisible());
EXPECT_TRUE(apps_page->separator_for_test()->GetVisible());
}
TEST_F(AppListBubbleAppsPageTest, ContinueLabelHiddenWhenNoTasksAndNoRecents) {
base::test::ScopedFeatureList feature_list(
features::kLauncherHideContinueSection);
// Show the app list with no continue suggestions and no recent apps.
auto* helper = GetAppListTestHelper();
helper->AddAppItems(5);
helper->ShowAppList();
auto* apps_page = helper->GetBubbleAppsPage();
ASSERT_TRUE(apps_page->continue_label_container_for_test());
EXPECT_FALSE(apps_page->continue_label_container_for_test()->GetVisible());
}
TEST_F(AppListBubbleAppsPageTest, CanHideContinueSection) {
base::test::ScopedFeatureList feature_list(
features::kLauncherHideContinueSection);
@ -272,19 +282,24 @@ TEST_F(AppListBubbleAppsPageTest, CanHideContinueSection) {
helper->AddAppItems(5);
helper->ShowAppList();
// Hide the continue section.
Shell::Get()->app_list_controller()->SetHideContinueSection(true);
// The show continue section button appears.
// The toggle continue section button has the "hide" tooltip.
auto* apps_page = helper->GetBubbleAppsPage();
auto* show_continue_section_button =
apps_page->show_continue_section_button_for_test();
EXPECT_TRUE(show_continue_section_button->GetVisible());
IconButton* toggle_continue_section_button =
apps_page->toggle_continue_section_button_for_test();
ASSERT_TRUE(toggle_continue_section_button);
EXPECT_EQ(toggle_continue_section_button->GetTooltipText(),
u"Hide all suggestions");
// Hide the continue section.
LeftClickOn(toggle_continue_section_button);
// Continue section and recent apps are hidden.
EXPECT_FALSE(helper->GetBubbleContinueSectionView()->GetVisible());
EXPECT_FALSE(helper->GetBubbleRecentAppsView()->GetVisible());
EXPECT_FALSE(apps_page->separator_for_test()->GetVisible());
// Label container and separator stay visible.
EXPECT_TRUE(apps_page->continue_label_container_for_test()->GetVisible());
EXPECT_TRUE(apps_page->separator_for_test()->GetVisible());
}
TEST_F(AppListBubbleAppsPageTest, CanShowContinueSectionByClickingButton) {
@ -302,22 +317,21 @@ TEST_F(AppListBubbleAppsPageTest, CanShowContinueSectionByClickingButton) {
helper->AddAppItems(5);
helper->ShowAppList();
// The show continue section button appears.
// The toggle continue section button has the "show" tooltip.
auto* apps_page = helper->GetBubbleAppsPage();
auto* show_continue_section_button =
apps_page->show_continue_section_button_for_test();
EXPECT_TRUE(show_continue_section_button->GetVisible());
IconButton* toggle_continue_section_button =
apps_page->toggle_continue_section_button_for_test();
ASSERT_TRUE(toggle_continue_section_button);
EXPECT_EQ(toggle_continue_section_button->GetTooltipText(),
u"Show all suggestions");
// Continue section and recent apps are hidden.
EXPECT_FALSE(helper->GetBubbleContinueSectionView()->GetVisible());
EXPECT_FALSE(helper->GetBubbleRecentAppsView()->GetVisible());
EXPECT_FALSE(apps_page->separator_for_test()->GetVisible());
EXPECT_TRUE(apps_page->separator_for_test()->GetVisible());
// Click the show continue section button.
LeftClickOn(show_continue_section_button);
// The button hides.
EXPECT_FALSE(show_continue_section_button->GetVisible());
LeftClickOn(toggle_continue_section_button);
// The continue section and recent apps are visible.
EXPECT_TRUE(helper->GetBubbleContinueSectionView()->GetVisible());

@ -21,6 +21,7 @@
#include "ash/app_list/views/continue_task_view.h"
#include "ash/app_list/views/search_result_page_dialog_controller.h"
#include "ash/bubble/bubble_utils.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
@ -108,7 +109,9 @@ ContinueSectionView::ContinueSectionView(
views::MinimumFlexSizeRule::kScaleToMinimumSnapToZero,
views::MaximumFlexSizeRule::kUnbounded));
if (!tablet_mode) {
// The launcher "hide continue section" feature makes the label a child of
// AppListBubbleAppsPage.
if (!tablet_mode && !features::IsLauncherHideContinueSectionEnabled()) {
continue_label_ = AddChildView(CreateContinueLabel(
l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_CONTINUE_SECTION_LABEL)));
continue_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);

@ -4568,6 +4568,12 @@ Here are some things you can try to get started.
<message name="IDS_ASH_LAUNCHER_HIDE_CONTINUE_SECTION" desc="Context menu label to hide the launcher continue section (which defaults to being shown).">
Hide all suggestions
</message>
<message name="IDS_ASH_LAUNCHER_SHOW_CONTINUE_SECTION_TOOLTIP" desc="Tooltip for button that shows the launcher continue section after it has been hidden.">
Show all suggestions
</message>
<message name="IDS_ASH_LAUNCHER_HIDE_CONTINUE_SECTION_TOOLTIP" desc="Tooltip for button that hides the launcher continue section (which defaults to being shown).">
Hide all suggestions
</message>
<message name="IDS_ASH_LAUNCHER_CONTINUE_SECTION_PRIVACY_TEXT" desc="Text for the privacy notice of the continue section of the launcher in clamshell mode, which shows recent files and apps that the user can continue using.">
Youll see recommendations so you can continue where you left off. You can right-click to remove recommendations.
</message>

@ -0,0 +1 @@
ed6eb6890e5465b0c4bd15d923e24f6d34de4bc7

@ -0,0 +1 @@
52fb662658e02186d2dc3b47a3acff6f85d4dcbe

@ -61,7 +61,9 @@ aggregate_vector_icons("ash_vector_icons") {
"caret_right.icon",
"check.icon",
"check_circle.icon",
"chevron_down.icon",
"chevron_right.icon",
"chevron_up.icon",
"clipboard.icon",
"clipboard_empty.icon",
"clipboard_launcher_inner.icon",

@ -0,0 +1,13 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
CANVAS_DIMENSIONS, 20,
MOVE_TO, 5.41f, 6,
LINE_TO, 10, 10.95f,
LINE_TO, 14.59f, 6,
LINE_TO, 16, 7.52f,
LINE_TO, 10, 14,
LINE_TO, 4, 7.52f,
LINE_TO, 5.41f, 6,
CLOSE

@ -0,0 +1,13 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
CANVAS_DIMENSIONS, 20,
MOVE_TO, 5.41f, 14,
LINE_TO, 10, 9.06f,
LINE_TO, 14.59f, 14,
LINE_TO, 16, 12.48f,
LINE_TO, 10, 6,
R_LINE_TO, -6, 6.48f,
LINE_TO, 5.41f, 14,
CLOSE

@ -26,6 +26,7 @@
namespace ash {
namespace {
constexpr int kTinyButtonSize = 24;
constexpr int kSmallButtonSize = 32;
constexpr int kMediumButtonSize = 36;
constexpr int kLargeButtonSize = 48;
@ -41,6 +42,9 @@ constexpr gfx::Insets kFocusRingPadding(1);
int GetButtonSizeOnType(IconButton::Type type) {
switch (type) {
case IconButton::Type::kTiny:
case IconButton::Type::kTinyFloating:
return kTinyButtonSize;
case IconButton::Type::kSmall:
case IconButton::Type::kSmallFloating:
return kSmallButtonSize;
@ -54,7 +58,8 @@ int GetButtonSizeOnType(IconButton::Type type) {
}
bool IsFloatingIconButton(IconButton::Type type) {
return type == IconButton::Type::kSmallFloating ||
return type == IconButton::Type::kTinyFloating ||
type == IconButton::Type::kSmallFloating ||
type == IconButton::Type::kMediumFloating ||
type == IconButton::Type::kLargeFloating;
}
@ -157,6 +162,13 @@ void IconButton::SetIconColor(const SkColor icon_color) {
UpdateVectorIcon();
}
void IconButton::SetIconSize(int size) {
if (icon_size_ == size)
return;
icon_size_ = size;
UpdateVectorIcon();
}
void IconButton::SetToggled(bool toggled) {
if (!is_togglable_ || toggled_ == toggled)
return;
@ -249,6 +261,7 @@ void IconButton::UpdateVectorIcon() {
const SkColor toggled_icon_color = color_provider->GetContentLayerColor(
AshColorProvider::ContentLayerType::kButtonIconColorPrimary);
const SkColor icon_color = toggled_ ? toggled_icon_color : normal_icon_color;
const int icon_size = icon_size_.value_or(kIconSize);
// Skip repainting if the incoming icon is the same as the current icon. If
// the icon has been painted before, |gfx::CreateVectorIcon()| will simply
@ -256,7 +269,7 @@ void IconButton::UpdateVectorIcon() {
// assumes that toggled/disabled images changes at the same time as the normal
// image, which it currently does.
const gfx::ImageSkia new_normal_image =
gfx::CreateVectorIcon(*icon_, kIconSize, icon_color);
gfx::CreateVectorIcon(*icon_, icon_size, icon_color);
const gfx::ImageSkia& old_normal_image =
GetImage(views::Button::STATE_NORMAL);
if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
@ -267,7 +280,7 @@ void IconButton::UpdateVectorIcon() {
SetImage(views::Button::STATE_NORMAL, new_normal_image);
SetImage(views::Button::STATE_DISABLED,
gfx::CreateVectorIcon(
*icon_, kIconSize,
*icon_, icon_size,
AshColorProvider::GetDisabledColor(normal_icon_color)));
}

@ -31,9 +31,11 @@ class ASH_EXPORT IconButton : public views::ImageButton {
METADATA_HEADER(IconButton);
enum class Type {
kTiny,
kSmall,
kMedium,
kLarge,
kTinyFloating,
kSmallFloating,
kMediumFloating,
kLargeFloating
@ -97,6 +99,9 @@ class ASH_EXPORT IconButton : public views::ImageButton {
// when it's not toggled.
void SetIconColor(const SkColor icon_color);
// Sets the size to use for the vector icon in DIPs.
void SetIconSize(int size);
// Updates the `toggled_` state of the button.
void SetToggled(bool toggled);
@ -126,6 +131,9 @@ class ASH_EXPORT IconButton : public views::ImageButton {
absl::optional<SkColor> background_color_;
absl::optional<SkColor> icon_color_;
// Custom value for icon size (usually used to make the icon smaller).
absl::optional<int> icon_size_;
DisabledButtonBehavior button_behavior_ = DisabledButtonBehavior::kNone;
};