1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ash/clipboard/clipboard_nudge.h"
6 #include "ash/public/cpp/ash_features.h"
7 #include "ash/public/cpp/assistant/assistant_state.h"
8 #include "ash/public/cpp/shelf_config.h"
9 #include "ash/public/cpp/shell_window_ids.h"
10 #include "ash/resources/vector_icons/vector_icons.h"
11 #include "ash/root_window_controller.h"
12 #include "ash/shelf/hotseat_widget.h"
13 #include "ash/shell.h"
14 #include "ash/strings/grit/ash_strings.h"
15 #include "ash/style/ash_color_provider.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/chromeos/events/keyboard_layout_util.h"
18 #include "ui/compositor/scoped_layer_animation_settings.h"
19 #include "ui/gfx/color_palette.h"
20 #include "ui/gfx/paint_vector_icon.h"
21 #include "ui/views/border.h"
22 #include "ui/views/controls/image_view.h"
23 #include "ui/views/controls/label.h"
24 #include "ui/views/controls/styled_label.h"
25 #include "ui/wm/core/coordinate_conversion.h"
26 
27 namespace ash {
28 
29 namespace {
30 
31 // The corner radius of the nudge view.
32 constexpr int kNudgeCornerRadius = 8;
33 
34 // The blur radius for the nudge view's background.
35 constexpr int kNudgeBlurRadius = 30;
36 
37 // The size of the clipboard icon.
38 constexpr int kClipboardIconSize = 20;
39 
40 // The size of the keyboard shortcut icon.
41 constexpr int kKeyboardShortcutIconSize = 14;
42 
43 // The minimum width of the label.
44 constexpr int kMinLabelWidth = 200;
45 
46 // The margin between the edge of the screen/shelf and the nudge widget bounds.
47 constexpr int kNudgeMargin = 8;
48 
49 // The spacing between the icon and label in the nudge view.
50 constexpr int kIconLabelSpacing = 16;
51 
52 // The padding which separates the nudge's border with its inner contents.
53 constexpr int kNudgePadding = 16;
54 
55 constexpr base::TimeDelta kNudgeBoundsAnimationTime =
56     base::TimeDelta::FromMilliseconds(250);
57 
IsAssistantAvailable()58 bool IsAssistantAvailable() {
59   AssistantStateBase* state = AssistantState::Get();
60   return state->allowed_state() ==
61              chromeos::assistant::AssistantAllowedState::ALLOWED &&
62          state->settings_enabled().value_or(false);
63 }
64 
65 }  // namespace
66 
67 class ClipboardNudge::ClipboardNudgeView : public views::View {
68  public:
ClipboardNudgeView()69   ClipboardNudgeView() {
70     SetPaintToLayer(ui::LAYER_SOLID_COLOR);
71     layer()->SetColor(ShelfConfig::Get()->GetDefaultShelfColor());
72     if (features::IsBackgroundBlurEnabled())
73       layer()->SetBackgroundBlur(kNudgeBlurRadius);
74     layer()->SetRoundedCornerRadius({kNudgeCornerRadius, kNudgeCornerRadius,
75                                      kNudgeCornerRadius, kNudgeCornerRadius});
76 
77     SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
78         AshColorProvider::ContentLayerType::kIconColorPrimary);
79 
80     clipboard_icon_ = AddChildView(std::make_unique<views::ImageView>());
81     clipboard_icon_->SetPaintToLayer();
82     clipboard_icon_->layer()->SetFillsBoundsOpaquely(false);
83     clipboard_icon_->SetBounds(kNudgePadding, kNudgePadding, kClipboardIconSize,
84                                kClipboardIconSize);
85     clipboard_icon_->SetImage(
86         gfx::CreateVectorIcon(kClipboardIcon, icon_color));
87 
88     label_ = AddChildView(std::make_unique<views::StyledLabel>());
89     label_->SetPaintToLayer();
90     label_->layer()->SetFillsBoundsOpaquely(false);
91     label_->SetPosition(gfx::Point(
92         kNudgePadding + kClipboardIconSize + kIconLabelSpacing, kNudgePadding));
93 
94     bool use_launcher_key = ui::DeviceUsesKeyboardLayout2();
95 
96     // Set the keyboard shortcut icon depending on whether search button or
97     // launcher button is being used.
98     gfx::ImageSkia shortcut_icon;
99     if (use_launcher_key) {
100       if (IsAssistantAvailable()) {
101         shortcut_icon = gfx::CreateVectorIcon(gfx::IconDescription(
102             kClipboardLauncherOuterIcon, kKeyboardShortcutIconSize, icon_color,
103             &kClipboardLauncherInnerIcon));
104       } else {
105         shortcut_icon =
106             gfx::CreateVectorIcon(kClipboardLauncherNoAssistantIcon,
107                                   kKeyboardShortcutIconSize, icon_color);
108       }
109     } else {
110       shortcut_icon = gfx::CreateVectorIcon(
111           kClipboardSearchIcon, kKeyboardShortcutIconSize, icon_color);
112     }
113     std::unique_ptr<views::ImageView> keyboard_shortcut_icon;
114     keyboard_shortcut_icon = std::make_unique<views::ImageView>();
115     keyboard_shortcut_icon->SetImage(shortcut_icon);
116     keyboard_shortcut_icon->SetBorder(views::CreateEmptyBorder(2, 4, 0, -2));
117 
118     // Set the text for |label_|.
119     base::string16 shortcut_key = l10n_util::GetStringUTF16(
120         use_launcher_key ? IDS_ASH_MULTIPASTE_CONTEXTUAL_NUDGE_LAUNCHER_KEY
121                          : IDS_ASH_MULTIPASTE_CONTEXTUAL_NUDGE_SEARCH_KEY);
122     size_t offset;
123     base::string16 label_text = l10n_util::GetStringFUTF16(
124         IDS_ASH_MULTIPASTE_CONTEXTUAL_NUDGE, shortcut_key, &offset);
125     offset = offset + shortcut_key.length();
126     label_->SetText(label_text);
127 
128     // Set the color of the text surrounding the shortcut icon.
129     views::StyledLabel::RangeStyleInfo text_color;
130     text_color.override_color = AshColorProvider::Get()->GetContentLayerColor(
131         AshColorProvider::ContentLayerType::kTextColorPrimary);
132     label_->AddStyleRange(gfx::Range(0, offset), text_color);
133     label_->AddStyleRange(gfx::Range(offset + 1, label_text.length()),
134                           text_color);
135 
136     // Add the shortcut icon to |label_|.
137     views::StyledLabel::RangeStyleInfo icon_style;
138     icon_style.custom_view = keyboard_shortcut_icon.get();
139     label_->AddCustomView(std::move(keyboard_shortcut_icon));
140     label_->AddStyleRange(gfx::Range(offset, offset + 1), icon_style);
141 
142     label_->SizeToFit(kMinLabelWidth);
143     label_->SetDisplayedOnBackgroundColor(SK_ColorTRANSPARENT);
144   }
145 
146   ~ClipboardNudgeView() override = default;
147 
148   views::StyledLabel* label_ = nullptr;
149   views::ImageView* clipboard_icon_ = nullptr;
150 };
151 
ClipboardNudge()152 ClipboardNudge::ClipboardNudge()
153     : widget_(std::make_unique<views::Widget>()),
154       root_window_(Shell::GetRootWindowForNewWindows()) {
155   shelf_observer_.Add(RootWindowController::ForWindow(root_window_)->shelf());
156 
157   views::Widget::InitParams params(
158       views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
159   params.z_order = ui::ZOrderLevel::kFloatingWindow;
160   params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
161   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
162   params.name = "ClipboardContextualNudge";
163   params.layer_type = ui::LAYER_NOT_DRAWN;
164   params.parent =
165       root_window_->GetChildById(kShellWindowId_SettingBubbleContainer);
166   widget_->Init(std::move(params));
167 
168   nudge_view_ =
169       widget_->SetContentsView(std::make_unique<ClipboardNudgeView>());
170   CalculateAndSetWidgetBounds();
171   widget_->Show();
172 }
173 
174 ClipboardNudge::~ClipboardNudge() = default;
175 
OnHotseatStateChanged(HotseatState old_state,HotseatState new_state)176 void ClipboardNudge::OnHotseatStateChanged(HotseatState old_state,
177                                            HotseatState new_state) {
178   CalculateAndSetWidgetBounds();
179 }
180 
Close()181 void ClipboardNudge::Close() {
182   widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
183 }
184 
CalculateAndSetWidgetBounds()185 void ClipboardNudge::CalculateAndSetWidgetBounds() {
186   gfx::Rect display_bounds = root_window_->bounds();
187   ::wm::ConvertRectToScreen(root_window_, &display_bounds);
188   gfx::Rect widget_bounds;
189 
190   // Calculate the nudge's size to ensure the label text accurately fits.
191   const int nudge_height =
192       2 * kNudgePadding + nudge_view_->label_->bounds().height();
193   const int nudge_width = 2 * kNudgePadding + kClipboardIconSize +
194                           kIconLabelSpacing +
195                           nudge_view_->label_->bounds().width();
196 
197   widget_bounds =
198       gfx::Rect(display_bounds.x() + kNudgeMargin,
199                 display_bounds.height() - ShelfConfig::Get()->shelf_size() -
200                     nudge_height - kNudgeMargin,
201                 nudge_width, nudge_height);
202   if (base::i18n::IsRTL())
203     widget_bounds.set_x(display_bounds.right() - nudge_width - kNudgeMargin);
204 
205   // Set the nudge's bounds above the hotseat when it is extended.
206   HotseatWidget* hotseat_widget =
207       RootWindowController::ForWindow(root_window_)->shelf()->hotseat_widget();
208   if (hotseat_widget->state() == HotseatState::kExtended) {
209     widget_bounds.set_y(hotseat_widget->GetTargetBounds().y() - nudge_height -
210                         kNudgeMargin);
211   }
212 
213   // Only run the widget bounds animation if the widget's bounds have already
214   // been initialized.
215   std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
216   if (widget_->GetWindowBoundsInScreen().size() != gfx::Size()) {
217     settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
218         widget_->GetLayer()->GetAnimator());
219     settings->SetPreemptionStrategy(
220         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
221     settings->SetTransitionDuration(kNudgeBoundsAnimationTime);
222     settings->SetTweenType(gfx::Tween::EASE_OUT);
223   }
224 
225   widget_->SetBounds(widget_bounds);
226 }
227 
228 }  // namespace ash