Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion snap_drawing/src/snap_drawing/cpp/Layers/ShapeLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ void ShapeLayer::onInitialize() {

void ShapeLayer::onDraw(DrawingContext& drawingContext) {
const auto& path = getActivePath();
drawingContext.drawPaint(_fillPaint, path);
auto fillPaint = _fillPaint;
if (_fillGradientWrapper.hasGradient()) {
fillPaint.setColor(Color::black());
_fillGradientWrapper.update(path.getBounds().value_or(drawingContext.drawBounds()));
_fillGradientWrapper.applyToPaint(fillPaint);
}
drawingContext.drawPaint(fillPaint, path);
drawingContext.drawPaint(_strokePaint, path);
}

Expand Down Expand Up @@ -77,6 +83,51 @@ void ShapeLayer::setFillColor(Color fillColor) {
setNeedsDisplay();
}

void ShapeLayer::setFillLinearGradient(std::vector<Scalar>&& locations,
std::vector<Color>&& colors,
LinearGradientOrientation orientation) {
if (_fillGradientWrapper.clearIfNeeded(GradientWrapper::GradientType::RADIAL)) {
setNeedsDisplay();
}

if (colors.empty()) {
if (_fillGradientWrapper.clearIfNeeded(GradientWrapper::GradientType::LINEAR)) {
setNeedsDisplay();
}
return;
}

_fillGradientWrapper.setAsLinear(std::move(locations), std::move(colors), orientation);

if (_fillGradientWrapper.isDirty()) {
setNeedsDisplay();
}
}

void ShapeLayer::setFillRadialGradient(std::vector<Scalar>&& locations, std::vector<Color>&& colors) {
if (_fillGradientWrapper.clearIfNeeded(GradientWrapper::GradientType::LINEAR)) {
setNeedsDisplay();
}

if (colors.empty()) {
if (_fillGradientWrapper.clearIfNeeded(GradientWrapper::GradientType::RADIAL)) {
setNeedsDisplay();
}
return;
}

_fillGradientWrapper.setAsRadial(std::move(locations), std::move(colors));

if (_fillGradientWrapper.isDirty()) {
setNeedsDisplay();
}
}

void ShapeLayer::resetFillGradient() {
_fillGradientWrapper.clear();
setNeedsDisplay();
}

void ShapeLayer::setStrokeCap(PaintStrokeCap strokeCap) {
_strokePaint.setStrokeCap(strokeCap);
setNeedsDisplay();
Expand Down
10 changes: 10 additions & 0 deletions snap_drawing/src/snap_drawing/cpp/Layers/ShapeLayer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@

#include "snap_drawing/cpp/Drawing/Paint.hpp"
#include "snap_drawing/cpp/Layers/Layer.hpp"
#include "snap_drawing/cpp/Utils/GradientWrapper.hpp"
#include "snap_drawing/cpp/Utils/Path.hpp"
#include "snap_drawing/cpp/Utils/PathInterpolator.hpp"

#include <vector>

namespace snap::drawing {

class MaskFilter;
Expand All @@ -34,6 +37,12 @@ class ShapeLayer : public Layer {
void setFillColor(Color fillColor);
Color getFillColor() const;

void setFillLinearGradient(std::vector<Scalar>&& locations,
std::vector<Color>&& colors,
LinearGradientOrientation orientation);
void setFillRadialGradient(std::vector<Scalar>&& locations, std::vector<Color>&& colors);
void resetFillGradient();

void setStrokeCap(PaintStrokeCap strokeCap);
void setStrokeJoin(PaintStrokeJoin strokeJoin);

Expand All @@ -52,6 +61,7 @@ class ShapeLayer : public Layer {
Path _path;
Paint _strokePaint;
Paint _fillPaint;
GradientWrapper _fillGradientWrapper;
Scalar _strokeStart = 0.0f;
Scalar _strokeEnd = 1.0f;
std::optional<PathInterpolator> _pathInterpolator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2168,6 +2168,11 @@ export interface ShapeView extends _ShapeView, LeafView {
*/
fillColor?: Color;

/**
* Gradient that the shape's enclosed area is filled with.
*/
fillGradient?: string;

/**
* The stroke cap specifies the shape of the endpoints of an open path when stroked.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.snap.valdi.attributes.AttributesBindingContext
import com.snap.valdi.attributes.impl.animations.ValdiAnimator
import com.snap.valdi.attributes.impl.animations.ValdiValueAnimation
import com.snap.valdi.attributes.impl.animations.ViewAnimator
import com.snap.valdi.attributes.impl.gradients.ValdiGradient
import com.snap.valdi.exceptions.AttributeError
import com.snap.valdi.extensions.ViewUtils
import com.snap.valdi.utils.CoordinateResolver
Expand Down Expand Up @@ -52,6 +53,23 @@ class ShapeViewAttributesBinder : AttributesBinder<ShapeView> {
view.resetFillColor()
}

private fun applyFillGradient(view: ShapeView, value: Array<Any>, animator: ValdiAnimator?) {
val valdiGradient = ValdiGradient.fromGradientData(value)

if (valdiGradient.colors.isEmpty()) {
view.resetFillGradient()
} else if (valdiGradient.colors.size == 1) {
view.resetFillGradient()
view.setFillColor(valdiGradient.colors[0])
} else {
view.setFillGradient(valdiGradient)
}
}

private fun resetFillGradient(view: ShapeView, animator: ValdiAnimator?) {
view.resetFillGradient()
}

private fun applyStrokeCap(view: ShapeView, value: String, animator: ValdiAnimator?) {
val strokeCap: Paint.Cap = when (value) {
"butt" -> Paint.Cap.BUTT
Expand Down Expand Up @@ -119,6 +137,7 @@ class ShapeViewAttributesBinder : AttributesBinder<ShapeView> {
attributesBindingContext.bindFloatAttribute("strokeWidth", false, this::applyStrokeWidth, this::resetStrokeWidth)
attributesBindingContext.bindColorAttribute("strokeColor", false, this::applyStrokeColor, this::resetStrokeColor)
attributesBindingContext.bindColorAttribute("fillColor", false, this::applyFillColor, this::resetFillColor)
attributesBindingContext.bindArrayAttribute("fillGradient", false, this::applyFillGradient, this::resetFillGradient)
attributesBindingContext.bindStringAttribute("strokeCap", false, this::applyStrokeCap, this::resetStrokeCap)
attributesBindingContext.bindStringAttribute("strokeJoin", false, this::applyStrokeJoin, this::resetStrokeJoin)
attributesBindingContext.bindFloatAttribute("strokeStart", false, this::applyStrokeStart, this::resetStrokeStart)
Expand Down
139 changes: 137 additions & 2 deletions valdi/src/java/com/snap/valdi/views/ShapeView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import androidx.annotation.Keep
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RadialGradient
import android.graphics.RectF
import android.graphics.Shader
import android.graphics.drawable.GradientDrawable
import android.view.View
import com.snap.valdi.attributes.impl.gradients.ValdiGradient
import com.snap.valdi.extensions.ViewUtils
import com.snap.valdi.utils.CoordinateResolver
import com.snap.valdi.utils.GeometricPath
import com.snap.valdi.utils.PathInterpolator
import kotlin.math.min

private val DEFAULT_STROKE_WIDTH = 1.0f
private val DEFAULT_COLOR = Color.TRANSPARENT
Expand Down Expand Up @@ -41,8 +48,14 @@ class ShapeView(context: Context) : View(context), ValdiRecyclableView {
private val geometricPath = GeometricPath()
private val strokePaint = Paint()
private val fillPaint = Paint()
private val fillGradientBounds = RectF()
private val coordinateResolver = CoordinateResolver(context)
private var pathInterpolator: PathInterpolator? = null
private var fillColor = DEFAULT_COLOR
private var fillGradient: ValdiGradient? = null
private var fillGradientDirty = false
private var fillGradientWidth = -1
private var fillGradientHeight = -1

init {
strokePaint.strokeJoin = Paint.Join.MITER
Expand Down Expand Up @@ -86,14 +99,33 @@ class ShapeView(context: Context) : View(context), ValdiRecyclableView {
}

fun setFillColor(fillColor: Int) {
fillPaint.color = fillColor
this.fillColor = fillColor
if (fillGradient == null) {
fillPaint.color = fillColor
}
invalidate()
}

fun resetFillColor() {
setFillColor(DEFAULT_COLOR)
}

fun setFillGradient(fillGradient: ValdiGradient) {
this.fillGradient = fillGradient
fillGradientDirty = true
invalidate()
}

fun resetFillGradient() {
fillGradient = null
fillGradientDirty = false
fillGradientWidth = -1
fillGradientHeight = -1
fillPaint.color = fillColor
fillPaint.shader = null
invalidate()
}

fun setStrokeJoin(strokeJoin: Paint.Join) {
strokePaint.strokeJoin = strokeJoin
invalidate()
Expand Down Expand Up @@ -122,11 +154,114 @@ class ShapeView(context: Context) : View(context), ValdiRecyclableView {

val activePath = this.getActivePath()

if (fillGradient != null) {
fillPaint.color = Color.BLACK
updateFillGradientShader()
} else {
fillPaint.color = fillColor
fillPaint.shader = null
}

it.drawPath(activePath, fillPaint)
it.drawPath(activePath, strokePaint)
}
}

private fun updateFillGradientShader() {
val gradient = fillGradient ?: return
if (!fillGradientDirty && fillGradientWidth == width && fillGradientHeight == height) {
return
}

fillGradientDirty = false
fillGradientWidth = width
fillGradientHeight = height
fillGradientBounds.set(0.0f, 0.0f, width.toFloat(), height.toFloat())

when (gradient.getDrawableGradientType()) {
GradientDrawable.LINEAR_GRADIENT -> {
val x0: Float
val x1: Float
val y0: Float
val y1: Float

when (gradient.getDrawableOrientation()) {
GradientDrawable.Orientation.TOP_BOTTOM -> {
x0 = fillGradientBounds.left
y0 = fillGradientBounds.top
x1 = x0
y1 = fillGradientBounds.bottom
}
GradientDrawable.Orientation.TR_BL -> {
x0 = fillGradientBounds.right
y0 = fillGradientBounds.top
x1 = fillGradientBounds.left
y1 = fillGradientBounds.bottom
}
GradientDrawable.Orientation.RIGHT_LEFT -> {
x0 = fillGradientBounds.right
y0 = fillGradientBounds.top
x1 = fillGradientBounds.left
y1 = y0
}
GradientDrawable.Orientation.BR_TL -> {
x0 = fillGradientBounds.right
y0 = fillGradientBounds.bottom
x1 = fillGradientBounds.left
y1 = fillGradientBounds.top
}
GradientDrawable.Orientation.BOTTOM_TOP -> {
x0 = fillGradientBounds.left
y0 = fillGradientBounds.bottom
x1 = x0
y1 = fillGradientBounds.top
}
GradientDrawable.Orientation.BL_TR -> {
x0 = fillGradientBounds.left
y0 = fillGradientBounds.bottom
x1 = fillGradientBounds.right
y1 = fillGradientBounds.top
}
GradientDrawable.Orientation.LEFT_RIGHT -> {
x0 = fillGradientBounds.left
y0 = fillGradientBounds.top
x1 = fillGradientBounds.right
y1 = y0
}
GradientDrawable.Orientation.TL_BR -> {
x0 = fillGradientBounds.left
y0 = fillGradientBounds.top
x1 = fillGradientBounds.right
y1 = fillGradientBounds.bottom
}
}

fillPaint.shader = LinearGradient(
x0,
y0,
x1,
y1,
gradient.colors,
gradient.locations,
Shader.TileMode.CLAMP,
)
}
GradientDrawable.RADIAL_GRADIENT -> {
fillPaint.shader = RadialGradient(
fillGradientBounds.centerX(),
fillGradientBounds.centerY(),
min(fillGradientBounds.width(), fillGradientBounds.height()) / 2.0f,
gradient.colors,
gradient.locations,
Shader.TileMode.CLAMP,
)
}
else -> {
fillPaint.shader = null
}
}
}

private fun getActivePath(): Path {
geometricPath.width = width
geometricPath.height = height
Expand All @@ -152,4 +287,4 @@ class ShapeView(context: Context) : View(context), ValdiRecyclableView {
return ViewUtils.hasOverlappingRendering(this)
}

}
}
Loading
Loading