File: display_alignment_indicator.cc

package info (click to toggle)
chromium 120.0.6099.224-1~deb11u1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,112,112 kB
  • sloc: cpp: 32,907,025; ansic: 8,148,123; javascript: 3,679,536; python: 2,031,248; asm: 959,718; java: 804,675; xml: 617,256; sh: 111,417; objc: 100,835; perl: 88,443; cs: 53,032; makefile: 29,579; fortran: 24,137; php: 21,162; tcl: 21,147; sql: 20,809; ruby: 17,735; pascal: 12,864; yacc: 8,045; lisp: 3,388; lex: 1,323; ada: 727; awk: 329; jsp: 267; csh: 117; exp: 43; sed: 37
file content (422 lines) | stat: -rw-r--r-- 15,944 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/display/display_alignment_indicator.h"

#include "ash/public/cpp/shell_window_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"

namespace ash {

namespace {

// Constants for indicator highlights.
constexpr SkColor kEdgeHighlightColor = gfx::kGoogleBlue600;
constexpr int kHighlightShadowElevation = 2;

// Thickness (and radius) of indicator highlight is dependent on resolution.
// If display has resolution smaller than 1440p, then its thickness is
// |kHighlightRadiusSub1440p|. Otherwise, use |kHighlightRadius1440p|.
constexpr int kHighlightRadiusSub1440p = 4;
constexpr int kHighlightRadius1440p = 6;
constexpr int kHighlightSizeChangeRes = 1440;

// Constants for pill theme.
// White with ~60% opacity.
constexpr SkColor kPillBackgroundColor = SkColorSetARGB(0x99, 0xFF, 0xFF, 0xFF);
constexpr SkColor kPillTextColor = gfx::kGoogleBlue600;
constexpr int kPillBackgroundBlur = 10;

// Constants for pill layout.
constexpr int kPillRadius = 12;
constexpr int kMaxPillWidth = 192;
constexpr int kPillHeight = 32;
constexpr int kTextMarginNormal = 24;
constexpr int kTextMarginElided = 20;
// Distance between the indicator highlight and the pill.
constexpr int kPillMargin = 20;

// Constants for arrow layout.
constexpr int kArrowSize = 28;
constexpr int kArrowHorizontalMargin = 12;
constexpr int kArrowVerticalMargin = (kPillHeight - kArrowSize) / 2;
constexpr int kArrowAllocatedWidth =
    kArrowHorizontalMargin + kArrowSize + kArrowHorizontalMargin;

enum class IndicatorPosition { kTop, kRight, kBottom, kLeft };

// Returns IndicatorPosition given the bounds of an indicator highlight along
// with its corresponding display.
IndicatorPosition GetIndicatorPosition(const display::Display& src_display,
                                       const gfx::Rect& indicator_bounds) {
  const gfx::Point midpoint = src_display.bounds().CenterPoint();

  // Horizontal shared edge (kTop or kBottom)
  if (indicator_bounds.width() > indicator_bounds.height()) {
    return (indicator_bounds.y() < midpoint.y()) ? IndicatorPosition::kTop
                                                 : IndicatorPosition::kBottom;
  }
  // Vertical shared edge (kLeft or kRight)
  return (indicator_bounds.x() < midpoint.x()) ? IndicatorPosition::kLeft
                                               : IndicatorPosition::kRight;
}

// Indicator thickness is dependent on display resolution.
int GetIndicatorThickness(const gfx::Size& display_size) {
  return std::min(display_size.width(), display_size.height()) >
                 kHighlightSizeChangeRes
             ? kHighlightRadius1440p
             : kHighlightRadiusSub1440p;
}

// Adjust the indicator bounds to the correct thickness depending on the
// resolution of |display|.
void AdjustIndicatorBounds(const display::Display& display,
                           gfx::Rect* out_indicator_bounds) {
  const int indicator_thickness = GetIndicatorThickness(display.size());

  // Apply the new thickness to the indicator.
  if (out_indicator_bounds->height() > out_indicator_bounds->width())
    out_indicator_bounds->set_width(indicator_thickness);
  else
    out_indicator_bounds->set_height(indicator_thickness);

  // Create enough space for the full indicator on the x and y axis.
  const gfx::Point display_bottom_right = display.bounds().bottom_right();
  if (out_indicator_bounds->x() == (display_bottom_right.x() - 1))
    out_indicator_bounds->set_x(display_bottom_right.x() - indicator_thickness);
  else if (out_indicator_bounds->y() == (display_bottom_right.y() - 1))
    out_indicator_bounds->set_y(display_bottom_right.y() - indicator_thickness);
}

// Returns the pill's origin based on |pill_size| and the indicator's
// |thickened_bounds|.
gfx::Point GetPillOrigin(const gfx::Size& pill_size,
                         IndicatorPosition src_position,
                         const gfx::Rect& thickened_bounds) {
  gfx::Point pill_origin;
  switch (src_position) {
    case IndicatorPosition::kLeft:
      pill_origin = thickened_bounds.right_center();
      pill_origin.Offset(kPillMargin, -1 * kPillHeight / 2);
      break;
    case IndicatorPosition::kRight:
      pill_origin = thickened_bounds.left_center();
      pill_origin.Offset(-1 * kPillMargin - pill_size.width(),
                         -1 * kPillHeight / 2);
      break;
    case IndicatorPosition::kTop:
      pill_origin = thickened_bounds.bottom_center();
      pill_origin.Offset(-1 * pill_size.width() / 2, kPillMargin);
      break;
    case IndicatorPosition::kBottom:
      pill_origin = thickened_bounds.top_center();
      pill_origin.Offset(-1 * pill_size.width() / 2,
                         -1 * kPillMargin - kPillHeight);
      break;
  }

  return pill_origin;
}

views::Widget::InitParams CreateInitParams(int64_t display_id,
                                           const std::string& target_name) {
  views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);

  aura::Window* root =
      Shell::GetRootWindowControllerWithDisplayId(display_id)->GetRootWindow();

  params.parent = Shell::GetContainer(root, kShellWindowId_OverlayContainer);
  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  params.ownership =
      views::Widget::InitParams::Ownership::WIDGET_OWNS_NATIVE_WIDGET;
  params.activatable = views::Widget::InitParams::Activatable::kNo;
  params.accept_events = false;
  params.name = target_name;

  return params;
}

}  // namespace

// -----------------------------------------------------------------------------
// IndicatorHighlightView:
// View for the indicator highlight that renders on a shared edge of a given
// display.
class IndicatorHighlightView : public views::View {
 public:
  explicit IndicatorHighlightView(const display::Display& display)
      // Corner radius is the same as edge thickness.
      : corner_radius_(GetIndicatorThickness(display.size())) {
    SetPaintToLayer(ui::LAYER_TEXTURED);

    layer()->SetFillsBoundsOpaquely(false);
    layer()->SetIsFastRoundedCorner(true);
    SetBackground(views::CreateSolidBackground(kEdgeHighlightColor));
  }

  IndicatorHighlightView(const IndicatorHighlightView&) = delete;
  IndicatorHighlightView& operator=(const IndicatorHighlightView&) = delete;
  ~IndicatorHighlightView() override = default;

  // Sets which corners should be rounded depending on the position of the
  // indicator edge.
  void SetPosition(IndicatorPosition position) {
    gfx::RoundedCornersF corners;

    switch (position) {
      case IndicatorPosition::kLeft:
        corners = {0, corner_radius_, corner_radius_, 0};
        break;
      case IndicatorPosition::kRight:
        corners = {corner_radius_, 0, 0, corner_radius_};
        break;
      case IndicatorPosition::kTop:
        corners = {0, 0, corner_radius_, corner_radius_};
        break;
      case IndicatorPosition::kBottom:
        corners = {corner_radius_, corner_radius_, 0, 0};
        break;
    }

    layer()->SetRoundedCornerRadius(corners);
  }

  // views::View:
  const char* GetClassName() const override { return "IndicatorHighlightView"; }

 private:
  // Radius for the rounded rectangle highlight. Determined by display
  // resolution.
  float corner_radius_;
};

// -----------------------------------------------------------------------------
// IndicatorPillView:
// View for the pill with an arrow pointing to an indicator highlight and name
// of the target display.
class IndicatorPillView : public views::View {
 public:
  explicit IndicatorPillView(const std::u16string& text)
      :  // TODO(1070352): Replace current placeholder arrow in
         // IndicatorPillView
        icon_(AddChildView(std::make_unique<views::ImageView>())),
        text_label_(AddChildView(std::make_unique<views::Label>())),
        arrow_image_(
            CreateVectorIcon(kLockScreenArrowIcon, gfx::kGoogleBlue600)) {
    SetPaintToLayer(ui::LAYER_TEXTURED);

    layer()->SetFillsBoundsOpaquely(false);
    layer()->SetIsFastRoundedCorner(true);
    layer()->SetBackgroundBlur(kPillBackgroundBlur);
    layer()->SetRoundedCornerRadius(gfx::RoundedCornersF{kPillRadius});

    SetBackground(
        views::CreateRoundedRectBackground(kPillBackgroundColor, kPillRadius));

    text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    text_label_->SetEnabledColor(kPillTextColor);
    text_label_->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
    text_label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
    text_label_->SetVerticalAlignment(gfx::VerticalAlignment::ALIGN_MIDDLE);

    text_label_->SetText(text);

    icon_->SetImage(arrow_image_);
  }

  IndicatorPillView(const IndicatorPillView&) = delete;
  IndicatorPillView& operator=(const IndicatorPillView&) = delete;
  ~IndicatorPillView() override = default;

  // views::View:
  gfx::Size CalculatePreferredSize() const override {
    // Pill is laid out as:
    // ( | | text )
    // Has max width of kMaxPillWidth.

    const int text_width = text_label_->CalculatePreferredSize().width();
    const int width = kArrowAllocatedWidth + text_width + kTextMarginNormal;

    return gfx::Size(std::min(width, kMaxPillWidth), kPillHeight);
  }

  // views::View:
  void Layout() override {
    icon_->SetImageSize(gfx::Size(kArrowSize, kArrowSize));

    // IndicatorPosition::kRight is a special case for layout as it is the only
    // time where the arrow is on the right of the text instead of the usual
    // left.
    const int local_width = GetLocalBounds().width();
    int icon_x = position_ == IndicatorPosition::kRight
                     ? local_width - kArrowHorizontalMargin - kArrowSize
                     : kArrowHorizontalMargin;

    icon_->SetBoundsRect(
        gfx::Rect(icon_x, kArrowVerticalMargin, kArrowSize, kArrowSize));

    // If width of the pill is equal or greater than the max pill width, then
    // text is elided and thus side margin must be reduced.
    const int side_margin = CalculatePreferredSize().width() >= kMaxPillWidth
                                ? kTextMarginElided
                                : kTextMarginNormal;

    const int text_label_width =
        local_width - kArrowAllocatedWidth - side_margin;

    const int text_label_x = position_ == IndicatorPosition::kRight
                                 ? side_margin
                                 : kArrowAllocatedWidth;

    text_label_->SetBoundsRect(
        gfx::Rect(text_label_x, 0, text_label_width, kPillHeight));
  }

  // views::View:
  const char* GetClassName() const override { return "IndicatorPillView"; }

  // Rotates the arrow depending on indicator highlight's position on-screen.
  void SetPosition(IndicatorPosition position) {
    if (position_ == position)
      return;

    position_ = position;

    switch (position) {
      case IndicatorPosition::kLeft:
        icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
            arrow_image_, SkBitmapOperations::ROTATION_180_CW));
        return;
      case IndicatorPosition::kRight:
        // |arrow_image_| points to right by default; no rotation required.
        icon_->SetImage(arrow_image_);
        return;
      case IndicatorPosition::kTop:
        icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
            arrow_image_, SkBitmapOperations::ROTATION_270_CW));
        return;
      case IndicatorPosition::kBottom:
        icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
            arrow_image_, SkBitmapOperations::ROTATION_90_CW));
        return;
    }
  }

 private:
  // Possibly rotated image of an arrow based on |vector_icon_|.
  raw_ptr<views::ImageView, ExperimentalAsh> icon_ = nullptr;  // NOT OWNED
  // Label containing name of target display in the pill.
  raw_ptr<views::Label, ExperimentalAsh> text_label_ = nullptr;  // NOT OWNED
  gfx::ImageSkia arrow_image_;
  // The side of the display indicator is postioned on. Used to determine arrow
  // direction and placement.
  IndicatorPosition position_ = IndicatorPosition::kRight;
};

// -----------------------------------------------------------------------------
// DisplayAlignmentIndicator:

// static
std::unique_ptr<DisplayAlignmentIndicator> DisplayAlignmentIndicator::Create(
    const display::Display& src_display,
    const gfx::Rect& bounds) {
  // Using `new` to access a non-public constructor.
  return base::WrapUnique(
      new DisplayAlignmentIndicator(src_display, bounds, ""));
}

// static
std::unique_ptr<DisplayAlignmentIndicator>
DisplayAlignmentIndicator::CreateWithPill(const display::Display& src_display,
                                          const gfx::Rect& bounds,
                                          const std::string& target_name) {
  // Using `new` to access a non-public constructor.
  return base::WrapUnique(
      new DisplayAlignmentIndicator(src_display, bounds, target_name));
}

DisplayAlignmentIndicator::DisplayAlignmentIndicator(
    const display::Display& src_display,
    const gfx::Rect& bounds,
    const std::string& target_name)
    : display_id_(src_display.id()) {
  gfx::Rect thickened_bounds = bounds;
  AdjustIndicatorBounds(src_display, &thickened_bounds);

  views::Widget::InitParams indicator_widget_params =
      CreateInitParams(src_display.id(), "IndicatorHighlight");
  indicator_widget_params.shadow_elevation = kHighlightShadowElevation;

  indicator_widget_.Init(std::move(indicator_widget_params));
  indicator_widget_.SetVisibilityChangedAnimationsEnabled(false);
  indicator_view_ = indicator_widget_.SetContentsView(
      std::make_unique<IndicatorHighlightView>(src_display));
  indicator_widget_.SetBounds(thickened_bounds);

  const IndicatorPosition indicator_position =
      GetIndicatorPosition(src_display, thickened_bounds);
  indicator_view_->SetPosition(indicator_position);

  // Only create IndicatorPillView when |target_name| is specified.
  if (!target_name.empty()) {
    pill_widget_ = std::make_unique<views::Widget>();
    pill_widget_->Init(CreateInitParams(src_display.id(), "IndicatorPill"));
    pill_widget_->SetVisibilityChangedAnimationsEnabled(false);
    pill_view_ = pill_widget_->SetContentsView(
        std::make_unique<IndicatorPillView>(base::UTF8ToUTF16(target_name)));
    pill_view_->SetPosition(indicator_position);

    gfx::Size pill_size = pill_view_->GetPreferredSize();
    gfx::Rect pill_bounds = gfx::Rect(
        GetPillOrigin(pill_size, indicator_position, thickened_bounds),
        pill_size);
    pill_widget_->SetBounds(pill_bounds);
  }

  Show();
}

DisplayAlignmentIndicator::~DisplayAlignmentIndicator() = default;

void DisplayAlignmentIndicator::Show() {
  indicator_widget_.Show();

  if (pill_widget_)
    pill_widget_->Show();
}

void DisplayAlignmentIndicator::Hide() {
  indicator_widget_.Hide();

  if (pill_widget_)
    pill_widget_->Hide();
}

void DisplayAlignmentIndicator::Update(const display::Display& display,
                                       gfx::Rect bounds) {
  DCHECK(!pill_widget_);

  AdjustIndicatorBounds(display, &bounds);
  const IndicatorPosition src_direction = GetIndicatorPosition(display, bounds);
  indicator_view_->SetPosition(src_direction);
  indicator_widget_.SetBounds(bounds);
}

}  // namespace ash