/*******************************************************************************
* Copyright 2012-present Pixate, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.pixate.freestyle.cg.shapes;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import com.pixate.freestyle.cg.paints.PXPaint;
import com.pixate.freestyle.cg.strokes.PXStroke;
import com.pixate.freestyle.styling.infos.PXBorderInfo;
import com.pixate.freestyle.styling.infos.PXBorderInfo.PXBorderStyle;
import com.pixate.freestyle.util.ObjectPool;
import com.pixate.freestyle.util.Size;
public class PXBoxModel extends PXShape implements PXBoundable {
public enum PXBoxSizing {
CONTENT_BOX,
PADDING_BOX,
BORDER_BOX
}
private PXBorderInfo borderTop;
private PXBorderInfo borderRight;
private PXBorderInfo borderBottom;
private PXBorderInfo borderLeft;
private PXPath borderPathTop;
private PXPath borderPathRight;
private PXPath borderPathBottom;
private PXPath borderPathLeft;
private Size radiusTopLeft;
private Size radiusTopRight;
private Size radiusBottomRight;
private Size radiusBottomLeft;
private RectF bounds;
public PXBoxModel() {
this(new RectF(0f, 0f, 0f, 0f));
}
public PXBoxModel(RectF bounds) {
this.bounds = bounds;
borderTop = new PXBorderInfo();
borderRight = new PXBorderInfo();
borderBottom = new PXBorderInfo();
borderLeft = new PXBorderInfo();
radiusTopLeft = Size.ZERO;
radiusTopRight = Size.ZERO;
radiusBottomRight = Size.ZERO;
radiusBottomLeft = Size.ZERO;
}
// GETTERS
public PXPaint getBorderTopPaint() {
return borderTop.getPaint();
}
public PXBorderStyle getBorderTopStyle() {
return borderTop.getStyle();
}
public float getBorderTopWidth() {
return borderTop.getWidth();
}
public PXPaint getBorderRightPaint() {
return borderRight.getPaint();
}
public PXBorderStyle getBorderRightStyle() {
return borderRight.getStyle();
}
public float getBorderRightWidth() {
return borderRight.getWidth();
}
public PXPaint getBorderBottomPaint() {
return borderBottom.getPaint();
}
public PXBorderStyle getBorderBottomStyle() {
return borderBottom.getStyle();
}
public float getBorderBottomWidth() {
return borderBottom.getWidth();
}
public PXPaint getBorderLeftPaint() {
return borderLeft.getPaint();
}
public PXBorderStyle getBorderLeftStyle() {
return borderLeft.getStyle();
}
public float getBorderLeftWidth() {
return borderLeft.getWidth();
}
public RectF getBorderBounds() {
RectF bounds = this.bounds;
bounds.left -= borderLeft.getWidth();
bounds.top -= borderTop.getWidth();
float width = bounds.width() + borderLeft.getWidth() + borderRight.getWidth();
float height = bounds.height() + borderTop.getWidth() + borderBottom.getWidth();
bounds.right = bounds.left + width;
bounds.bottom = bounds.top + height;
return bounds;
}
public RectF getContentBounds() {
return bounds;
}
public Size getRadiusTopLeft() {
return radiusTopLeft;
}
public Size getRadiusTopRight() {
return radiusTopRight;
}
public Size getRadiusBottomRight() {
return radiusBottomRight;
}
public Size getRadiusBottomLeft() {
return radiusBottomLeft;
}
public boolean hasBorder() {
return borderTop.hasContent() || borderRight.hasContent() || borderBottom.hasContent()
|| borderLeft.hasContent();
}
public boolean hasCornerRadius() {
return Size.isNonZero(radiusTopLeft) || Size.isNonZero(radiusTopRight)
|| Size.isNonZero(radiusBottomRight) || Size.isNonZero(radiusBottomLeft);
}
public boolean isOpaque() {
return !hasCornerRadius() && borderTop.isOpaque() && borderRight.isOpaque()
&& borderBottom.isOpaque() && borderLeft.isOpaque();
}
// SETTERS
public void setBorderTopPaint(PXPaint paint) {
borderTop.setPaint(paint);
clearPath();
}
public void setBorderTopStyle(PXBorderStyle style) {
borderTop.setStyle(style);
clearPath();
}
public void setBorderTopWidth(float width) {
borderTop.setWidth(width);
clearPath();
}
public void setBorderRightPaint(PXPaint paint) {
borderRight.setPaint(paint);
clearPath();
}
public void setBorderRightStyle(PXBorderStyle style) {
borderRight.setStyle(style);
clearPath();
}
public void setBorderRightWidth(float width) {
borderRight.setWidth(width);
clearPath();
}
public void setBorderBottomPaint(PXPaint paint) {
borderBottom.setPaint(paint);
clearPath();
}
public void setBorderBottomStyle(PXBorderStyle style) {
borderBottom.setStyle(style);
clearPath();
}
public void setBorderBottomWidth(float width) {
borderBottom.setWidth(width);
clearPath();
}
public void setBorderLeftPaint(PXPaint paint) {
borderLeft.setPaint(paint);
clearPath();
}
public void setBorderLeftStyle(PXBorderStyle style) {
borderLeft.setStyle(style);
clearPath();
}
public void setBorderLeftWidth(float width) {
borderLeft.setWidth(width);
clearPath();
}
public void setBorderTop(PXPaint paint, float width, PXBorderStyle style) {
setBorderTopPaint(paint);
setBorderTopWidth(width);
setBorderTopStyle(style);
}
public void setBorderRight(PXPaint paint, float width, PXBorderStyle style) {
setBorderRightPaint(paint);
setBorderRightWidth(width);
setBorderRightStyle(style);
}
public void setBorderBottom(PXPaint paint, float width, PXBorderStyle style) {
setBorderBottomPaint(paint);
setBorderBottomWidth(width);
setBorderBottomStyle(style);
}
public void setBorderLeft(PXPaint paint, float width, PXBorderStyle style) {
setBorderLeftPaint(paint);
setBorderLeftWidth(width);
setBorderLeftStyle(style);
}
public void setBorderPaint(PXPaint paint) {
setBorderTopPaint(paint);
setBorderRightPaint(paint);
setBorderBottomPaint(paint);
setBorderLeftPaint(paint);
}
public void setBorderWidth(float width) {
setBorderTopWidth(width);
setBorderRightWidth(width);
setBorderBottomWidth(width);
setBorderLeftWidth(width);
}
public void setBorderStyle(PXBorderStyle style) {
setBorderTopStyle(style);
setBorderRightStyle(style);
setBorderBottomStyle(style);
setBorderLeftStyle(style);
}
public void setBorder(PXPaint paint, float width, PXBorderStyle style) {
setBorderTop(paint, width, style);
setBorderRight(paint, width, style);
setBorderBottom(paint, width, style);
setBorderLeft(paint, width, style);
}
public void setRadiusTopLeft(Size radius) {
radiusTopLeft = radius;
clearPath();
}
public void setRadiusTopRight(Size radius) {
radiusTopRight = radius;
clearPath();
}
public void setRadiusBottomRight(Size radius) {
radiusBottomRight = radius;
clearPath();
}
public void setRadiusBottomLeft(Size radius) {
radiusBottomLeft = radius;
clearPath();
}
/**
* Set the corner radius of all corners to the specified value.
*
* @param radius A corner radius
*/
public void setCornerRadius(float radius) {
setCornerRadii(new Size(radius, radius));
}
/**
* Set the corner radius of all corners to the specified value.
*
* @param radii The x and y radii.
*/
public void setCornerRadii(Size radii) {
radiusTopLeft = radii;
radiusTopRight = radii;
radiusBottomRight = radii;
radiusBottomLeft = radii;
}
// IMPLEMENTATIONS
/**
* @link {@link PXBoundable#getBounds()}
*/
public RectF getBounds() {
return bounds;
}
/**
* @link {@link PXBoundable#setBounds(RectF)}
*/
public void setBounds(RectF bounds) {
this.bounds = bounds;
}
// OVERRIDES
/**
* @link {@link PXShape#newPath()}
*/
@Override
protected Path newPath() {
Path resultPath = null;
if (!hasCornerRadius()) {
resultPath = ObjectPool.pathPool.checkOut();
resultPath.addRect(getBorderBounds(), Path.Direction.CW);
createBorders();
}
return resultPath;
}
/**
* @link {@link PXShape#renderChildren(Canvas)}
*/
@Override
protected void renderChildren(Canvas context) {
if (borderPathTop != null) {
borderPathTop.render(context);
}
if (borderPathRight != null) {
borderPathRight.render(context);
}
if (borderPathBottom != null) {
borderPathBottom.render(context);
}
if (borderPathLeft != null) {
borderPathLeft.render(context);
}
}
// PRIVATE METHODS
private float[] buildDashArray(float length, float width) {
float minWidth = 1.75f * width;
float count = (int) (length / minWidth);
float spacing = (length - (count * width)) / (count - 1.0f);
return new float[] { width, spacing };
}
private void createBorders() {
RectF borderBounds = getBorderBounds();
float borderLeftVal = borderBounds.left;
float borderRightVal = borderBounds.right;
float borderTopVal = borderBounds.top;
float borderBottomVal = borderBounds.bottom;
float contentLeftVal = bounds.left;
float contentRightVal = bounds.right;
float contentTopVal = bounds.top;
float contentBottomVal = bounds.bottom;
// reset borders
borderPathTop = borderPathRight = borderPathBottom = borderPathLeft = null;
// top
if (borderTop.hasContent()) {
borderPathTop = new PXPath();
switch (borderTop.getStyle()) {
case SOLID:
borderPathTop.moveTo(borderLeftVal, borderTopVal)
.lineTo(borderRightVal, borderTopVal)
.lineTo(contentRightVal, contentTopVal)
.lineTo(contentLeftVal, contentTopVal).close().setFillColor(fillColor);
break;
case DASHED: {
float y = (borderTopVal + contentTopVal) * 0.5f;
float width = borderTop.getWidth();
borderPathTop.moveTo(borderLeftVal, y).lineTo(borderRightVal, y);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderTop.getPaint());
stroke.setDashArray(buildDashArray(borderRightVal - borderLeftVal, 2.0f * width));
borderPathTop.setStroke(stroke);
break;
}
case DOTTED: {
float y = (borderTopVal + contentTopVal) * 0.5f;
float width = borderTop.getWidth();
borderPathTop.moveTo(borderLeftVal, y).lineTo(borderRightVal, y);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderTop.getPaint());
stroke.setDashArray(buildDashArray(borderRightVal - borderLeftVal, width));
borderPathTop.setStroke(stroke);
break;
}
case DOUBLE:
// TODO
break;
case GROOVE:
case INSET:
case OUTSET:
case RIDGE:
break;
// NOTE: We should never hit these cases
case NONE:
case HIDDEN:
default:
break;
}
}
// right
if (borderRight.hasContent()) {
borderPathRight = new PXPath();
switch (borderRight.getStyle()) {
case SOLID:
borderPathRight.moveTo(borderRightVal, borderTopVal)
.lineTo(borderRightVal, borderBottomVal)
.lineTo(contentRightVal, contentBottomVal)
.lineTo(contentRightVal, contentTopVal).close()
.setFillColor(borderRight.getPaint());
break;
case DASHED: {
float x = (borderRightVal + contentRightVal) * 0.5f;
float width = borderRight.getWidth();
borderPathRight.moveTo(x, borderTopVal).lineTo(x, borderBottomVal);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderRight.getPaint());
stroke.setDashArray(buildDashArray(borderBottomVal - borderTopVal, 2.0f * width));
borderPathRight.setStroke(stroke);
break;
}
case DOTTED: {
float x = (borderRightVal + contentRightVal) * 0.5f;
float width = borderRight.getWidth();
borderPathRight.moveTo(x, borderTopVal).lineTo(x, borderBottomVal);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderRight.getPaint());
stroke.setDashArray(buildDashArray(borderBottomVal - borderTopVal, width));
borderPathRight.setStroke(stroke);
break;
}
case DOUBLE:
case GROOVE:
case INSET:
case OUTSET:
case RIDGE:
break;
// NOTE: We should never hit these cases
case NONE:
case HIDDEN:
default:
break;
}
}
// bottom
if (borderBottom.hasContent()) {
borderPathBottom = new PXPath();
switch (borderBottom.getStyle()) {
case SOLID:
borderPathBottom.moveTo(contentRightVal, contentBottomVal)
.lineTo(borderRightVal, borderBottomVal)
.lineTo(borderLeftVal, borderBottomVal)
.lineTo(contentLeftVal, contentBottomVal).close()
.setFillColor(borderBottom.getPaint());
case DASHED: {
float y = (borderBottomVal + contentBottomVal) * 0.5f;
float width = borderBottom.getWidth();
borderPathBottom.moveTo(borderLeftVal, y).lineTo(borderRightVal, y);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderBottom.getPaint());
stroke.setDashArray(buildDashArray(borderRightVal - borderLeftVal, 2.0f * width));
borderPathBottom.setStroke(stroke);
break;
}
case DOTTED: {
float y = (borderBottomVal + contentBottomVal) * 0.5f;
float width = borderBottom.getWidth();
borderPathBottom.moveTo(borderLeftVal, y).lineTo(borderRightVal, y);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderBottom.getPaint());
stroke.setDashArray(buildDashArray(borderRightVal - borderLeftVal, width));
borderPathBottom.setStroke(stroke);
break;
}
case DOUBLE:
case GROOVE:
case INSET:
case OUTSET:
case RIDGE:
break;
// NOTE: We should never hit these cases
case NONE:
case HIDDEN:
default:
break;
}
}
// left
if (borderLeft.hasContent()) {
borderPathLeft = new PXPath();
switch (borderLeft.getStyle()) {
case SOLID:
borderPathLeft.moveTo(contentLeftVal, contentTopVal)
.lineTo(contentLeftVal, contentBottomVal)
.lineTo(borderLeftVal, borderBottomVal)
.lineTo(borderLeftVal, borderTopVal).close()
.setFillColor(borderLeft.getPaint());
break;
case DASHED: {
float x = (borderLeftVal + contentLeftVal) * 0.5f;
float width = borderLeft.getWidth();
borderPathLeft.moveTo(x, borderTopVal).lineTo(x, borderBottomVal);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderLeft.getPaint());
stroke.setDashArray(buildDashArray(borderBottomVal - borderTopVal, 2.0f * width));
borderPathLeft.setStroke(stroke);
break;
}
case DOTTED: {
float x = (borderLeftVal + contentLeftVal) * 0.5f;
float width = borderLeft.getWidth();
borderPathLeft.moveTo(x, borderTopVal).lineTo(x, borderBottomVal);
PXStroke stroke = new PXStroke(width);
stroke.setColor(borderLeft.getPaint());
stroke.setDashArray(buildDashArray(borderBottomVal - borderTopVal, width));
borderPathLeft.setStroke(stroke);
break;
}
case DOUBLE:
case GROOVE:
case INSET:
case OUTSET:
case RIDGE:
break;
// NOTE: We should never hit these cases
case NONE:
case HIDDEN:
default:
break;
}
}
}
}