/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import com.facebook.react.uimanager.Spacing;
/* package */ final class DrawBorder extends AbstractDrawBorder {
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final float[] TMP_FLOAT_ARRAY = new float[4];
private static final int BORDER_STYLE_SOLID = 0;
private static final int BORDER_STYLE_DOTTED = 1;
private static final int BORDER_STYLE_DASHED = 2;
private static final int BORDER_LEFT_COLOR_SET = 1 << 1;
private static final int BORDER_TOP_COLOR_SET = 1 << 2;
private static final int BORDER_RIGHT_COLOR_SET = 1 << 3;
private static final int BORDER_BOTTOM_COLOR_SET = 1 << 4;
private static final int BORDER_PATH_EFFECT_DIRTY = 1 << 5;
private float mBorderLeftWidth;
private float mBorderTopWidth;
private float mBorderRightWidth;
private float mBorderBottomWidth;
private int mBorderLeftColor;
private int mBorderTopColor;
private int mBorderRightColor;
private int mBorderBottomColor;
private int mBorderStyle = BORDER_STYLE_SOLID;
private int mBackgroundColor;
private @Nullable DashPathEffect mPathEffectForBorderStyle;
private @Nullable Path mPathForBorder;
public void setBorderWidth(int position, float borderWidth) {
switch (position) {
case Spacing.LEFT:
mBorderLeftWidth = borderWidth;
break;
case Spacing.TOP:
mBorderTopWidth = borderWidth;
break;
case Spacing.RIGHT:
mBorderRightWidth = borderWidth;
break;
case Spacing.BOTTOM:
mBorderBottomWidth = borderWidth;
break;
case Spacing.ALL:
setBorderWidth(borderWidth);
break;
}
}
public float getBorderWidth(int position) {
switch (position) {
case Spacing.LEFT:
return mBorderLeftWidth;
case Spacing.TOP:
return mBorderTopWidth;
case Spacing.RIGHT:
return mBorderRightWidth;
case Spacing.BOTTOM:
return mBorderBottomWidth;
case Spacing.ALL:
return getBorderWidth();
}
return 0.0f;
}
public void setBorderStyle(@Nullable String style) {
if ("dotted".equals(style)) {
mBorderStyle = BORDER_STYLE_DOTTED;
} else if ("dashed".equals(style)) {
mBorderStyle = BORDER_STYLE_DASHED;
} else {
mBorderStyle = BORDER_STYLE_SOLID;
}
setFlag(BORDER_PATH_EFFECT_DIRTY);
}
public void resetBorderColor(int position) {
switch (position) {
case Spacing.LEFT:
resetFlag(BORDER_LEFT_COLOR_SET);
break;
case Spacing.TOP:
resetFlag(BORDER_TOP_COLOR_SET);
break;
case Spacing.RIGHT:
resetFlag(BORDER_RIGHT_COLOR_SET);
break;
case Spacing.BOTTOM:
resetFlag(BORDER_BOTTOM_COLOR_SET);
break;
case Spacing.ALL:
setBorderColor(Color.BLACK);
break;
}
}
public void setBorderColor(int position, int borderColor) {
switch (position) {
case Spacing.LEFT:
mBorderLeftColor = borderColor;
setFlag(BORDER_LEFT_COLOR_SET);
break;
case Spacing.TOP:
mBorderTopColor = borderColor;
setFlag(BORDER_TOP_COLOR_SET);
break;
case Spacing.RIGHT:
mBorderRightColor = borderColor;
setFlag(BORDER_RIGHT_COLOR_SET);
break;
case Spacing.BOTTOM:
mBorderBottomColor = borderColor;
setFlag(BORDER_BOTTOM_COLOR_SET);
break;
case Spacing.ALL:
setBorderColor(borderColor);
break;
}
}
public int getBorderColor(int position) {
int defaultColor = getBorderColor();
switch (position) {
case Spacing.LEFT:
return resolveBorderColor(BORDER_LEFT_COLOR_SET, mBorderLeftColor, defaultColor);
case Spacing.TOP:
return resolveBorderColor(BORDER_TOP_COLOR_SET, mBorderTopColor, defaultColor);
case Spacing.RIGHT:
return resolveBorderColor(BORDER_RIGHT_COLOR_SET, mBorderRightColor, defaultColor);
case Spacing.BOTTOM:
return resolveBorderColor(BORDER_BOTTOM_COLOR_SET, mBorderBottomColor, defaultColor);
case Spacing.ALL:
return defaultColor;
}
return defaultColor;
}
public void setBackgroundColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
}
public int getBackgroundColor() {
return mBackgroundColor;
}
@Override
protected void onDraw(Canvas canvas) {
if (getBorderRadius() >= 0.5f || getPathEffectForBorderStyle() != null) {
drawRoundedBorders(canvas);
} else {
drawRectangularBorders(canvas);
}
}
@Override
protected @Nullable DashPathEffect getPathEffectForBorderStyle() {
if (isFlagSet(BORDER_PATH_EFFECT_DIRTY)) {
switch (mBorderStyle) {
case BORDER_STYLE_DOTTED:
mPathEffectForBorderStyle = createDashPathEffect(getBorderWidth());
break;
case BORDER_STYLE_DASHED:
mPathEffectForBorderStyle = createDashPathEffect(getBorderWidth() * 3);
break;
default:
mPathEffectForBorderStyle = null;
break;
}
resetFlag(BORDER_PATH_EFFECT_DIRTY);
}
return mPathEffectForBorderStyle;
}
private void drawRoundedBorders(Canvas canvas) {
if (mBackgroundColor != 0) {
PAINT.setColor(mBackgroundColor);
canvas.drawPath(getPathForBorderRadius(), PAINT);
}
drawBorders(canvas);
}
/**
* @return true when border colors differs where two border sides meet (e.g. right and top border
* colors differ)
*/
private boolean isBorderColorDifferentAtIntersectionPoints() {
return isFlagSet(BORDER_TOP_COLOR_SET) ||
isFlagSet(BORDER_BOTTOM_COLOR_SET) ||
isFlagSet(BORDER_LEFT_COLOR_SET) ||
isFlagSet(BORDER_RIGHT_COLOR_SET);
}
private void drawRectangularBorders(Canvas canvas) {
int defaultColor = getBorderColor();
float defaultWidth = getBorderWidth();
float top = getTop();
float topWidth = resolveWidth(mBorderTopWidth, defaultWidth);
float bottomOfTheTop = top + topWidth;
int topColor = resolveBorderColor(BORDER_TOP_COLOR_SET, mBorderTopColor, defaultColor);
float bottom = getBottom();
float bottomWidth = resolveWidth(mBorderBottomWidth, defaultWidth);
float topOfTheBottom = bottom - bottomWidth;
int bottomColor = resolveBorderColor(BORDER_BOTTOM_COLOR_SET, mBorderBottomColor, defaultColor);
float left = getLeft();
float leftWidth = resolveWidth(mBorderLeftWidth, defaultWidth);
float rightOfTheLeft = left + leftWidth;
int leftColor = resolveBorderColor(BORDER_LEFT_COLOR_SET, mBorderLeftColor, defaultColor);
float right = getRight();
float rightWidth = resolveWidth(mBorderRightWidth, defaultWidth);
float leftOfTheRight = right - rightWidth;
int rightColor = resolveBorderColor(BORDER_RIGHT_COLOR_SET, mBorderRightColor, defaultColor);
boolean isDrawPathRequired = isBorderColorDifferentAtIntersectionPoints();
if (isDrawPathRequired && mPathForBorder == null) {
mPathForBorder = new Path();
}
// draw top
if (Color.alpha(topColor) != 0 && topWidth != 0) {
PAINT.setColor(topColor);
if (isDrawPathRequired) {
updatePathForTopBorder(
top,
bottomOfTheTop,
left,
rightOfTheLeft,
right,
leftOfTheRight);
canvas.drawPath(mPathForBorder, PAINT);
} else {
canvas.drawRect(left, top, right, bottomOfTheTop, PAINT);
}
}
// draw bottom
if (Color.alpha(bottomColor) != 0 && bottomWidth != 0) {
PAINT.setColor(bottomColor);
if (isDrawPathRequired) {
updatePathForBottomBorder(
bottom,
topOfTheBottom,
left,
rightOfTheLeft,
right,
leftOfTheRight);
canvas.drawPath(mPathForBorder, PAINT);
} else {
canvas.drawRect(left, topOfTheBottom, right, bottom, PAINT);
}
}
// draw left
if (Color.alpha(leftColor) != 0 && leftWidth != 0) {
PAINT.setColor(leftColor);
if (isDrawPathRequired) {
updatePathForLeftBorder(
top,
bottomOfTheTop,
bottom,
topOfTheBottom,
left,
rightOfTheLeft);
canvas.drawPath(mPathForBorder, PAINT);
} else {
canvas.drawRect(left, bottomOfTheTop, rightOfTheLeft, topOfTheBottom, PAINT);
}
}
// draw right
if (Color.alpha(rightColor) != 0 && rightWidth != 0) {
PAINT.setColor(rightColor);
if (isDrawPathRequired) {
updatePathForRightBorder(
top,
bottomOfTheTop,
bottom,
topOfTheBottom,
right,
leftOfTheRight);
canvas.drawPath(mPathForBorder, PAINT);
} else {
canvas.drawRect(leftOfTheRight, bottomOfTheTop, right, topOfTheBottom, PAINT);
}
}
// draw center
if (Color.alpha(mBackgroundColor) != 0) {
PAINT.setColor(mBackgroundColor);
canvas.drawRect(rightOfTheLeft, bottomOfTheTop, leftOfTheRight, topOfTheBottom, PAINT);
}
}
private void updatePathForTopBorder(
float top,
float bottomOfTheTop,
float left,
float rightOfTheLeft,
float right,
float leftOfTheRight) {
if (mPathForBorder == null) {
mPathForBorder = new Path();
}
mPathForBorder.reset();
mPathForBorder.moveTo(left, top);
mPathForBorder.lineTo(rightOfTheLeft, bottomOfTheTop);
mPathForBorder.lineTo(leftOfTheRight, bottomOfTheTop);
mPathForBorder.lineTo(right, top);
mPathForBorder.lineTo(left, top);
}
private void updatePathForBottomBorder(
float bottom,
float topOfTheBottom,
float left,
float rightOfTheLeft,
float right,
float leftOfTheRight) {
if (mPathForBorder == null) {
mPathForBorder = new Path();
}
mPathForBorder.reset();
mPathForBorder.moveTo(left, bottom);
mPathForBorder.lineTo(right, bottom);
mPathForBorder.lineTo(leftOfTheRight, topOfTheBottom);
mPathForBorder.lineTo(rightOfTheLeft, topOfTheBottom);
mPathForBorder.lineTo(left, bottom);
}
private void updatePathForLeftBorder(
float top,
float bottomOfTheTop,
float bottom,
float topOfTheBottom,
float left,
float rightOfTheLeft) {
if (mPathForBorder == null) {
mPathForBorder = new Path();
}
mPathForBorder.reset();
mPathForBorder.moveTo(left, top);
mPathForBorder.lineTo(rightOfTheLeft, bottomOfTheTop);
mPathForBorder.lineTo(rightOfTheLeft, topOfTheBottom);
mPathForBorder.lineTo(left, bottom);
mPathForBorder.lineTo(left, top);
}
private void updatePathForRightBorder(
float top,
float bottomOfTheTop,
float bottom,
float topOfTheBottom,
float right,
float leftOfTheRight) {
if (mPathForBorder == null) {
mPathForBorder = new Path();
}
mPathForBorder.reset();
mPathForBorder.moveTo(right, top);
mPathForBorder.lineTo(right, bottom);
mPathForBorder.lineTo(leftOfTheRight, topOfTheBottom);
mPathForBorder.lineTo(leftOfTheRight, bottomOfTheTop);
mPathForBorder.lineTo(right, top);
}
private int resolveBorderColor(int flag, int color, int defaultColor) {
return isFlagSet(flag) ? color : defaultColor;
}
private static float resolveWidth(float width, float defaultWidth) {
return (width == 0 || /* check for NaN */ width != width) ? defaultWidth : width;
}
private static DashPathEffect createDashPathEffect(float borderWidth) {
for (int i = 0; i < 4; ++i) {
TMP_FLOAT_ARRAY[i] = borderWidth;
}
return new DashPathEffect(TMP_FLOAT_ARRAY, 0);
}
}