diff --git a/README.md b/README.md
index ebc3b53..e798a5e 100644
--- a/README.md
+++ b/README.md
@@ -65,6 +65,9 @@ There is a [sample](https://github.com/fornewid/neumorphism/tree/master/sample)
app:neumorph_shadowColorLight="@color/solid_light_color"
app:neumorph_shadowColorDark="@color/solid_dark_color"
+ // Set light source
+ app:neumorph_lightSource="leftTop|leftBottom|rightTop|rightBottom"
+
// Set shape type and corner size
app:neumorph_shapeType="{flat|pressed|basin}"
app:neumorph_shapeAppearance="@style/CustomShapeAppearance"
@@ -94,6 +97,11 @@ There is a [sample](https://github.com/fornewid/neumorphism/tree/master/sample)
```
+- #### LightSource
+| LEFT_TOP | LEFT_BOTTOM | RIGHT_TOP | RIGHT_BOTTOM |
+| :--: | :-----: | :---: | :---: |
+|
|
|
|
|
+
- #### ShapeType
| FLAT | PRESSED | BASIN |
| :--: | :-----: | :---: |
diff --git a/art/lightSource_leftBottom.png b/art/lightSource_leftBottom.png
new file mode 100644
index 0000000..620ceaf
Binary files /dev/null and b/art/lightSource_leftBottom.png differ
diff --git a/art/lightSource_leftTop.png b/art/lightSource_leftTop.png
new file mode 100644
index 0000000..0442687
Binary files /dev/null and b/art/lightSource_leftTop.png differ
diff --git a/art/lightSource_rightBottom.png b/art/lightSource_rightBottom.png
new file mode 100644
index 0000000..4f877b0
Binary files /dev/null and b/art/lightSource_rightBottom.png differ
diff --git a/art/lightSource_rightTop.png b/art/lightSource_rightTop.png
new file mode 100644
index 0000000..1b970b6
Binary files /dev/null and b/art/lightSource_rightTop.png differ
diff --git a/neumorphism/src/main/java/soup/neumorphism/LightSource.kt b/neumorphism/src/main/java/soup/neumorphism/LightSource.kt
new file mode 100644
index 0000000..17e09fc
--- /dev/null
+++ b/neumorphism/src/main/java/soup/neumorphism/LightSource.kt
@@ -0,0 +1,39 @@
+package soup.neumorphism
+
+import androidx.annotation.IntDef
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@IntDef(
+ LightSource.LEFT_TOP,
+ LightSource.LEFT_BOTTOM,
+ LightSource.RIGHT_TOP,
+ LightSource.RIGHT_BOTTOM
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class LightSource {
+ companion object {
+ const val LEFT_TOP = 0
+ const val LEFT_BOTTOM = 1
+ const val RIGHT_TOP = 2
+ const val RIGHT_BOTTOM = 3
+
+ const val DEFAULT = LEFT_TOP
+
+ fun isLeft(@LightSource lightSource: Int): Boolean {
+ return lightSource == LEFT_TOP || lightSource == LEFT_BOTTOM
+ }
+
+ fun isTop(@LightSource lightSource: Int): Boolean {
+ return lightSource == LEFT_TOP || lightSource == RIGHT_TOP
+ }
+
+ fun isRight(@LightSource lightSource: Int): Boolean {
+ return lightSource == RIGHT_TOP || lightSource == RIGHT_BOTTOM
+ }
+
+ fun isBottom(@LightSource lightSource: Int): Boolean {
+ return lightSource == LEFT_BOTTOM || lightSource == RIGHT_BOTTOM
+ }
+ }
+}
diff --git a/neumorphism/src/main/java/soup/neumorphism/NeumorphButton.kt b/neumorphism/src/main/java/soup/neumorphism/NeumorphButton.kt
index 4da8272..fbe0ec5 100644
--- a/neumorphism/src/main/java/soup/neumorphism/NeumorphButton.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/NeumorphButton.kt
@@ -31,6 +31,7 @@ class NeumorphButton @JvmOverloads constructor(
val fillColor = a.getColorStateList(R.styleable.NeumorphButton_neumorph_backgroundColor)
val strokeColor = a.getColorStateList(R.styleable.NeumorphButton_neumorph_strokeColor)
val strokeWidth = a.getDimension(R.styleable.NeumorphButton_neumorph_strokeWidth, 0f)
+ val lightSource = a.getInt(R.styleable.NeumorphButton_neumorph_lightSource, LightSource.DEFAULT)
val shapeType = a.getInt(R.styleable.NeumorphButton_neumorph_shapeType, ShapeType.DEFAULT)
val inset = a.getDimensionPixelSize(
R.styleable.NeumorphButton_neumorph_inset, 0
@@ -64,6 +65,7 @@ class NeumorphButton @JvmOverloads constructor(
shapeDrawable = NeumorphShapeDrawable(context, attrs, defStyleAttr, defStyleRes).apply {
setInEditMode(isInEditMode)
+ setLightSource(lightSource)
setShapeType(shapeType)
setShadowElevation(shadowElevation)
setShadowColorLight(shadowColorLight)
@@ -130,6 +132,15 @@ class NeumorphButton @JvmOverloads constructor(
return shapeDrawable.getStrokeWidth()
}
+ fun setLightSource(@LightSource lightSource: Int) {
+ shapeDrawable.setLightSource(lightSource)
+ }
+
+ @LightSource
+ fun getLightSource(): Int {
+ return shapeDrawable.getLightSource()
+ }
+
fun setShapeType(@ShapeType shapeType: Int) {
shapeDrawable.setShapeType(shapeType)
}
diff --git a/neumorphism/src/main/java/soup/neumorphism/NeumorphCardView.kt b/neumorphism/src/main/java/soup/neumorphism/NeumorphCardView.kt
index e5d1203..bff7a3f 100644
--- a/neumorphism/src/main/java/soup/neumorphism/NeumorphCardView.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/NeumorphCardView.kt
@@ -33,6 +33,7 @@ class NeumorphCardView @JvmOverloads constructor(
val fillColor = a.getColorStateList(R.styleable.NeumorphCardView_neumorph_backgroundColor)
val strokeColor = a.getColorStateList(R.styleable.NeumorphCardView_neumorph_strokeColor)
val strokeWidth = a.getDimension(R.styleable.NeumorphCardView_neumorph_strokeWidth, 0f)
+ val lightSource = a.getInt(R.styleable.NeumorphCardView_neumorph_lightSource, LightSource.DEFAULT)
val shapeType = a.getInt(R.styleable.NeumorphCardView_neumorph_shapeType, ShapeType.DEFAULT)
val inset = a.getDimensionPixelSize(
R.styleable.NeumorphCardView_neumorph_inset, 0
@@ -66,6 +67,7 @@ class NeumorphCardView @JvmOverloads constructor(
shapeDrawable = NeumorphShapeDrawable(context, attrs, defStyleAttr, defStyleRes).apply {
setInEditMode(isInEditMode)
+ setLightSource(lightSource)
setShapeType(shapeType)
setShadowElevation(shadowElevation)
setShadowColorLight(shadowColorLight)
@@ -143,6 +145,15 @@ class NeumorphCardView @JvmOverloads constructor(
return shapeDrawable.getStrokeWidth()
}
+ fun setLightSource(@LightSource lightSource: Int) {
+ shapeDrawable.setLightSource(lightSource)
+ }
+
+ @LightSource
+ fun getLightSource(): Int {
+ return shapeDrawable.getLightSource()
+ }
+
fun setShapeType(@ShapeType shapeType: Int) {
shapeDrawable.setShapeType(shapeType)
}
diff --git a/neumorphism/src/main/java/soup/neumorphism/NeumorphFloatingActionButton.kt b/neumorphism/src/main/java/soup/neumorphism/NeumorphFloatingActionButton.kt
index 94d154b..3e10793 100644
--- a/neumorphism/src/main/java/soup/neumorphism/NeumorphFloatingActionButton.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/NeumorphFloatingActionButton.kt
@@ -31,8 +31,8 @@ class NeumorphFloatingActionButton @JvmOverloads constructor(
val fillColor = a.getColorStateList(R.styleable.NeumorphFloatingActionButton_neumorph_backgroundColor)
val strokeColor = a.getColorStateList(R.styleable.NeumorphFloatingActionButton_neumorph_strokeColor)
val strokeWidth = a.getDimension(R.styleable.NeumorphFloatingActionButton_neumorph_strokeWidth, 0f)
- val shapeType =
- a.getInt(R.styleable.NeumorphFloatingActionButton_neumorph_shapeType, ShapeType.DEFAULT)
+ val lightSource = a.getInt(R.styleable.NeumorphFloatingActionButton_neumorph_lightSource, LightSource.DEFAULT)
+ val shapeType = a.getInt(R.styleable.NeumorphFloatingActionButton_neumorph_shapeType, ShapeType.DEFAULT)
val inset = a.getDimensionPixelSize(
R.styleable.NeumorphFloatingActionButton_neumorph_inset, 0
)
@@ -65,6 +65,7 @@ class NeumorphFloatingActionButton @JvmOverloads constructor(
shapeDrawable = NeumorphShapeDrawable(context, attrs, defStyleAttr, defStyleRes).apply {
setInEditMode(isInEditMode)
+ setLightSource(lightSource)
setShapeType(shapeType)
setShadowElevation(shadowElevation)
setShadowColorLight(shadowColorLight)
@@ -131,6 +132,15 @@ class NeumorphFloatingActionButton @JvmOverloads constructor(
return shapeDrawable.getStrokeWidth()
}
+ fun setLightSource(@LightSource lightSource: Int) {
+ shapeDrawable.setLightSource(lightSource)
+ }
+
+ @LightSource
+ fun getLightSource(): Int {
+ return shapeDrawable.getLightSource()
+ }
+
fun setShapeType(@ShapeType shapeType: Int) {
shapeDrawable.setShapeType(shapeType)
}
diff --git a/neumorphism/src/main/java/soup/neumorphism/NeumorphImageButton.kt b/neumorphism/src/main/java/soup/neumorphism/NeumorphImageButton.kt
index 1bcb4a3..48a4f74 100644
--- a/neumorphism/src/main/java/soup/neumorphism/NeumorphImageButton.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/NeumorphImageButton.kt
@@ -31,6 +31,7 @@ class NeumorphImageButton @JvmOverloads constructor(
val fillColor = a.getColorStateList(R.styleable.NeumorphImageButton_neumorph_backgroundColor)
val strokeColor = a.getColorStateList(R.styleable.NeumorphImageButton_neumorph_strokeColor)
val strokeWidth = a.getDimension(R.styleable.NeumorphImageButton_neumorph_strokeWidth, 0f)
+ val lightSource = a.getInt(R.styleable.NeumorphImageButton_neumorph_lightSource, LightSource.DEFAULT)
val shapeType = a.getInt(R.styleable.NeumorphImageButton_neumorph_shapeType, ShapeType.DEFAULT)
val inset = a.getDimensionPixelSize(
R.styleable.NeumorphImageButton_neumorph_inset, 0
@@ -64,6 +65,7 @@ class NeumorphImageButton @JvmOverloads constructor(
shapeDrawable = NeumorphShapeDrawable(context, attrs, defStyleAttr, defStyleRes).apply {
setInEditMode(isInEditMode)
+ setLightSource(lightSource)
setShapeType(shapeType)
setShadowElevation(shadowElevation)
setShadowColorLight(shadowColorLight)
@@ -130,6 +132,15 @@ class NeumorphImageButton @JvmOverloads constructor(
return shapeDrawable.getStrokeWidth()
}
+ fun setLightSource(@LightSource lightSource: Int) {
+ shapeDrawable.setLightSource(lightSource)
+ }
+
+ @LightSource
+ fun getLightSource(): Int {
+ return shapeDrawable.getLightSource()
+ }
+
fun setShapeType(@ShapeType shapeType: Int) {
shapeDrawable.setShapeType(shapeType)
}
diff --git a/neumorphism/src/main/java/soup/neumorphism/NeumorphImageView.kt b/neumorphism/src/main/java/soup/neumorphism/NeumorphImageView.kt
index a180fe1..dbdd6b0 100644
--- a/neumorphism/src/main/java/soup/neumorphism/NeumorphImageView.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/NeumorphImageView.kt
@@ -31,6 +31,7 @@ class NeumorphImageView @JvmOverloads constructor(
val fillColor = a.getColorStateList(R.styleable.NeumorphImageView_neumorph_backgroundColor)
val strokeColor = a.getColorStateList(R.styleable.NeumorphImageView_neumorph_strokeColor)
val strokeWidth = a.getDimension(R.styleable.NeumorphImageView_neumorph_strokeWidth, 0f)
+ val lightSource = a.getInt(R.styleable.NeumorphImageView_neumorph_lightSource, LightSource.DEFAULT)
val shapeType = a.getInt(R.styleable.NeumorphImageView_neumorph_shapeType, ShapeType.DEFAULT)
val inset = a.getDimensionPixelSize(
R.styleable.NeumorphImageView_neumorph_inset, 0
@@ -64,6 +65,7 @@ class NeumorphImageView @JvmOverloads constructor(
shapeDrawable = NeumorphShapeDrawable(context, attrs, defStyleAttr, defStyleRes).apply {
setInEditMode(isInEditMode)
+ setLightSource(lightSource)
setShapeType(shapeType)
setShadowElevation(shadowElevation)
setShadowColorLight(shadowColorLight)
@@ -130,6 +132,15 @@ class NeumorphImageView @JvmOverloads constructor(
return shapeDrawable.getStrokeWidth()
}
+ fun setLightSource(@LightSource lightSource: Int) {
+ shapeDrawable.setLightSource(lightSource)
+ }
+
+ @LightSource
+ fun getLightSource(): Int {
+ return shapeDrawable.getLightSource()
+ }
+
fun setShapeType(@ShapeType shapeType: Int) {
shapeDrawable.setShapeType(shapeType)
}
diff --git a/neumorphism/src/main/java/soup/neumorphism/NeumorphShapeDrawable.kt b/neumorphism/src/main/java/soup/neumorphism/NeumorphShapeDrawable.kt
index 8080a2d..ebde463 100644
--- a/neumorphism/src/main/java/soup/neumorphism/NeumorphShapeDrawable.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/NeumorphShapeDrawable.kt
@@ -166,6 +166,18 @@ class NeumorphShapeDrawable : Drawable {
invalidateSelf()
}
+ fun setLightSource(@LightSource lightSource: Int) {
+ if (drawableState.lightSource != lightSource) {
+ drawableState.lightSource = lightSource
+ invalidateSelf()
+ }
+ }
+
+ @LightSource
+ fun getLightSource(): Int {
+ return drawableState.lightSource
+ }
+
fun setShapeType(@ShapeType shapeType: Int) {
if (drawableState.shapeType != shapeType) {
drawableState.shapeType = shapeType
@@ -382,6 +394,8 @@ class NeumorphShapeDrawable : Drawable {
var alpha = 255
+ @LightSource
+ var lightSource: Int = LightSource.DEFAULT
@ShapeType
var shapeType: Int = ShapeType.DEFAULT
var shadowElevation: Float = 0f
@@ -408,6 +422,7 @@ class NeumorphShapeDrawable : Drawable {
strokeColor = orig.strokeColor
strokeWidth = orig.strokeWidth
alpha = orig.alpha
+ lightSource = orig.lightSource
shapeType = orig.shapeType
shadowElevation = orig.shadowElevation
shadowColorLight = orig.shadowColorLight
diff --git a/neumorphism/src/main/java/soup/neumorphism/internal/shape/FlatShape.kt b/neumorphism/src/main/java/soup/neumorphism/internal/shape/FlatShape.kt
index 0287ed3..eb936b8 100644
--- a/neumorphism/src/main/java/soup/neumorphism/internal/shape/FlatShape.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/internal/shape/FlatShape.kt
@@ -7,6 +7,7 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import soup.neumorphism.CornerFamily
+import soup.neumorphism.LightSource
import soup.neumorphism.NeumorphShapeAppearanceModel
import soup.neumorphism.NeumorphShapeDrawable.NeumorphShapeDrawableState
import soup.neumorphism.internal.util.onCanvas
@@ -29,20 +30,23 @@ internal class FlatShape(
override fun draw(canvas: Canvas, outlinePath: Path) {
canvas.withClipOut(outlinePath) {
+ val lightSource = drawableState.lightSource
val elevation = drawableState.shadowElevation
val z = drawableState.shadowElevation + drawableState.translationZ
- val left: Float
- val top: Float
val inset = drawableState.inset
- left = inset.left.toFloat()
- top = inset.top.toFloat()
+ val left = inset.left.toFloat()
+ val top = inset.top.toFloat()
lightShadowBitmap?.let {
+ val offsetX = if (LightSource.isLeft(lightSource)) -elevation - z else -elevation + z
+ val offsetY = if (LightSource.isTop(lightSource)) -elevation - z else -elevation + z
val offset = -elevation - z
- drawBitmap(it, offset + left, offset + top, null)
+ drawBitmap(it, offsetX + left, offsetY + top, null)
}
darkShadowBitmap?.let {
+ val offsetX = if (LightSource.isLeft(lightSource)) -elevation + z else -elevation - z
+ val offsetY = if (LightSource.isTop(lightSource)) -elevation + z else -elevation - z
val offset = -elevation + z
- drawBitmap(it, offset + left, offset + top, null)
+ drawBitmap(it, offsetX + left, offsetY + top, null)
}
}
}
diff --git a/neumorphism/src/main/java/soup/neumorphism/internal/shape/PressedShape.kt b/neumorphism/src/main/java/soup/neumorphism/internal/shape/PressedShape.kt
index c279331..8dd4d33 100644
--- a/neumorphism/src/main/java/soup/neumorphism/internal/shape/PressedShape.kt
+++ b/neumorphism/src/main/java/soup/neumorphism/internal/shape/PressedShape.kt
@@ -6,6 +6,7 @@ import android.graphics.Path
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import soup.neumorphism.CornerFamily
+import soup.neumorphism.LightSource
import soup.neumorphism.NeumorphShapeDrawable.NeumorphShapeDrawableState
import soup.neumorphism.internal.util.onCanvas
import soup.neumorphism.internal.util.withClip
@@ -58,7 +59,7 @@ internal class PressedShape(
drawableState.shapeAppearanceModel.getCornerSize()
)
shape = GradientDrawable.RECTANGLE
- cornerRadii = floatArrayOf(0f, 0f, 0f, 0f, cornerSize, cornerSize, 0f, 0f)
+ cornerRadii = getCornerRadiiForLightShadow(cornerSize)
}
}
}
@@ -76,7 +77,7 @@ internal class PressedShape(
drawableState.shapeAppearanceModel.getCornerSize()
)
shape = GradientDrawable.RECTANGLE
- cornerRadii = floatArrayOf(cornerSize, cornerSize, 0f, 0f, 0f, 0f, 0f, 0f)
+ cornerRadii = getCornerRadiiForDarkShadow(cornerSize)
}
}
}
@@ -88,6 +89,26 @@ internal class PressedShape(
shadowBitmap = generateShadowBitmap(w, h)
}
+ private fun getCornerRadiiForLightShadow(cornerSize: Float): FloatArray {
+ return when (drawableState.lightSource) {
+ LightSource.LEFT_TOP -> floatArrayOf(0f, 0f, 0f, 0f, cornerSize, cornerSize, 0f, 0f)
+ LightSource.LEFT_BOTTOM -> floatArrayOf(0f, 0f, cornerSize, cornerSize, 0f, 0f, 0f, 0f)
+ LightSource.RIGHT_TOP -> floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, cornerSize, cornerSize)
+ LightSource.RIGHT_BOTTOM -> floatArrayOf(cornerSize, cornerSize, 0f, 0f, 0f, 0f, 0f, 0f)
+ else -> throw IllegalStateException("LightSource ${drawableState.lightSource} is not supported.")
+ }
+ }
+
+ private fun getCornerRadiiForDarkShadow(cornerSize: Float): FloatArray {
+ return when (drawableState.lightSource) {
+ LightSource.LEFT_TOP -> floatArrayOf(cornerSize, cornerSize, 0f, 0f, 0f, 0f, 0f, 0f)
+ LightSource.LEFT_BOTTOM -> floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, cornerSize, cornerSize)
+ LightSource.RIGHT_TOP -> floatArrayOf(0f, 0f, cornerSize, cornerSize, 0f, 0f, 0f, 0f)
+ LightSource.RIGHT_BOTTOM -> floatArrayOf(0f, 0f, 0f, 0f, cornerSize, cornerSize, 0f, 0f)
+ else -> throw IllegalStateException("LightSource ${drawableState.lightSource} is not supported.")
+ }
+ }
+
private fun generateShadowBitmap(w: Int, h: Int): Bitmap? {
fun Bitmap.blurred(): Bitmap? {
if (drawableState.inEditMode) {
@@ -97,12 +118,21 @@ internal class PressedShape(
}
val shadowElevation = drawableState.shadowElevation
+ val lightSource = drawableState.lightSource
return Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
.onCanvas {
- withTranslation(-shadowElevation, -shadowElevation) {
+ withTranslation(
+ x = if (LightSource.isLeft(lightSource)) -shadowElevation else 0f,
+ y = if (LightSource.isTop(lightSource)) -shadowElevation else 0f
+ ) {
lightShadowDrawable.draw(this)
}
- darkShadowDrawable.draw(this)
+ withTranslation(
+ x = if (LightSource.isRight(lightSource)) -shadowElevation else 0f,
+ y = if (LightSource.isBottom(lightSource)) -shadowElevation else 0f
+ ) {
+ darkShadowDrawable.draw(this)
+ }
}
.blurred()
}
diff --git a/neumorphism/src/main/res/values/attrs.xml b/neumorphism/src/main/res/values/attrs.xml
index 53b1d69..36fc094 100644
--- a/neumorphism/src/main/res/values/attrs.xml
+++ b/neumorphism/src/main/res/values/attrs.xml
@@ -1,12 +1,18 @@
-
-
+
+
+
+
+
+
+
+
@@ -27,6 +33,7 @@
+
@@ -44,6 +51,7 @@
+
@@ -61,6 +69,7 @@
+
@@ -78,6 +87,7 @@
+
@@ -95,6 +105,7 @@
+