[M87]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.
(cherry picked from commit 4262b7b6810f7c703666a71006d531164cdb199f)
Bug: 1105541
Change-Id: Ic31863e1bab23ca50a298e22848611168bd17dca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2417978
Commit-Queue: Matthew Mourgos <[email protected]>
Reviewed-by: Xiyuan Xia <[email protected]>
Reviewed-by: Alex Newcomer <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#815835}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2472921
Reviewed-by: Matthew Mourgos <[email protected]>
Cr-Commit-Position: refs/branch-heads/4280@{#392}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 1bd198a..9552865 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -292,6 +292,8 @@
"clipboard/clipboard_history_resource_manager.h",
"clipboard/clipboard_history_util.cc",
"clipboard/clipboard_history_util.h",
+ "clipboard/clipboard_nudge.cc",
+ "clipboard/clipboard_nudge.h",
"clipboard/clipboard_nudge_constants.h",
"clipboard/clipboard_nudge_controller.cc",
"clipboard/clipboard_nudge_controller.h",
diff --git a/ash/clipboard/clipboard_nudge.cc b/ash/clipboard/clipboard_nudge.cc
new file mode 100644
index 0000000..dbd5ffb
--- /dev/null
+++ b/ash/clipboard/clipboard_nudge.cc
@@ -0,0 +1,142 @@
+// 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
diff --git a/ash/clipboard/clipboard_nudge.h b/ash/clipboard/clipboard_nudge.h
new file mode 100644
index 0000000..2724461
--- /dev/null
+++ b/ash/clipboard/clipboard_nudge.h
@@ -0,0 +1,37 @@
+// 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_
diff --git a/ash/clipboard/clipboard_nudge_constants.h b/ash/clipboard/clipboard_nudge_constants.h
index 90e80ea..c7983f6 100644
--- a/ash/clipboard/clipboard_nudge_constants.h
+++ b/ash/clipboard/clipboard_nudge_constants.h
@@ -13,6 +13,8 @@
constexpr static base::TimeDelta kMinInterval = base::TimeDelta::FromDays(1);
constexpr static base::TimeDelta kMaxTimeBetweenPaste =
base::TimeDelta::FromMinutes(10);
+constexpr static base::TimeDelta kNudgeShowTime =
+ base::TimeDelta::FromSeconds(6);
} // namespace ash
diff --git a/ash/clipboard/clipboard_nudge_controller.cc b/ash/clipboard/clipboard_nudge_controller.cc
index afc8f1d..dd9eb33 100644
--- a/ash/clipboard/clipboard_nudge_controller.cc
+++ b/ash/clipboard/clipboard_nudge_controller.cc
@@ -5,6 +5,7 @@
#include "ash/clipboard/clipboard_nudge_controller.h"
#include "ash/clipboard/clipboard_history_item.h"
+#include "ash/clipboard/clipboard_nudge.h"
#include "ash/clipboard/clipboard_nudge_constants.h"
#include "ash/public/cpp/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
@@ -15,6 +16,7 @@
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
+#include "ui/base/clipboard/clipboard_monitor.h"
namespace {
@@ -30,10 +32,12 @@
ClipboardHistory* clipboard_history)
: clipboard_history_(clipboard_history) {
clipboard_history_->AddObserver(this);
+ ui::ClipboardMonitor::GetInstance()->AddObserver(this);
}
ClipboardNudgeController::~ClipboardNudgeController() {
clipboard_history_->RemoveObserver(this);
+ ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
}
// static
@@ -80,8 +84,8 @@
return;
case ClipboardState::kSecondCopy:
if (GetTime() - last_paste_timestamp_ < kMaxTimeBetweenPaste) {
- clipboard_state_ = ClipboardState::kShouldShowNudge;
- // TODO(crbug/1105541): Show clipboard nudge.
+ ShowNudge();
+ HandleNudgeShown();
} else {
// ClipboardState should be reset to kFirstPaste when timed out.
clipboard_state_ = ClipboardState::kFirstPaste;
@@ -94,6 +98,21 @@
}
}
+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() {
clipboard_state_ = ClipboardState::kInit;
PrefService* prefs =
diff --git a/ash/clipboard/clipboard_nudge_controller.h b/ash/clipboard/clipboard_nudge_controller.h
index 8d3b361..a7c12ba 100644
--- a/ash/clipboard/clipboard_nudge_controller.h
+++ b/ash/clipboard/clipboard_nudge_controller.h
@@ -7,7 +7,9 @@
#include "ash/ash_export.h"
#include "ash/clipboard/clipboard_history.h"
+#include "base/memory/weak_ptr.h"
#include "base/time/clock.h"
+#include "base/timer/timer.h"
#include "ui/base/clipboard/clipboard_observer.h"
class PrefService;
@@ -15,6 +17,7 @@
class ClipboardHistoryItem;
namespace ash {
+class ClipboardNudge;
// 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
@@ -38,7 +41,7 @@
// Registers profile prefs.
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
- // ui::ClipboardHistoryObserver:
+ // ui::ClipboardHistory::Observer:
void OnClipboardHistoryItemAdded(const ClipboardHistoryItem& item) override;
// ui::ClipboardObserver:
@@ -52,6 +55,7 @@
void ClearClockOverrideForTesting();
const ClipboardState& GetClipboardStateForTesting();
+ ClipboardNudge* GetClipboardNudgeForTesting() { return nudge_.get(); }
private:
// Gets the number of times the nudge has been shown.
@@ -63,6 +67,12 @@
// Gets the current time. Can be overridden for testing.
base::Time GetTime();
+ // Shows the nudge widget.
+ void ShowNudge();
+
+ // Hides the nudge widget.
+ void HideNudge();
+
// Owned by ClipboardHistoryController.
const ClipboardHistory* clipboard_history_;
@@ -72,6 +82,14 @@
base::Time last_paste_timestamp_;
// Clock that can be overridden for testing.
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
diff --git a/ash/clipboard/clipboard_nudge_controller_unittest.cc b/ash/clipboard/clipboard_nudge_controller_unittest.cc
index b3aea50a..c89dd42 100644
--- a/ash/clipboard/clipboard_nudge_controller_unittest.cc
+++ b/ash/clipboard/clipboard_nudge_controller_unittest.cc
@@ -75,10 +75,16 @@
EXPECT_EQ(ClipboardState::kSecondCopy,
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();
- EXPECT_EQ(ClipboardState::kShouldShowNudge,
+ EXPECT_EQ(ClipboardState::kInit,
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
@@ -121,6 +127,9 @@
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
// ClipboardState.
nudge_controller_->OnClipboardHistoryItemAdded(
@@ -128,8 +137,11 @@
EXPECT_EQ(ClipboardState::kSecondCopy,
nudge_controller_->GetClipboardStateForTesting());
nudge_controller_->OnClipboardDataRead();
- EXPECT_EQ(ClipboardState::kShouldShowNudge,
+ EXPECT_EQ(ClipboardState::kInit,
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.
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index ecc8a62..1f48e97 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -38,6 +38,7 @@
"capture_mode_window.icon",
"check_circle.icon",
"chevron_right.icon",
+ "clipboard.icon",
"close_button.icon",
"copy.icon",
"dark_theme_color_mode.icon",
diff --git a/ash/resources/vector_icons/clipboard.icon b/ash/resources/vector_icons/clipboard.icon
new file mode 100644
index 0000000..17af8e6
--- /dev/null
+++ b/ash/resources/vector_icons/clipboard.icon
@@ -0,0 +1,34 @@
+// 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