Commit 4262b7b6 authored by Matthew Mourgos's avatar Matthew Mourgos Committed by Commit Bot

Multipaste: Add a contextual nudge view

This change adds a nudge view that is triggered to show after copy and
pasting twice. The nudge is only shown for 6 seconds before being
automatically dismissed. The nudge should only be shown 3 times total.

Bug: 1105541
Change-Id: Ic31863e1bab23ca50a298e22848611168bd17dca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2417978
Commit-Queue: Matthew Mourgos <mmourgos@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarAlex Newcomer <newcomer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815835}
parent c6dfd54d
...@@ -298,6 +298,8 @@ component("ash") { ...@@ -298,6 +298,8 @@ component("ash") {
"clipboard/clipboard_history_resource_manager.h", "clipboard/clipboard_history_resource_manager.h",
"clipboard/clipboard_history_util.cc", "clipboard/clipboard_history_util.cc",
"clipboard/clipboard_history_util.h", "clipboard/clipboard_history_util.h",
"clipboard/clipboard_nudge.cc",
"clipboard/clipboard_nudge.h",
"clipboard/clipboard_nudge_constants.h", "clipboard/clipboard_nudge_constants.h",
"clipboard/clipboard_nudge_controller.cc", "clipboard/clipboard_nudge_controller.cc",
"clipboard/clipboard_nudge_controller.h", "clipboard/clipboard_nudge_controller.h",
......
// Copyright 2020 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.
#include "ash/clipboard/clipboard_nudge.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/assistant/assistant_state.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/style/ash_color_provider.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/events/keyboard_layout_util.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
// The corner radius of the nudge view.
constexpr int kNudgeCornerRadius = 8;
// The blur radius for the nudge view's background.
constexpr int kNudgeBlurRadius = 30;
// The size of the clipboard icon.
constexpr int kClipboardIconSize = 20;
// The size of the keyboard shortcut icon.
constexpr int kKeyboardShortcutIconSize = 16;
// The minimum width of the label.
constexpr int kMinLabelWidth = 200;
// The margin between the edge of the screen/shelf and the nudge widget bounds.
constexpr int kNudgeMargin = 8;
// The spacing between the icon and label in the nudge view.
constexpr int kIconLabelSpacing = 16;
// The padding which separates the nudge's border with its inner contents.
constexpr int kNudgePadding = 16;
} // namespace
class ClipboardNudge::ClipboardNudgeView : public views::View {
public:
ClipboardNudgeView() {
SetPaintToLayer(ui::LAYER_SOLID_COLOR);
layer()->SetColor(ShelfConfig::Get()->GetDefaultShelfColor());
if (features::IsBackgroundBlurEnabled())
layer()->SetBackgroundBlur(kNudgeBlurRadius);
layer()->SetRoundedCornerRadius({kNudgeCornerRadius, kNudgeCornerRadius,
kNudgeCornerRadius, kNudgeCornerRadius});
SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary);
clipboard_icon_ = AddChildView(std::make_unique<views::ImageView>());
clipboard_icon_->SetPaintToLayer();
clipboard_icon_->layer()->SetFillsBoundsOpaquely(false);
clipboard_icon_->SetBounds(kNudgePadding, kNudgePadding, kClipboardIconSize,
kClipboardIconSize);
clipboard_icon_->SetImage(
gfx::CreateVectorIcon(kClipboardIcon, icon_color));
label_ = AddChildView(std::make_unique<views::Label>());
label_->SetPaintToLayer();
label_->layer()->SetFillsBoundsOpaquely(false);
label_->SetMultiLine(true);
label_->SetPosition(gfx::Point(
kNudgePadding + kClipboardIconSize + kIconLabelSpacing, kNudgePadding));
label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label_->SetVerticalAlignment(gfx::ALIGN_TOP);
label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
label_->SetBackgroundColor(SK_ColorTRANSPARENT);
// TODO(mmourgos): Create and set text for |label_|.
label_->SetSize(gfx::Size(kMinLabelWidth, kKeyboardShortcutIconSize));
}
~ClipboardNudgeView() override = default;
views::Label* label_ = nullptr;
views::ImageView* clipboard_icon_ = nullptr;
};
ClipboardNudge::ClipboardNudge() : widget_(std::make_unique<views::Widget>()) {
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.z_order = ui::ZOrderLevel::kFloatingWindow;
params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.name = "ClipboardContextualNudge";
params.layer_type = ui::LAYER_NOT_DRAWN;
params.parent = Shell::GetPrimaryRootWindow()->GetChildById(
kShellWindowId_OverlayContainer);
widget_->Init(std::move(params));
nudge_view_ =
widget_->SetContentsView(std::make_unique<ClipboardNudgeView>());
CalculateAndSetWidgetBounds();
widget_->Show();
}
ClipboardNudge::~ClipboardNudge() = default;
void ClipboardNudge::Close() {
widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
void ClipboardNudge::CalculateAndSetWidgetBounds() {
aura::Window* root_window = Shell::GetRootWindowForNewWindows();
gfx::Rect display_bounds = root_window->bounds();
::wm::ConvertRectToScreen(root_window, &display_bounds);
gfx::Rect widget_bounds;
// Calculate the nudge's size to ensure the label text accurately fits.
const int nudge_height =
2 * kNudgePadding + nudge_view_->label_->bounds().height();
const int nudge_width = 2 * kNudgePadding + kClipboardIconSize +
kIconLabelSpacing +
nudge_view_->label_->bounds().width();
widget_bounds =
gfx::Rect(display_bounds.x() + kNudgeMargin,
display_bounds.height() - ShelfConfig::Get()->shelf_size() -
nudge_height - kNudgeMargin,
nudge_width, nudge_height);
if (base::i18n::IsRTL())
widget_bounds.set_x(display_bounds.right() - nudge_width - kNudgeMargin);
widget_->SetBounds(widget_bounds);
}
} // namespace ash
\ No newline at end of file
// Copyright 2020 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.
#ifndef ASH_CLIPBOARD_CLIPBOARD_NUDGE_H_
#define ASH_CLIPBOARD_CLIPBOARD_NUDGE_H_
#include "ash/ash_export.h"
#include "ui/views/widget/widget.h"
namespace ash {
// Implements a contextual nudge for multipaste.
class ASH_EXPORT ClipboardNudge {
public:
ClipboardNudge();
ClipboardNudge(const ClipboardNudge&) = delete;
ClipboardNudge& operator=(const ClipboardNudge&) = delete;
~ClipboardNudge();
void Close();
private:
class ClipboardNudgeView;
// Calculate and set widget bounds based ona fixed width and a variable
// height to correctly fit the text.
void CalculateAndSetWidgetBounds();
std::unique_ptr<views::Widget> widget_;
ClipboardNudgeView* nudge_view_ = nullptr; // not_owned
};
} // namespace ash
#endif // ASH_CLIPBOARD_CLIPBOARD_NUDGE_H_
...@@ -13,6 +13,8 @@ constexpr static int kNotificationLimit = 3; ...@@ -13,6 +13,8 @@ constexpr static int kNotificationLimit = 3;
constexpr static base::TimeDelta kMinInterval = base::TimeDelta::FromDays(1); constexpr static base::TimeDelta kMinInterval = base::TimeDelta::FromDays(1);
constexpr static base::TimeDelta kMaxTimeBetweenPaste = constexpr static base::TimeDelta kMaxTimeBetweenPaste =
base::TimeDelta::FromMinutes(10); base::TimeDelta::FromMinutes(10);
constexpr static base::TimeDelta kNudgeShowTime =
base::TimeDelta::FromSeconds(6);
} // namespace ash } // namespace ash
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "ash/clipboard/clipboard_nudge_controller.h" #include "ash/clipboard/clipboard_nudge_controller.h"
#include "ash/clipboard/clipboard_history_item.h" #include "ash/clipboard/clipboard_history_item.h"
#include "ash/clipboard/clipboard_nudge.h"
#include "ash/clipboard/clipboard_nudge_constants.h" #include "ash/clipboard/clipboard_nudge_constants.h"
#include "ash/public/cpp/ash_pref_names.h" #include "ash/public/cpp/ash_pref_names.h"
#include "ash/session/session_controller_impl.h" #include "ash/session/session_controller_impl.h"
...@@ -15,6 +16,7 @@ ...@@ -15,6 +16,7 @@
#include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h" #include "components/prefs/scoped_user_pref_update.h"
#include "ui/base/clipboard/clipboard_monitor.h"
namespace { namespace {
...@@ -30,10 +32,12 @@ ClipboardNudgeController::ClipboardNudgeController( ...@@ -30,10 +32,12 @@ ClipboardNudgeController::ClipboardNudgeController(
ClipboardHistory* clipboard_history) ClipboardHistory* clipboard_history)
: clipboard_history_(clipboard_history) { : clipboard_history_(clipboard_history) {
clipboard_history_->AddObserver(this); clipboard_history_->AddObserver(this);
ui::ClipboardMonitor::GetInstance()->AddObserver(this);
} }
ClipboardNudgeController::~ClipboardNudgeController() { ClipboardNudgeController::~ClipboardNudgeController() {
clipboard_history_->RemoveObserver(this); clipboard_history_->RemoveObserver(this);
ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
} }
// static // static
...@@ -80,8 +84,8 @@ void ClipboardNudgeController::OnClipboardDataRead() { ...@@ -80,8 +84,8 @@ void ClipboardNudgeController::OnClipboardDataRead() {
return; return;
case ClipboardState::kSecondCopy: case ClipboardState::kSecondCopy:
if (GetTime() - last_paste_timestamp_ < kMaxTimeBetweenPaste) { if (GetTime() - last_paste_timestamp_ < kMaxTimeBetweenPaste) {
clipboard_state_ = ClipboardState::kShouldShowNudge; ShowNudge();
// TODO(crbug/1105541): Show clipboard nudge. HandleNudgeShown();
} else { } else {
// ClipboardState should be reset to kFirstPaste when timed out. // ClipboardState should be reset to kFirstPaste when timed out.
clipboard_state_ = ClipboardState::kFirstPaste; clipboard_state_ = ClipboardState::kFirstPaste;
...@@ -94,6 +98,21 @@ void ClipboardNudgeController::OnClipboardDataRead() { ...@@ -94,6 +98,21 @@ void ClipboardNudgeController::OnClipboardDataRead() {
} }
} }
void ClipboardNudgeController::ShowNudge() {
// Create and show the nudge.
nudge_ = std::make_unique<ClipboardNudge>();
// Start a timer to close the nudge after a set amount of time.
hide_nudge_timer_.Start(FROM_HERE, kNudgeShowTime,
base::BindOnce(&ClipboardNudgeController::HideNudge,
weak_ptr_factory_.GetWeakPtr()));
}
void ClipboardNudgeController::HideNudge() {
nudge_->Close();
nudge_.reset();
}
void ClipboardNudgeController::HandleNudgeShown() { void ClipboardNudgeController::HandleNudgeShown() {
clipboard_state_ = ClipboardState::kInit; clipboard_state_ = ClipboardState::kInit;
PrefService* prefs = PrefService* prefs =
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "ash/clipboard/clipboard_history.h" #include "ash/clipboard/clipboard_history.h"
#include "base/memory/weak_ptr.h"
#include "base/time/clock.h" #include "base/time/clock.h"
#include "base/timer/timer.h"
#include "ui/base/clipboard/clipboard_observer.h" #include "ui/base/clipboard/clipboard_observer.h"
class PrefService; class PrefService;
...@@ -15,6 +17,7 @@ class PrefRegistrySimple; ...@@ -15,6 +17,7 @@ class PrefRegistrySimple;
class ClipboardHistoryItem; class ClipboardHistoryItem;
namespace ash { namespace ash {
class ClipboardNudge;
// The clipboard contextual nudge will be shown after 4 user actions that must // The clipboard contextual nudge will be shown after 4 user actions that must
// happen in sequence. The user must perform copy, paste, copy and paste in // happen in sequence. The user must perform copy, paste, copy and paste in
...@@ -38,7 +41,7 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer, ...@@ -38,7 +41,7 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer,
// Registers profile prefs. // Registers profile prefs.
static void RegisterProfilePrefs(PrefRegistrySimple* registry); static void RegisterProfilePrefs(PrefRegistrySimple* registry);
// ui::ClipboardHistoryObserver: // ui::ClipboardHistory::Observer:
void OnClipboardHistoryItemAdded(const ClipboardHistoryItem& item) override; void OnClipboardHistoryItemAdded(const ClipboardHistoryItem& item) override;
// ui::ClipboardObserver: // ui::ClipboardObserver:
...@@ -52,6 +55,7 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer, ...@@ -52,6 +55,7 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer,
void ClearClockOverrideForTesting(); void ClearClockOverrideForTesting();
const ClipboardState& GetClipboardStateForTesting(); const ClipboardState& GetClipboardStateForTesting();
ClipboardNudge* GetClipboardNudgeForTesting() { return nudge_.get(); }
private: private:
// Gets the number of times the nudge has been shown. // Gets the number of times the nudge has been shown.
...@@ -63,6 +67,12 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer, ...@@ -63,6 +67,12 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer,
// Gets the current time. Can be overridden for testing. // Gets the current time. Can be overridden for testing.
base::Time GetTime(); base::Time GetTime();
// Shows the nudge widget.
void ShowNudge();
// Hides the nudge widget.
void HideNudge();
// Owned by ClipboardHistoryController. // Owned by ClipboardHistoryController.
const ClipboardHistory* clipboard_history_; const ClipboardHistory* clipboard_history_;
...@@ -72,6 +82,14 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer, ...@@ -72,6 +82,14 @@ class ASH_EXPORT ClipboardNudgeController : public ClipboardHistory::Observer,
base::Time last_paste_timestamp_; base::Time last_paste_timestamp_;
// Clock that can be overridden for testing. // Clock that can be overridden for testing.
base::Clock* g_clock_override = nullptr; base::Clock* g_clock_override = nullptr;
// Contextual nudge which shows a view to inform the user on multipaste usage.
std::unique_ptr<ClipboardNudge> nudge_;
// Timer to hide the clipboard nudge.
base::OneShotTimer hide_nudge_timer_;
base::WeakPtrFactory<ClipboardNudgeController> weak_ptr_factory_{this};
}; };
} // namespace ash } // namespace ash
......
...@@ -75,10 +75,16 @@ TEST_F(ClipboardNudgeControllerTest, ShouldShowNudgeAfterCorrectSequence) { ...@@ -75,10 +75,16 @@ TEST_F(ClipboardNudgeControllerTest, ShouldShowNudgeAfterCorrectSequence) {
EXPECT_EQ(ClipboardState::kSecondCopy, EXPECT_EQ(ClipboardState::kSecondCopy,
nudge_controller_->GetClipboardStateForTesting()); nudge_controller_->GetClipboardStateForTesting());
// Checks that the second paste advances state as expected. // Check that clipbaord nudge has not yet been created.
EXPECT_FALSE(nudge_controller_->GetClipboardNudgeForTesting());
// Checks that the second paste resets state as expected.
nudge_controller_->OnClipboardDataRead(); nudge_controller_->OnClipboardDataRead();
EXPECT_EQ(ClipboardState::kShouldShowNudge, EXPECT_EQ(ClipboardState::kInit,
nudge_controller_->GetClipboardStateForTesting()); nudge_controller_->GetClipboardStateForTesting());
// Check that clipbaord nudge has been created.
EXPECT_TRUE(nudge_controller_->GetClipboardNudgeForTesting());
} }
// Checks that the clipboard state does not advace if too much time passes // Checks that the clipboard state does not advace if too much time passes
...@@ -121,6 +127,9 @@ TEST_F(ClipboardNudgeControllerTest, NudgeDoesNotTimeOutWithSparsePastes) { ...@@ -121,6 +127,9 @@ TEST_F(ClipboardNudgeControllerTest, NudgeDoesNotTimeOutWithSparsePastes) {
nudge_controller_->GetClipboardStateForTesting()); nudge_controller_->GetClipboardStateForTesting());
} }
// Check that clipbaord nudge has not yet been created.
EXPECT_FALSE(nudge_controller_->GetClipboardNudgeForTesting());
// Check that HandleClipboardChanged() will advance nudge_controller's // Check that HandleClipboardChanged() will advance nudge_controller's
// ClipboardState. // ClipboardState.
nudge_controller_->OnClipboardHistoryItemAdded( nudge_controller_->OnClipboardHistoryItemAdded(
...@@ -128,8 +137,11 @@ TEST_F(ClipboardNudgeControllerTest, NudgeDoesNotTimeOutWithSparsePastes) { ...@@ -128,8 +137,11 @@ TEST_F(ClipboardNudgeControllerTest, NudgeDoesNotTimeOutWithSparsePastes) {
EXPECT_EQ(ClipboardState::kSecondCopy, EXPECT_EQ(ClipboardState::kSecondCopy,
nudge_controller_->GetClipboardStateForTesting()); nudge_controller_->GetClipboardStateForTesting());
nudge_controller_->OnClipboardDataRead(); nudge_controller_->OnClipboardDataRead();
EXPECT_EQ(ClipboardState::kShouldShowNudge, EXPECT_EQ(ClipboardState::kInit,
nudge_controller_->GetClipboardStateForTesting()); nudge_controller_->GetClipboardStateForTesting());
// Check that clipbaord nudge has been created.
EXPECT_TRUE(nudge_controller_->GetClipboardNudgeForTesting());
} }
// Checks that consecutive copy events does not advance the clipboard state. // Checks that consecutive copy events does not advance the clipboard state.
......
...@@ -37,6 +37,7 @@ aggregate_vector_icons2("ash_vector_icons") { ...@@ -37,6 +37,7 @@ aggregate_vector_icons2("ash_vector_icons") {
"capture_mode_window.icon", "capture_mode_window.icon",
"check_circle.icon", "check_circle.icon",
"chevron_right.icon", "chevron_right.icon",
"clipboard.icon",
"close_button.icon", "close_button.icon",
"copy.icon", "copy.icon",
"dark_theme_color_mode.icon", "dark_theme_color_mode.icon",
......
// Copyright 2020 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, 16, 3,
R_H_LINE_TO, -3.2f,
ARC_TO, 3, 3, 0, 0, 0, 10, 1,
R_ARC_TO, 3, 3, 0, 0, 0, -2.8f, 2,
H_LINE_TO, 4,
R_ARC_TO, 2, 2, 0, 0, 0, -2, 2,
R_V_LINE_TO, 11,
R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
R_H_LINE_TO, 12,
R_ARC_TO, 2, 2, 0, 0, 0, 2, -2,
V_LINE_TO, 5,
R_ARC_TO, 2, 2, 0, 0, 0, -2, -2,
CLOSE,
R_MOVE_TO, -6, 0,
R_ARC_TO, 1, 1, 0, 0, 1, 1, 1,
R_ARC_TO, 1, 1, 0, 0, 1, -1, 1,
R_ARC_TO, 1, 1, 0, 0, 1, -1, -1,
R_ARC_TO, 1, 1, 0, 0, 1, 1, -1,
CLOSE,
R_MOVE_TO, 6, 13,
H_LINE_TO, 4,
V_LINE_TO, 5,
R_H_LINE_TO, 3,
R_V_LINE_TO, 2,
R_H_LINE_TO, 6,
V_LINE_TO, 5,
R_H_LINE_TO, 3,
R_V_LINE_TO, 11,
CLOSE
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment