fixup! Fix for Geolocation webTCT failures
[platform/framework/web/chromium-efl.git] / ash / display / display_alignment_indicator.cc
1 // Copyright 2020 The Chromium Authors
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/display/display_alignment_indicator.h"
6
7 #include "ash/public/cpp/shell_window_ids.h"
8 #include "ash/resources/vector_icons/vector_icons.h"
9 #include "ash/root_window_controller.h"
10 #include "ash/shell.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "ui/compositor/layer.h"
15 #include "ui/display/display.h"
16 #include "ui/gfx/color_palette.h"
17 #include "ui/gfx/image/image_skia_operations.h"
18 #include "ui/gfx/paint_vector_icon.h"
19 #include "ui/gfx/vector_icon_types.h"
20 #include "ui/views/background.h"
21 #include "ui/views/controls/image_view.h"
22 #include "ui/views/controls/label.h"
23 #include "ui/views/view.h"
24
25 namespace ash {
26
27 namespace {
28
29 // Constants for indicator highlights.
30 constexpr SkColor kEdgeHighlightColor = gfx::kGoogleBlue600;
31 constexpr int kHighlightShadowElevation = 2;
32
33 // Thickness (and radius) of indicator highlight is dependent on resolution.
34 // If display has resolution smaller than 1440p, then its thickness is
35 // |kHighlightRadiusSub1440p|. Otherwise, use |kHighlightRadius1440p|.
36 constexpr int kHighlightRadiusSub1440p = 4;
37 constexpr int kHighlightRadius1440p = 6;
38 constexpr int kHighlightSizeChangeRes = 1440;
39
40 // Constants for pill theme.
41 // White with ~60% opacity.
42 constexpr SkColor kPillBackgroundColor = SkColorSetARGB(0x99, 0xFF, 0xFF, 0xFF);
43 constexpr SkColor kPillTextColor = gfx::kGoogleBlue600;
44 constexpr int kPillBackgroundBlur = 10;
45
46 // Constants for pill layout.
47 constexpr int kPillRadius = 12;
48 constexpr int kMaxPillWidth = 192;
49 constexpr int kPillHeight = 32;
50 constexpr int kTextMarginNormal = 24;
51 constexpr int kTextMarginElided = 20;
52 // Distance between the indicator highlight and the pill.
53 constexpr int kPillMargin = 20;
54
55 // Constants for arrow layout.
56 constexpr int kArrowSize = 28;
57 constexpr int kArrowHorizontalMargin = 12;
58 constexpr int kArrowVerticalMargin = (kPillHeight - kArrowSize) / 2;
59 constexpr int kArrowAllocatedWidth =
60     kArrowHorizontalMargin + kArrowSize + kArrowHorizontalMargin;
61
62 enum class IndicatorPosition { kTop, kRight, kBottom, kLeft };
63
64 // Returns IndicatorPosition given the bounds of an indicator highlight along
65 // with its corresponding display.
66 IndicatorPosition GetIndicatorPosition(const display::Display& src_display,
67                                        const gfx::Rect& indicator_bounds) {
68   const gfx::Point midpoint = src_display.bounds().CenterPoint();
69
70   // Horizontal shared edge (kTop or kBottom)
71   if (indicator_bounds.width() > indicator_bounds.height()) {
72     return (indicator_bounds.y() < midpoint.y()) ? IndicatorPosition::kTop
73                                                  : IndicatorPosition::kBottom;
74   }
75   // Vertical shared edge (kLeft or kRight)
76   return (indicator_bounds.x() < midpoint.x()) ? IndicatorPosition::kLeft
77                                                : IndicatorPosition::kRight;
78 }
79
80 // Indicator thickness is dependent on display resolution.
81 int GetIndicatorThickness(const gfx::Size& display_size) {
82   return std::min(display_size.width(), display_size.height()) >
83                  kHighlightSizeChangeRes
84              ? kHighlightRadius1440p
85              : kHighlightRadiusSub1440p;
86 }
87
88 // Adjust the indicator bounds to the correct thickness depending on the
89 // resolution of |display|.
90 void AdjustIndicatorBounds(const display::Display& display,
91                            gfx::Rect* out_indicator_bounds) {
92   const int indicator_thickness = GetIndicatorThickness(display.size());
93
94   // Apply the new thickness to the indicator.
95   if (out_indicator_bounds->height() > out_indicator_bounds->width())
96     out_indicator_bounds->set_width(indicator_thickness);
97   else
98     out_indicator_bounds->set_height(indicator_thickness);
99
100   // Create enough space for the full indicator on the x and y axis.
101   const gfx::Point display_bottom_right = display.bounds().bottom_right();
102   if (out_indicator_bounds->x() == (display_bottom_right.x() - 1))
103     out_indicator_bounds->set_x(display_bottom_right.x() - indicator_thickness);
104   else if (out_indicator_bounds->y() == (display_bottom_right.y() - 1))
105     out_indicator_bounds->set_y(display_bottom_right.y() - indicator_thickness);
106 }
107
108 // Returns the pill's origin based on |pill_size| and the indicator's
109 // |thickened_bounds|.
110 gfx::Point GetPillOrigin(const gfx::Size& pill_size,
111                          IndicatorPosition src_position,
112                          const gfx::Rect& thickened_bounds) {
113   gfx::Point pill_origin;
114   switch (src_position) {
115     case IndicatorPosition::kLeft:
116       pill_origin = thickened_bounds.right_center();
117       pill_origin.Offset(kPillMargin, -1 * kPillHeight / 2);
118       break;
119     case IndicatorPosition::kRight:
120       pill_origin = thickened_bounds.left_center();
121       pill_origin.Offset(-1 * kPillMargin - pill_size.width(),
122                          -1 * kPillHeight / 2);
123       break;
124     case IndicatorPosition::kTop:
125       pill_origin = thickened_bounds.bottom_center();
126       pill_origin.Offset(-1 * pill_size.width() / 2, kPillMargin);
127       break;
128     case IndicatorPosition::kBottom:
129       pill_origin = thickened_bounds.top_center();
130       pill_origin.Offset(-1 * pill_size.width() / 2,
131                          -1 * kPillMargin - kPillHeight);
132       break;
133   }
134
135   return pill_origin;
136 }
137
138 views::Widget::InitParams CreateInitParams(int64_t display_id,
139                                            const std::string& target_name) {
140   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
141
142   aura::Window* root =
143       Shell::GetRootWindowControllerWithDisplayId(display_id)->GetRootWindow();
144
145   params.parent = Shell::GetContainer(root, kShellWindowId_OverlayContainer);
146   params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
147   params.ownership =
148       views::Widget::InitParams::Ownership::WIDGET_OWNS_NATIVE_WIDGET;
149   params.activatable = views::Widget::InitParams::Activatable::kNo;
150   params.accept_events = false;
151   params.name = target_name;
152
153   return params;
154 }
155
156 }  // namespace
157
158 // -----------------------------------------------------------------------------
159 // IndicatorHighlightView:
160 // View for the indicator highlight that renders on a shared edge of a given
161 // display.
162 class IndicatorHighlightView : public views::View {
163  public:
164   explicit IndicatorHighlightView(const display::Display& display)
165       // Corner radius is the same as edge thickness.
166       : corner_radius_(GetIndicatorThickness(display.size())) {
167     SetPaintToLayer(ui::LAYER_TEXTURED);
168
169     layer()->SetFillsBoundsOpaquely(false);
170     layer()->SetIsFastRoundedCorner(true);
171     SetBackground(views::CreateSolidBackground(kEdgeHighlightColor));
172   }
173
174   IndicatorHighlightView(const IndicatorHighlightView&) = delete;
175   IndicatorHighlightView& operator=(const IndicatorHighlightView&) = delete;
176   ~IndicatorHighlightView() override = default;
177
178   // Sets which corners should be rounded depending on the position of the
179   // indicator edge.
180   void SetPosition(IndicatorPosition position) {
181     gfx::RoundedCornersF corners;
182
183     switch (position) {
184       case IndicatorPosition::kLeft:
185         corners = {0, corner_radius_, corner_radius_, 0};
186         break;
187       case IndicatorPosition::kRight:
188         corners = {corner_radius_, 0, 0, corner_radius_};
189         break;
190       case IndicatorPosition::kTop:
191         corners = {0, 0, corner_radius_, corner_radius_};
192         break;
193       case IndicatorPosition::kBottom:
194         corners = {corner_radius_, corner_radius_, 0, 0};
195         break;
196     }
197
198     layer()->SetRoundedCornerRadius(corners);
199   }
200
201   // views::View:
202   const char* GetClassName() const override { return "IndicatorHighlightView"; }
203
204  private:
205   // Radius for the rounded rectangle highlight. Determined by display
206   // resolution.
207   float corner_radius_;
208 };
209
210 // -----------------------------------------------------------------------------
211 // IndicatorPillView:
212 // View for the pill with an arrow pointing to an indicator highlight and name
213 // of the target display.
214 class IndicatorPillView : public views::View {
215  public:
216   explicit IndicatorPillView(const std::u16string& text)
217       :  // TODO(1070352): Replace current placeholder arrow in
218          // IndicatorPillView
219         icon_(AddChildView(std::make_unique<views::ImageView>())),
220         text_label_(AddChildView(std::make_unique<views::Label>())),
221         arrow_image_(
222             CreateVectorIcon(kLockScreenArrowIcon, gfx::kGoogleBlue600)) {
223     SetPaintToLayer(ui::LAYER_TEXTURED);
224
225     layer()->SetFillsBoundsOpaquely(false);
226     layer()->SetIsFastRoundedCorner(true);
227     layer()->SetBackgroundBlur(kPillBackgroundBlur);
228     layer()->SetRoundedCornerRadius(gfx::RoundedCornersF{kPillRadius});
229
230     SetBackground(
231         views::CreateRoundedRectBackground(kPillBackgroundColor, kPillRadius));
232
233     text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
234     text_label_->SetEnabledColor(kPillTextColor);
235     text_label_->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
236     text_label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
237     text_label_->SetVerticalAlignment(gfx::VerticalAlignment::ALIGN_MIDDLE);
238
239     text_label_->SetText(text);
240
241     icon_->SetImage(arrow_image_);
242   }
243
244   IndicatorPillView(const IndicatorPillView&) = delete;
245   IndicatorPillView& operator=(const IndicatorPillView&) = delete;
246   ~IndicatorPillView() override = default;
247
248   // views::View:
249   gfx::Size CalculatePreferredSize() const override {
250     // Pill is laid out as:
251     // ( | | text )
252     // Has max width of kMaxPillWidth.
253
254     const int text_width = text_label_->CalculatePreferredSize().width();
255     const int width = kArrowAllocatedWidth + text_width + kTextMarginNormal;
256
257     return gfx::Size(std::min(width, kMaxPillWidth), kPillHeight);
258   }
259
260   // views::View:
261   void Layout() override {
262     icon_->SetImageSize(gfx::Size(kArrowSize, kArrowSize));
263
264     // IndicatorPosition::kRight is a special case for layout as it is the only
265     // time where the arrow is on the right of the text instead of the usual
266     // left.
267     const int local_width = GetLocalBounds().width();
268     int icon_x = position_ == IndicatorPosition::kRight
269                      ? local_width - kArrowHorizontalMargin - kArrowSize
270                      : kArrowHorizontalMargin;
271
272     icon_->SetBoundsRect(
273         gfx::Rect(icon_x, kArrowVerticalMargin, kArrowSize, kArrowSize));
274
275     // If width of the pill is equal or greater than the max pill width, then
276     // text is elided and thus side margin must be reduced.
277     const int side_margin = CalculatePreferredSize().width() >= kMaxPillWidth
278                                 ? kTextMarginElided
279                                 : kTextMarginNormal;
280
281     const int text_label_width =
282         local_width - kArrowAllocatedWidth - side_margin;
283
284     const int text_label_x = position_ == IndicatorPosition::kRight
285                                  ? side_margin
286                                  : kArrowAllocatedWidth;
287
288     text_label_->SetBoundsRect(
289         gfx::Rect(text_label_x, 0, text_label_width, kPillHeight));
290   }
291
292   // views::View:
293   const char* GetClassName() const override { return "IndicatorPillView"; }
294
295   // Rotates the arrow depending on indicator highlight's position on-screen.
296   void SetPosition(IndicatorPosition position) {
297     if (position_ == position)
298       return;
299
300     position_ = position;
301
302     switch (position) {
303       case IndicatorPosition::kLeft:
304         icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
305             arrow_image_, SkBitmapOperations::ROTATION_180_CW));
306         return;
307       case IndicatorPosition::kRight:
308         // |arrow_image_| points to right by default; no rotation required.
309         icon_->SetImage(arrow_image_);
310         return;
311       case IndicatorPosition::kTop:
312         icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
313             arrow_image_, SkBitmapOperations::ROTATION_270_CW));
314         return;
315       case IndicatorPosition::kBottom:
316         icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
317             arrow_image_, SkBitmapOperations::ROTATION_90_CW));
318         return;
319     }
320   }
321
322  private:
323   // Possibly rotated image of an arrow based on |vector_icon_|.
324   raw_ptr<views::ImageView, ExperimentalAsh> icon_ = nullptr;  // NOT OWNED
325   // Label containing name of target display in the pill.
326   raw_ptr<views::Label, ExperimentalAsh> text_label_ = nullptr;  // NOT OWNED
327   gfx::ImageSkia arrow_image_;
328   // The side of the display indicator is postioned on. Used to determine arrow
329   // direction and placement.
330   IndicatorPosition position_ = IndicatorPosition::kRight;
331 };
332
333 // -----------------------------------------------------------------------------
334 // DisplayAlignmentIndicator:
335
336 // static
337 std::unique_ptr<DisplayAlignmentIndicator> DisplayAlignmentIndicator::Create(
338     const display::Display& src_display,
339     const gfx::Rect& bounds) {
340   // Using `new` to access a non-public constructor.
341   return base::WrapUnique(
342       new DisplayAlignmentIndicator(src_display, bounds, ""));
343 }
344
345 // static
346 std::unique_ptr<DisplayAlignmentIndicator>
347 DisplayAlignmentIndicator::CreateWithPill(const display::Display& src_display,
348                                           const gfx::Rect& bounds,
349                                           const std::string& target_name) {
350   // Using `new` to access a non-public constructor.
351   return base::WrapUnique(
352       new DisplayAlignmentIndicator(src_display, bounds, target_name));
353 }
354
355 DisplayAlignmentIndicator::DisplayAlignmentIndicator(
356     const display::Display& src_display,
357     const gfx::Rect& bounds,
358     const std::string& target_name)
359     : display_id_(src_display.id()) {
360   gfx::Rect thickened_bounds = bounds;
361   AdjustIndicatorBounds(src_display, &thickened_bounds);
362
363   views::Widget::InitParams indicator_widget_params =
364       CreateInitParams(src_display.id(), "IndicatorHighlight");
365   indicator_widget_params.shadow_elevation = kHighlightShadowElevation;
366
367   indicator_widget_.Init(std::move(indicator_widget_params));
368   indicator_widget_.SetVisibilityChangedAnimationsEnabled(false);
369   indicator_view_ = indicator_widget_.SetContentsView(
370       std::make_unique<IndicatorHighlightView>(src_display));
371   indicator_widget_.SetBounds(thickened_bounds);
372
373   const IndicatorPosition indicator_position =
374       GetIndicatorPosition(src_display, thickened_bounds);
375   indicator_view_->SetPosition(indicator_position);
376
377   // Only create IndicatorPillView when |target_name| is specified.
378   if (!target_name.empty()) {
379     pill_widget_ = std::make_unique<views::Widget>();
380     pill_widget_->Init(CreateInitParams(src_display.id(), "IndicatorPill"));
381     pill_widget_->SetVisibilityChangedAnimationsEnabled(false);
382     pill_view_ = pill_widget_->SetContentsView(
383         std::make_unique<IndicatorPillView>(base::UTF8ToUTF16(target_name)));
384     pill_view_->SetPosition(indicator_position);
385
386     gfx::Size pill_size = pill_view_->GetPreferredSize();
387     gfx::Rect pill_bounds = gfx::Rect(
388         GetPillOrigin(pill_size, indicator_position, thickened_bounds),
389         pill_size);
390     pill_widget_->SetBounds(pill_bounds);
391   }
392
393   Show();
394 }
395
396 DisplayAlignmentIndicator::~DisplayAlignmentIndicator() = default;
397
398 void DisplayAlignmentIndicator::Show() {
399   indicator_widget_.Show();
400
401   if (pill_widget_)
402     pill_widget_->Show();
403 }
404
405 void DisplayAlignmentIndicator::Hide() {
406   indicator_widget_.Hide();
407
408   if (pill_widget_)
409     pill_widget_->Hide();
410 }
411
412 void DisplayAlignmentIndicator::Update(const display::Display& display,
413                                        gfx::Rect bounds) {
414   DCHECK(!pill_widget_);
415
416   AdjustIndicatorBounds(display, &bounds);
417   const IndicatorPosition src_direction = GetIndicatorPosition(display, bounds);
418   indicator_view_->SetPosition(src_direction);
419   indicator_widget_.SetBounds(bounds);
420 }
421
422 }  // namespace ash