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