1 // Copyright 2014 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/accessibility/accessibility_focus_ring_layer.h"
6
7 #include "ash/shell.h"
8 #include "base/bind.h"
9 #include "third_party/skia/include/core/SkPaint.h"
10 #include "third_party/skia/include/core/SkPath.h"
11 #include "third_party/skia/include/effects/SkDashPathEffect.h"
12 #include "ui/aura/window.h"
13 #include "ui/compositor/layer.h"
14 #include "ui/gfx/canvas.h"
15 #include "ui/wm/core/coordinate_conversion.h"
16
17 namespace ash {
18
19 namespace {
20
21 // The number of density-indpendent pixels in the color gradient that fades to
22 // transparent.
23 constexpr int kGradientWidth = 6;
24 constexpr int kDefaultStrokeWidth = 2;
25 constexpr float kDashLengthDip = 3.f;
26 constexpr float kGapLengthDip = 5.f;
27
sign(int x)28 int sign(int x) {
29 return ((x > 0) ? 1 : (x == 0) ? 0 : -1);
30 }
31
MakePath(const AccessibilityFocusRing & input_ring,int outset,const gfx::Vector2d & offset)32 SkPath MakePath(const AccessibilityFocusRing& input_ring,
33 int outset,
34 const gfx::Vector2d& offset) {
35 AccessibilityFocusRing ring = input_ring;
36
37 for (int i = 0; i < 36; i++) {
38 gfx::Point p = input_ring.points[i];
39 gfx::Point prev;
40 gfx::Point next;
41
42 int prev_index = i;
43 do {
44 prev_index = (prev_index + 35) % 36;
45 prev = input_ring.points[prev_index];
46 } while (prev.x() == p.x() && prev.y() == p.y() && prev_index != i);
47
48 int next_index = i;
49 do {
50 next_index = (next_index + 1) % 36;
51 next = input_ring.points[next_index];
52 } while (next.x() == p.x() && next.y() == p.y() && next_index != i);
53
54 gfx::Point delta0 =
55 gfx::Point(sign(p.x() - prev.x()), sign(p.y() - prev.y()));
56 gfx::Point delta1 =
57 gfx::Point(sign(next.x() - p.x()), sign(next.y() - p.y()));
58
59 if (delta0.x() == delta1.x() && delta0.y() == delta1.y()) {
60 ring.points[i] =
61 gfx::Point(input_ring.points[i].x() + outset * delta0.y(),
62 input_ring.points[i].y() - outset * delta0.x());
63 } else {
64 ring.points[i] = gfx::Point(
65 input_ring.points[i].x() + ((i + 13) % 36 >= 18 ? outset : -outset),
66 input_ring.points[i].y() + ((i + 4) % 36 >= 18 ? outset : -outset));
67 }
68 }
69
70 SkPath path;
71 gfx::Point p0 = ring.points[0] - offset;
72 path.moveTo(SkIntToScalar(p0.x()), SkIntToScalar(p0.y()));
73 for (int i = 0; i < 12; i++) {
74 int index0 = ((3 * i) + 1) % 36;
75 int index1 = ((3 * i) + 2) % 36;
76 int index2 = ((3 * i) + 3) % 36;
77 gfx::Point p0 = ring.points[index0] - offset;
78 gfx::Point p1 = ring.points[index1] - offset;
79 gfx::Point p2 = ring.points[index2] - offset;
80 path.lineTo(SkIntToScalar(p0.x()), SkIntToScalar(p0.y()));
81 path.quadTo(SkIntToScalar(p1.x()), SkIntToScalar(p1.y()),
82 SkIntToScalar(p2.x()), SkIntToScalar(p2.y()));
83 }
84
85 return path;
86 }
87
88 } // namespace
89
AccessibilityFocusRingLayer(AccessibilityLayerDelegate * delegate)90 AccessibilityFocusRingLayer::AccessibilityFocusRingLayer(
91 AccessibilityLayerDelegate* delegate)
92 : FocusRingLayer(delegate) {}
93
94 AccessibilityFocusRingLayer::~AccessibilityFocusRingLayer() = default;
95
Set(const AccessibilityFocusRing & ring)96 void AccessibilityFocusRingLayer::Set(const AccessibilityFocusRing& ring) {
97 ring_ = ring;
98
99 gfx::Rect bounds = ring.GetBounds();
100 display::Display display =
101 display::Screen::GetScreen()->GetDisplayMatching(bounds);
102 aura::Window* root_window = Shell::GetRootWindowForDisplayId(display.id());
103
104 if (SkColorGetA(background_color_) > 0) {
105 bounds = display.bounds();
106 } else {
107 int inset = kGradientWidth;
108 bounds.Inset(-inset, -inset, -inset, -inset);
109 }
110 ::wm::ConvertRectFromScreen(root_window, &bounds);
111 CreateOrUpdateLayer(root_window, "AccessibilityFocusRing", bounds);
112 }
113
SetAppearance(FocusRingType type,SkColor color,SkColor secondary_color,SkColor background_color)114 void AccessibilityFocusRingLayer::SetAppearance(FocusRingType type,
115 SkColor color,
116 SkColor secondary_color,
117 SkColor background_color) {
118 SetColor(color);
119 type_ = type;
120 secondary_color_ = secondary_color;
121 background_color_ = background_color;
122 }
123
OnPaintLayer(const ui::PaintContext & context)124 void AccessibilityFocusRingLayer::OnPaintLayer(
125 const ui::PaintContext& context) {
126 ui::PaintRecorder recorder(context, layer()->size());
127
128 if (SkColorGetA(background_color_) > 0)
129 DrawFocusBackground(recorder);
130
131 cc::PaintFlags flags;
132 flags.setAntiAlias(true);
133 flags.setStyle(cc::PaintFlags::kStroke_Style);
134
135 switch (type_) {
136 case FocusRingType::GLOW:
137 DrawGlowFocusRing(recorder, flags);
138 break;
139 case FocusRingType::SOLID:
140 DrawSolidFocusRing(recorder, flags);
141 break;
142 case FocusRingType::DASHED:
143 DrawDashedFocusRing(recorder, flags);
144 break;
145 }
146 }
147
DrawSolidFocusRing(ui::PaintRecorder & recorder,cc::PaintFlags & flags)148 void AccessibilityFocusRingLayer::DrawSolidFocusRing(
149 ui::PaintRecorder& recorder,
150 cc::PaintFlags& flags) {
151 if (!has_custom_color())
152 NOTREACHED();
153
154 SkPath path;
155 gfx::Vector2d offset = layer()->bounds().OffsetFromOrigin();
156 flags.setColor(custom_color());
157 flags.setStrokeWidth(kDefaultStrokeWidth);
158
159 path = MakePath(ring_, 0, offset);
160 recorder.canvas()->DrawPath(path, flags);
161
162 flags.setColor(secondary_color_);
163 path = MakePath(ring_, kDefaultStrokeWidth, offset);
164 recorder.canvas()->DrawPath(path, flags);
165 }
166
DrawDashedFocusRing(ui::PaintRecorder & recorder,cc::PaintFlags & flags)167 void AccessibilityFocusRingLayer::DrawDashedFocusRing(
168 ui::PaintRecorder& recorder,
169 cc::PaintFlags& flags) {
170 if (!has_custom_color())
171 NOTREACHED();
172
173 SkPath path;
174 gfx::Vector2d offset = layer()->bounds().OffsetFromOrigin();
175
176 SkScalar intervals[] = {kDashLengthDip, kGapLengthDip};
177 int intervals_length = 2;
178 flags.setPathEffect(SkDashPathEffect::Make(intervals, intervals_length, 0));
179
180 // To keep the dashes properly lined up, we will draw the outside line first,
181 // and cover it with the inner line.
182 flags.setColor(secondary_color_);
183 flags.setStrokeWidth(3 * kDefaultStrokeWidth);
184
185 path = MakePath(ring_, 0, offset);
186 recorder.canvas()->DrawPath(path, flags);
187
188 flags.setColor(custom_color());
189 flags.setStrokeWidth(kDefaultStrokeWidth);
190
191 path = MakePath(ring_, 0, offset);
192 recorder.canvas()->DrawPath(path, flags);
193 }
194
DrawGlowFocusRing(ui::PaintRecorder & recorder,cc::PaintFlags & flags)195 void AccessibilityFocusRingLayer::DrawGlowFocusRing(ui::PaintRecorder& recorder,
196 cc::PaintFlags& flags) {
197 if (!has_custom_color())
198 NOTREACHED();
199 SkColor base_color = custom_color();
200
201 SkPath path;
202 gfx::Vector2d offset = layer()->bounds().OffsetFromOrigin();
203 flags.setStrokeWidth(kDefaultStrokeWidth);
204
205 const int w = kGradientWidth;
206 for (int i = 0; i < w; ++i) {
207 flags.setColor(SkColorSetA(base_color, 255 * (w - i) * (w - i) / (w * w)));
208 path = MakePath(ring_, i, offset);
209 recorder.canvas()->DrawPath(path, flags);
210 }
211 }
212
DrawFocusBackground(ui::PaintRecorder & recorder)213 void AccessibilityFocusRingLayer::DrawFocusBackground(
214 ui::PaintRecorder& recorder) {
215 recorder.canvas()->DrawColor(background_color_);
216
217 gfx::Vector2d offset = layer()->bounds().OffsetFromOrigin();
218 SkPath path = MakePath(ring_, 0, offset);
219 cc::PaintFlags flags;
220 flags.setStyle(cc::PaintFlags::kFill_Style);
221 flags.setBlendMode(SkBlendMode::kClear);
222 flags.setColor(SkColorSetARGB(0, 0, 0, 0));
223 recorder.canvas()->DrawPath(path, flags);
224 }
225
226 } // namespace ash
227