1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.assist.ui; 18 19 import android.graphics.Path; 20 import android.graphics.PointF; 21 22 import java.util.ArrayList; 23 import java.util.List; 24 25 /** 26 * Handles paths along device corners. 27 */ 28 public abstract class CornerPathRenderer { 29 30 // The maximum delta between the corner curve and points approximating the corner curve. 31 private static final float ACCEPTABLE_ERROR = 0.1f; 32 33 /** 34 * For convenience, labels the four device corners. 35 * 36 * Corners must be listed in CCW order, otherwise we'll break rotation. 37 */ 38 public enum Corner { 39 BOTTOM_LEFT, 40 BOTTOM_RIGHT, 41 TOP_RIGHT, 42 TOP_LEFT 43 } 44 45 /** 46 * Returns the path along the inside of a corner (centered insetAmountPx from the corner's 47 * edge). 48 */ getInsetPath(Corner corner, float insetAmountPx)49 public Path getInsetPath(Corner corner, float insetAmountPx) { 50 return approximateInnerPath(getCornerPath(corner), -insetAmountPx); 51 } 52 53 /** 54 * Returns the path of a corner (centered on the exact corner). Must be implemented by extending 55 * classes, based on the device-specific rounded corners. A default implementation for circular 56 * corners is provided by CircularCornerPathRenderer. 57 */ getCornerPath(Corner corner)58 public abstract Path getCornerPath(Corner corner); 59 approximateInnerPath(Path input, float delta)60 private Path approximateInnerPath(Path input, float delta) { 61 List<PointF> points = shiftBy(getApproximatePoints(input), delta); 62 return toPath(points); 63 } 64 getApproximatePoints(Path path)65 private ArrayList<PointF> getApproximatePoints(Path path) { 66 float[] rawInput = path.approximate(ACCEPTABLE_ERROR); 67 68 ArrayList<PointF> output = new ArrayList<>(); 69 70 for (int i = 0; i < rawInput.length; i = i + 3) { 71 output.add(new PointF(rawInput[i + 1], rawInput[i + 2])); 72 } 73 74 return output; 75 } 76 shiftBy(ArrayList<PointF> input, float delta)77 private ArrayList<PointF> shiftBy(ArrayList<PointF> input, float delta) { 78 ArrayList<PointF> output = new ArrayList<>(); 79 80 for (int i = 0; i < input.size(); i++) { 81 PointF point = input.get(i); 82 PointF normal = normalAt(input, i); 83 PointF shifted = 84 new PointF(point.x + (normal.x * delta), point.y + (normal.y * delta)); 85 output.add(shifted); 86 } 87 return output; 88 } 89 toPath(List<PointF> points)90 private Path toPath(List<PointF> points) { 91 Path path = new Path(); 92 if (points.size() > 0) { 93 path.moveTo(points.get(0).x, points.get(0).y); 94 for (PointF point : points.subList(1, points.size())) { 95 path.lineTo(point.x, point.y); 96 } 97 } 98 return path; 99 } 100 normalAt(List<PointF> points, int index)101 private PointF normalAt(List<PointF> points, int index) { 102 PointF d1; 103 if (index == 0) { 104 d1 = new PointF(0, 0); 105 } else { 106 PointF point = points.get(index); 107 PointF previousPoint = points.get(index - 1); 108 d1 = new PointF((point.x - previousPoint.x), (point.y - previousPoint.y)); 109 } 110 111 PointF d2; 112 if (index == (points.size() - 1)) { 113 d2 = new PointF(0, 0); 114 } else { 115 PointF point = points.get(index); 116 PointF nextPoint = points.get(index + 1); 117 d2 = new PointF((nextPoint.x - point.x), (nextPoint.y - point.y)); 118 } 119 120 return rotate90Ccw(normalize(new PointF(d1.x + d2.x, d1.y + d2.y))); 121 } 122 rotate90Ccw(PointF input)123 private PointF rotate90Ccw(PointF input) { 124 return new PointF(-input.y, input.x); 125 } 126 magnitude(PointF point)127 private float magnitude(PointF point) { 128 return (float) Math.sqrt((point.x * point.x) + (point.y * point.y)); 129 } 130 normalize(PointF point)131 private PointF normalize(PointF point) { 132 float magnitude = magnitude(point); 133 if (magnitude == 0.f) { 134 return point; 135 } 136 137 float normal = 1 / magnitude; 138 return new PointF((point.x * normal), (point.y * normal)); 139 } 140 } 141