summaryrefslogtreecommitdiff
path: root/chromium/ui/views/controls/focus_ring.cc
blob: f5fbb33ec887684778abd8d16bb994771a0bd1b1 (plain)
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
// Copyright 2016 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 "ui/views/controls/focus_ring.h"

#include "ui/gfx/canvas.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/view_properties.h"

namespace {

ui::NativeTheme::ColorId ColorIdForValidity(bool valid) {
  return valid ? ui::NativeTheme::kColorId_FocusedBorderColor
               : ui::NativeTheme::kColorId_AlertSeverityHigh;
}

}  // namespace

namespace views {

const char FocusRing::kViewClassName[] = "FocusRing";

// static
std::unique_ptr<FocusRing> FocusRing::Install(View* parent) {
  auto ring = base::WrapUnique<FocusRing>(new FocusRing(parent));
  ring->set_owned_by_client();
  parent->AddChildView(ring.get());
  parent->AddObserver(ring.get());
  ring->Layout();
  ring->SchedulePaint();
  return ring;
}

// static
bool FocusRing::IsPathUseable(const SkPath& path) {
  return !path.isEmpty() && (path.isRect(nullptr) || path.isOval(nullptr) ||
                             path.isRRect(nullptr));
}

void FocusRing::SetPath(const SkPath& path) {
  path_ = IsPathUseable(path) ? path : SkPath();
  SchedulePaint();
}

void FocusRing::SetInvalid(bool invalid) {
  invalid_ = invalid;
  SchedulePaint();
}

void FocusRing::SetHasFocusPredicate(const ViewPredicate& predicate) {
  has_focus_predicate_ = predicate;
  SchedulePaint();
}

const char* FocusRing::GetClassName() const {
  return kViewClassName;
}

void FocusRing::Layout() {
  // The focus ring handles its own sizing, which is simply to fill the parent
  // and extend a little beyond its borders.
  gfx::Rect focus_bounds = parent()->GetLocalBounds();
  focus_bounds.Inset(gfx::Insets(PlatformStyle::kFocusHaloInset));
  SetBoundsRect(focus_bounds);
}

void FocusRing::OnPaint(gfx::Canvas* canvas) {
  if (!has_focus_predicate_(parent()))
    return;

  SkColor base_color =
      GetNativeTheme()->GetSystemColor(ColorIdForValidity(!invalid_));

  cc::PaintFlags paint;
  paint.setAntiAlias(true);
  paint.setColor(SkColorSetA(base_color, 0x66));
  paint.setStyle(cc::PaintFlags::kStroke_Style);
  paint.setStrokeWidth(PlatformStyle::kFocusHaloThickness);

  SkPath path = path_;
  if (path.isEmpty()) {
    SkPath* highlight_path = parent()->GetProperty(kHighlightPathKey);
    if (highlight_path)
      path = *highlight_path;
  }
  if (path.isEmpty())
    path.addRect(RectToSkRect(parent()->GetLocalBounds()));

  DCHECK(IsPathUseable(path));
  SkRect bounds;
  SkRRect rbounds;
  if (path.isRect(&bounds)) {
    canvas->sk_canvas()->drawRRect(RingRectFromPathRect(bounds), paint);
  } else if (path.isOval(&bounds)) {
    gfx::RectF rect = gfx::SkRectToRectF(bounds);
    View::ConvertRectToTarget(view_, this, &rect);
    canvas->sk_canvas()->drawRRect(SkRRect::MakeOval(gfx::RectFToSkRect(rect)),
                                   paint);
  } else if (path.isRRect(&rbounds)) {
    canvas->sk_canvas()->drawRRect(RingRectFromPathRect(rbounds), paint);
  }
}

void FocusRing::OnViewFocused(View* view) {
  SchedulePaint();
}

void FocusRing::OnViewBlurred(View* view) {
  SchedulePaint();
}

FocusRing::FocusRing(View* parent) : view_(parent) {
  // A layer is necessary to paint beyond the parent's bounds.
  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);
  // Don't allow the view to process events.
  set_can_process_events_within_subtree(false);

  has_focus_predicate_ = [](View* p) -> bool { return p->HasFocus(); };
}

FocusRing::~FocusRing() {
  if (parent())
    parent()->RemoveObserver(this);
}

SkRRect FocusRing::RingRectFromPathRect(const SkRect& rect) const {
  double thickness = PlatformStyle::kFocusHaloThickness / 2.f;
  double corner_radius = FocusableBorder::kCornerRadiusDp + thickness;
  return RingRectFromPathRect(
      SkRRect::MakeRectXY(rect, corner_radius, corner_radius));
}

SkRRect FocusRing::RingRectFromPathRect(const SkRRect& rrect) const {
  double thickness = PlatformStyle::kFocusHaloThickness / 2.f;
  gfx::RectF r = gfx::SkRectToRectF(rrect.rect());
  View::ConvertRectToTarget(view_, this, &r);

  SkRRect skr =
      rrect.makeOffset(r.x() - rrect.rect().x(), r.y() - rrect.rect().y());

  // The focus indicator should hug the normal border, when present (as in the
  // case of text buttons). Since it's drawn outside the parent view, increase
  // the rounding slightly by adding half the ring thickness.
  skr.inset(PlatformStyle::kFocusHaloInset, PlatformStyle::kFocusHaloInset);
  skr.inset(thickness, thickness);

  return skr;
}

}  // namespace views