From d00602dce0f518e0a39625be15ff53d94e5acd2a Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Wed, 27 Dec 2023 17:46:17 +0100 Subject: [PATCH 01/11] Implement CoreRect as alternative for Android Rect and RectF --- .gitignore | 1 + .../dionsegijn/konfetti/core/PartySystem.kt | 7 ++-- .../konfetti/core/emitter/BaseEmitter.kt | 4 +-- .../konfetti/core/emitter/Confetti.kt | 9 +++-- .../konfetti/core/emitter/PartyEmitter.kt | 12 +++---- .../konfetti/core/models/CoreRect.kt | 34 +++++++++++++++++++ .../dionsegijn/konfetti/xml/KonfettiView.kt | 13 ++++--- 7 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt diff --git a/.gitignore b/.gitignore index e2b585c4..32fbe422 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ captures/ /.DS_Store jarRepositories.xml androidTestResultsUserPreferences.xml +migrations.xml diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt index e09a08fc..67ff96f3 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt @@ -1,10 +1,9 @@ package nl.dionsegijn.konfetti.core -import android.content.res.Resources -import android.graphics.Rect import nl.dionsegijn.konfetti.core.emitter.BaseEmitter import nl.dionsegijn.konfetti.core.emitter.Confetti import nl.dionsegijn.konfetti.core.emitter.PartyEmitter +import nl.dionsegijn.konfetti.core.models.CoreRect /** * PartySystem is responsible for requesting particles from the emitter and updating the particles @@ -16,7 +15,7 @@ import nl.dionsegijn.konfetti.core.emitter.PartyEmitter class PartySystem( val party: Party, val createdAt: Long = System.currentTimeMillis(), - pixelDensity: Float = Resources.getSystem().displayMetrics.density + pixelDensity: Float = 1f ) { var enabled = true @@ -27,7 +26,7 @@ class PartySystem( // Called every frame to create and update the particles state // returns a list of particles that are ready to be rendered - fun render(deltaTime: Float, drawArea: Rect): List { + fun render(deltaTime: Float, drawArea: CoreRect): List { if (enabled) { activeParticles.addAll(emitter.createConfetti(deltaTime, party, drawArea)) } diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/BaseEmitter.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/BaseEmitter.kt index a6ae4a20..7171d712 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/BaseEmitter.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/BaseEmitter.kt @@ -1,7 +1,7 @@ package nl.dionsegijn.konfetti.core.emitter -import android.graphics.Rect import nl.dionsegijn.konfetti.core.Party +import nl.dionsegijn.konfetti.core.models.CoreRect /** * An abstract class for creating a custom emitter @@ -16,7 +16,7 @@ abstract class BaseEmitter { abstract fun createConfetti( deltaTime: Float, party: Party, - drawArea: Rect, + drawArea: CoreRect, ): List /** diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/Confetti.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/Confetti.kt index fcd0ec65..48386c28 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/Confetti.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/Confetti.kt @@ -1,7 +1,6 @@ package nl.dionsegijn.konfetti.core.emitter -import android.graphics.Paint -import android.graphics.Rect +import nl.dionsegijn.konfetti.core.models.CoreRect import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.konfetti.core.models.Vector import kotlin.math.abs @@ -90,7 +89,7 @@ class Confetti( /** * Updates the state of the particle for each frame of the animation. */ - fun render(deltaTime: Float, drawArea: Rect) { + fun render(deltaTime: Float, drawArea: CoreRect) { applyForce(gravity) update(deltaTime, drawArea) } @@ -99,11 +98,11 @@ class Confetti( * Updates the state of the particle based on its current acceleration, velocity, and location. * Also handles the fading out of the particle when its lifespan is over. */ - private fun update(deltaTime: Float, drawArea: Rect) { + private fun update(deltaTime: Float, drawArea: CoreRect) { // Calculate frameRate dynamically, fallback to 60fps in case deltaTime is 0 frameRate = if (deltaTime > 0) 1f / deltaTime else DEFAULT_FRAME_RATE - if (location.y > drawArea.height()) { + if (location.y > drawArea.height) { alpha = 0 return } diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt index 5c9f64b3..72e5e09b 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt @@ -1,9 +1,9 @@ package nl.dionsegijn.konfetti.core.emitter -import android.graphics.Rect import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.Position import nl.dionsegijn.konfetti.core.Rotation +import nl.dionsegijn.konfetti.core.models.CoreRect import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.konfetti.core.models.Size import nl.dionsegijn.konfetti.core.models.Vector @@ -36,7 +36,7 @@ class PartyEmitter( * If timer isn't started yet, set initial start time * Create the first confetti immediately and update the last emitting time */ - override fun createConfetti(deltaTime: Float, party: Party, drawArea: Rect): List { + override fun createConfetti(deltaTime: Float, party: Party, drawArea: CoreRect): List { createParticleMs += deltaTime // Initial deltaTime can't be higher than the emittingTime, if so calculate @@ -68,7 +68,7 @@ class PartyEmitter( * @param party Configurations used for creating the initial Confetti states * @param drawArea the area and size of the canvas */ - private fun createParticle(party: Party, drawArea: Rect): Confetti { + private fun createParticle(party: Party, drawArea: CoreRect): Confetti { particlesCreated++ with(party) { val randomSize = size[random.nextInt(size.size)] @@ -129,13 +129,13 @@ class PartyEmitter( return (maxAngle - minAngle) * random.nextDouble() + minAngle } - private fun Position.get(drawArea: Rect): Position.Absolute { + private fun Position.get(drawArea: CoreRect): Position.Absolute { return when (this) { is Position.Absolute -> Position.Absolute(x, y) is Position.Relative -> { Position.Absolute( - drawArea.width() * x.toFloat(), - drawArea.height() * y.toFloat() + drawArea.width * x.toFloat(), + drawArea.height * y.toFloat() ) } is Position.between -> { diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt new file mode 100644 index 00000000..762635e5 --- /dev/null +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt @@ -0,0 +1,34 @@ +package nl.dionsegijn.konfetti.core.models + +interface CoreRect { + var x: Float + var y: Float + var width: Float + var height: Float + + fun set(x: Float, y: Float, width: Float, height: Float) { + this.x = x + this.y = y + this.width = width + this.height = height + } + + fun contains(px: Int, py: Int): Boolean { + return px >= x && px <= x + width && py >= y && py <= y + height + } +} + +class CoreRectImpl( + override var x: Float = 0f, + override var y: Float = 0f, + override var width: Float = 0f, + override var height: Float = 0f +) : CoreRect { + override fun set(x: Float, y: Float, width: Float, height: Float) { + super.set(x, y, width, height) + } + + override fun contains(px: Int, py: Int): Boolean { + return super.contains(px, py) + } +} \ No newline at end of file diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt index 9e711ff3..a4273b38 100644 --- a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt @@ -1,6 +1,7 @@ package nl.dionsegijn.konfetti.xml import android.content.Context +import android.content.res.Resources import android.graphics.Canvas import android.graphics.Paint import android.graphics.Rect @@ -9,6 +10,8 @@ import android.view.View import nl.dionsegijn.konfetti.core.Particle import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.PartySystem +import nl.dionsegijn.konfetti.core.models.CoreRect +import nl.dionsegijn.konfetti.core.models.CoreRectImpl import nl.dionsegijn.konfetti.xml.listeners.OnParticleSystemUpdateListener /** @@ -32,7 +35,7 @@ open class KonfettiView : View { */ private var timer: TimerIntegration = TimerIntegration() - private var drawArea = Rect() + private var drawArea = CoreRectImpl() /** * [OnParticleSystemUpdateListener] listener to notify when a new particle system @@ -97,7 +100,7 @@ open class KonfettiView : View { systems.addAll( party.map { onParticleSystemUpdateListener?.onParticleSystemStarted(this, it, systems.size) - PartySystem(it) + PartySystem(party = it, pixelDensity = Resources.getSystem().displayMetrics.density) } ) invalidate() @@ -107,14 +110,14 @@ open class KonfettiView : View { systems.addAll( party.map { onParticleSystemUpdateListener?.onParticleSystemStarted(this, it, systems.size) - PartySystem(it) + PartySystem(party = it, pixelDensity = Resources.getSystem().displayMetrics.density) } ) invalidate() } fun start(party: Party) { - systems.add(PartySystem(party)) + systems.add(PartySystem(party = party, pixelDensity = Resources.getSystem().displayMetrics.density)) onParticleSystemUpdateListener?.onParticleSystemStarted(this, party, systems.size) invalidate() } @@ -173,7 +176,7 @@ open class KonfettiView : View { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) - drawArea = Rect(0, 0, w, h) + drawArea = CoreRectImpl(0f, 0f, w.toFloat(), h.toFloat()) } override fun onVisibilityChanged(changedView: View, visibility: Int) { From 1f59a38c2676b94d7f938c5b39a1cd064a5d2e9c Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 10:51:37 +0100 Subject: [PATCH 02/11] Make the core library work with DrawableShapes and use ReferenceImage and ImageStore for efficiency and compatibility --- .../konfetti/core/emitter/PartyEmitter.kt | 14 ++---- .../konfetti/core/models/CoreImage.kt | 14 ++++++ .../dionsegijn/konfetti/core/models/Shape.kt | 21 ++++---- .../nl/dionsegijn/konfetti/xml/DrawShapes.kt | 37 ++++++++------ .../dionsegijn/konfetti/xml/KonfettiView.kt | 49 ++++++++++++++++--- .../konfetti/xml/image/DrawableImage.kt | 10 ++++ .../konfetti/xml/image/ImageStore.kt | 30 ++++++++++++ .../konfetti/xml/image/ImageUtil.kt | 19 +++++++ .../nl/dionsegijn/samples/shared/Presets.kt | 2 +- .../nl/dionsegijn/xml/java/MainActivity.java | 3 +- 10 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt create mode 100644 konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/DrawableImage.kt create mode 100644 konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt create mode 100644 konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageUtil.kt diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt index 72e5e09b..df3c23f3 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitter.kt @@ -76,7 +76,7 @@ class PartyEmitter( location = position.get(drawArea).run { Vector(x, y) }, width = randomSize.sizeInDp * pixelDensity, mass = randomSize.massWithVariance(), - shape = getRandomShape(party.shapes), + shape = getRandomShape(shapes), color = colors[random.nextInt(colors.size)], lifespan = timeToLive, fadeOut = fadeOutEnabled, @@ -150,18 +150,10 @@ class PartyEmitter( } /** - * When the shape is a DrawableShape, mutate the drawable so that all drawables - * have different values when drawn on the canvas. + * Get a random shape from the list of shapes */ private fun getRandomShape(shapes: List): Shape { - return when (val shape = shapes[random.nextInt(shapes.size)]) { - is Shape.DrawableShape -> { - val mutatedState = - shape.drawable.constantState?.newDrawable()?.mutate() ?: shape.drawable - shape.copy(drawable = mutatedState) - } - else -> shape - } + return shapes[random.nextInt(shapes.size)] } /** diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt new file mode 100644 index 00000000..ac5872ec --- /dev/null +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt @@ -0,0 +1,14 @@ +package nl.dionsegijn.konfetti.core.models + +import java.nio.ByteBuffer + +interface CoreImage { + val width: Int + val height: Int +} + +data class ReferenceImage( + val reference: Int, + override val width: Int, + override val height: Int +): CoreImage \ No newline at end of file diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/Shape.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/Shape.kt index b235928d..4af866c6 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/Shape.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/Shape.kt @@ -1,11 +1,9 @@ package nl.dionsegijn.konfetti.core.models -import android.graphics.RectF -import android.graphics.drawable.Drawable - sealed interface Shape { object Circle : Shape { - val rect = RectF() + // Default replacement for RectF + val rect = CoreRectImpl() } object Square : Shape class Rectangle( @@ -19,24 +17,25 @@ sealed interface Shape { /** * A drawable shape - * @param drawable drawable + * @param image CoreImage * @param tint Set to `false` to opt out of tinting the drawable, keeping its original colors. * @param applyAlpha Set to false to not apply alpha to drawables */ data class DrawableShape( - val drawable: Drawable, + val image: CoreImage, val tint: Boolean = true, val applyAlpha: Boolean = true, ) : Shape { + val heightRatio = - if (drawable.intrinsicHeight == -1 && drawable.intrinsicWidth == -1) { - // If the drawable has no intrinsic size, fill the available space. + if (image.height == -1 && image.width == -1) { + // If the image has no intrinsic size, fill the available space. 1f - } else if (drawable.intrinsicHeight == -1 || drawable.intrinsicWidth == -1) { - // Currently cannot handle a drawable with only one intrinsic dimension. + } else if (image.height == -1 || image.width == -1) { + // Currently cannot handle an image with only one intrinsic dimension. 0f } else { - drawable.intrinsicHeight.toFloat() / drawable.intrinsicWidth + image.height.toFloat() / image.width.toFloat() } } } diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/DrawShapes.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/DrawShapes.kt index dba6b65a..d50fd024 100644 --- a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/DrawShapes.kt +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/DrawShapes.kt @@ -5,26 +5,29 @@ import android.graphics.BlendModeColorFilter import android.graphics.Canvas import android.graphics.Paint import android.graphics.PorterDuff +import android.graphics.RectF import android.os.Build +import nl.dionsegijn.konfetti.core.models.ReferenceImage import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.konfetti.core.models.Shape.Circle import nl.dionsegijn.konfetti.core.models.Shape.Circle.rect import nl.dionsegijn.konfetti.core.models.Shape.DrawableShape import nl.dionsegijn.konfetti.core.models.Shape.Rectangle import nl.dionsegijn.konfetti.core.models.Shape.Square +import nl.dionsegijn.konfetti.xml.image.ImageStore /** * Draw a shape to `canvas`. Implementations are expected to draw within a square of size * `size` and must vertically/horizontally center their asset if it does not have an equal width * and height. */ -fun Shape.draw(canvas: Canvas, paint: Paint, size: Float) { +fun Shape.draw(canvas: Canvas, paint: Paint, size: Float, imageStore: ImageStore) { when (this) { Square -> canvas.drawRect(0f, 0f, size, size, paint) Circle -> { rect.set(0f, 0f, size, size) - canvas.drawOval(rect, paint) + canvas.drawOval(RectF(rect.x, rect.y, rect.width, rect.height), paint) } is Rectangle -> { val height = size * heightRatio @@ -32,21 +35,27 @@ fun Shape.draw(canvas: Canvas, paint: Paint, size: Float) { canvas.drawRect(0f, top, size, top + height, paint) } is DrawableShape -> { - if (tint) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - drawable.colorFilter = BlendModeColorFilter(paint.color, BlendMode.SRC_IN) - } else { - drawable.setColorFilter(paint.color, PorterDuff.Mode.SRC_IN) + val referenceImage = image + if (referenceImage is ReferenceImage) { + // Making use of the ImageStore for performance reasons, see ImageStore for more info + val drawable = imageStore.getImage(referenceImage.reference) ?: return + + if (tint) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + drawable.colorFilter = BlendModeColorFilter(paint.color, BlendMode.SRC_IN) + } else { + drawable.setColorFilter(paint.color, PorterDuff.Mode.SRC_IN) + } + } else if (applyAlpha) { + drawable.alpha = paint.alpha } - } else if (applyAlpha) { - drawable.alpha = paint.alpha - } - val height = (size * heightRatio).toInt() - val top = ((size - height) / 2f).toInt() + val height = (size * heightRatio).toInt() + val top = ((size - height) / 2f).toInt() - drawable.setBounds(0, top, size.toInt(), top + height) - drawable.draw(canvas) + drawable.setBounds(0, top, size.toInt(), top + height) + drawable.draw(canvas) + } } } } diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt index a4273b38..90ed4053 100644 --- a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/KonfettiView.kt @@ -4,14 +4,17 @@ import android.content.Context import android.content.res.Resources import android.graphics.Canvas import android.graphics.Paint -import android.graphics.Rect +import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View import nl.dionsegijn.konfetti.core.Particle import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.PartySystem -import nl.dionsegijn.konfetti.core.models.CoreRect import nl.dionsegijn.konfetti.core.models.CoreRectImpl +import nl.dionsegijn.konfetti.core.models.ReferenceImage +import nl.dionsegijn.konfetti.core.models.Shape +import nl.dionsegijn.konfetti.xml.image.DrawableImage +import nl.dionsegijn.konfetti.xml.image.ImageStore import nl.dionsegijn.konfetti.xml.listeners.OnParticleSystemUpdateListener /** @@ -45,6 +48,8 @@ open class KonfettiView : View { fun getActiveSystems() = systems + private val imageStore = ImageStore() + /** * Check if current systems are active rendering particles. * @return true if konfetti is actively rendering @@ -92,7 +97,7 @@ open class KonfettiView : View { canvas.rotate(rotation, centerX, width / 2) canvas.scale(scaleX, 1f) - shape.draw(canvas, paint, width) + shape.draw(canvas, paint, width, imageStore) canvas.restoreToCount(saveCount) } @@ -100,7 +105,7 @@ open class KonfettiView : View { systems.addAll( party.map { onParticleSystemUpdateListener?.onParticleSystemStarted(this, it, systems.size) - PartySystem(party = it, pixelDensity = Resources.getSystem().displayMetrics.density) + PartySystem(party = storeImages(it), pixelDensity = Resources.getSystem().displayMetrics.density) } ) invalidate() @@ -109,19 +114,51 @@ open class KonfettiView : View { fun start(party: List) { systems.addAll( party.map { + storeImages(it) onParticleSystemUpdateListener?.onParticleSystemStarted(this, it, systems.size) - PartySystem(party = it, pixelDensity = Resources.getSystem().displayMetrics.density) + PartySystem(party = storeImages(it), pixelDensity = Resources.getSystem().displayMetrics.density) } ) invalidate() } fun start(party: Party) { - systems.add(PartySystem(party = party, pixelDensity = Resources.getSystem().displayMetrics.density)) onParticleSystemUpdateListener?.onParticleSystemStarted(this, party, systems.size) + systems.add(PartySystem(party = storeImages(party), pixelDensity = Resources.getSystem().displayMetrics.density)) invalidate() } + /** + * Transforms the shapes in the given [Party] object. If a shape is a [Shape.DrawableShape], + * it replaces the [DrawableImage] with a [ReferenceImage] and stores the [Drawable] in the [ImageStore]. + * + * @param party The Party object containing the shapes to be transformed. + * @return A new Party object with the transformed shapes. + */ + private fun storeImages(party: Party): Party { + val transformedShapes = party.shapes.map { shape -> + when (shape) { + is Shape.DrawableShape -> { + val referenceImage = drawableToReferenceImage(shape.image as DrawableImage) + shape.copy(image = referenceImage) + } + else -> shape + } + } + return party.copy(shapes = transformedShapes) + } + + /** + * Converts a [DrawableImage] to a [ReferenceImage] and stores the [Drawable] in the [ImageStore]. + * + * @param drawableImage The DrawableImage to be converted. + * @return A ReferenceImage with the same dimensions as the DrawableImage and a reference to the stored Drawable. + */ + fun drawableToReferenceImage(drawableImage: DrawableImage): ReferenceImage { + val id = imageStore.storeImage(drawableImage.drawable) + return ReferenceImage(id, drawableImage.width, drawableImage.height) + } + /** * Stop a particular particle system. All particles belonging to this system will directly disappear from the view. */ diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/DrawableImage.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/DrawableImage.kt new file mode 100644 index 00000000..1e38a9fc --- /dev/null +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/DrawableImage.kt @@ -0,0 +1,10 @@ +package nl.dionsegijn.konfetti.xml.image + +import android.graphics.drawable.Drawable +import nl.dionsegijn.konfetti.core.models.CoreImage + +data class DrawableImage( + val drawable: Drawable, + override val width: Int, + override val height: Int +) : CoreImage diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt new file mode 100644 index 00000000..36ce7fb9 --- /dev/null +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt @@ -0,0 +1,30 @@ +package nl.dionsegijn.konfetti.xml.image + +import android.graphics.drawable.Drawable +import nl.dionsegijn.konfetti.core.models.CoreImage +import nl.dionsegijn.konfetti.core.models.Shape + +/** + * The ImageStore class is used to store Drawable objects and provide a way to reference them. + * This is done for performance reasons and to allow the core library, which can't use Android Drawables, + * to work with images. + * + * Instead of converting a Drawable to a ByteBuffer, then to a Bitmap, and then back to a Drawable, + * which is inefficient for the render code, the Drawable is stored in the ImageStore. + * The rest of the application can then work with a simple integer reference to the Drawable. + * + * The ImageStore provides methods to store a Drawable and retrieve it using its reference. + */ +class ImageStore { + private val images = mutableMapOf() + + fun storeImage(drawable: Drawable): Int { + val id = drawable.hashCode() + images[id] = drawable + return id + } + + fun getImage(id: Int): Drawable? { + return images[id] + } +} diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageUtil.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageUtil.kt new file mode 100644 index 00000000..f49300d0 --- /dev/null +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageUtil.kt @@ -0,0 +1,19 @@ +package nl.dionsegijn.konfetti.xml.image + +import android.graphics.drawable.Drawable +import nl.dionsegijn.konfetti.core.models.Shape + +object ImageUtil { + + @JvmStatic + fun loadDrawable( + drawable: Drawable, + tint: Boolean = true, + applyAlpha: Boolean = true + ): Shape.DrawableShape { + val width = drawable.intrinsicWidth + val height = drawable.intrinsicHeight + val drawableImage = DrawableImage(drawable, width, height) + return Shape.DrawableShape(drawableImage, tint, applyAlpha) + } +} diff --git a/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt b/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt index 2f2f5dbf..e3102541 100644 --- a/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt +++ b/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt @@ -18,7 +18,7 @@ class Presets { damping = 0.9f, angle = Angle.TOP, spread = 45, - size = listOf(Size.SMALL, Size.LARGE), + size = listOf(Size.SMALL, Size.LARGE, Size.LARGE), timeToLive = 3000L, rotation = Rotation(), colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def), diff --git a/samples/xml-java/src/main/java/nl/dionsegijn/xml/java/MainActivity.java b/samples/xml-java/src/main/java/nl/dionsegijn/xml/java/MainActivity.java index ad0d099b..13970212 100644 --- a/samples/xml-java/src/main/java/nl/dionsegijn/xml/java/MainActivity.java +++ b/samples/xml-java/src/main/java/nl/dionsegijn/xml/java/MainActivity.java @@ -19,6 +19,7 @@ import nl.dionsegijn.konfetti.core.emitter.EmitterConfig; import nl.dionsegijn.konfetti.core.models.Shape; import nl.dionsegijn.konfetti.core.models.Size; +import nl.dionsegijn.konfetti.xml.image.ImageUtil; import nl.dionsegijn.konfetti.xml.KonfettiView; public class MainActivity extends AppCompatActivity { @@ -32,7 +33,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); final Drawable drawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_heart); - drawableShape = new Shape.DrawableShape(drawable, true, true); + drawableShape = ImageUtil.loadDrawable(drawable, true, true); konfettiView = findViewById(R.id.konfettiView); EmitterConfig emitterConfig = new Emitter(5L, TimeUnit.SECONDS).perSecond(50); From 61321f6f32405a75c5e1ccbf0d3137a76e93c7ff Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 11:25:32 +0100 Subject: [PATCH 03/11] Add drawable example to xml kotlin sample --- .../main/java/nl/dionsegijn/samples/shared/Presets.kt | 5 ++++- .../main/java/nl/dionsegijn/xml/kotlin/MainActivity.kt | 7 ++++++- samples/xml-kotlin/src/main/res/drawable/ic_heart.xml | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 samples/xml-kotlin/src/main/res/drawable/ic_heart.xml diff --git a/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt b/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt index e3102541..354eba0c 100644 --- a/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt +++ b/samples/shared/src/main/java/nl/dionsegijn/samples/shared/Presets.kt @@ -1,17 +1,19 @@ package nl.dionsegijn.samples.shared +import android.graphics.drawable.Drawable import nl.dionsegijn.konfetti.core.Angle import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.Position import nl.dionsegijn.konfetti.core.Rotation import nl.dionsegijn.konfetti.core.Spread import nl.dionsegijn.konfetti.core.emitter.Emitter +import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.konfetti.core.models.Size import java.util.concurrent.TimeUnit class Presets { companion object { - fun festive(): List { + fun festive(drawable: Shape.DrawableShape? = null): List { val party = Party( speed = 30f, maxSpeed = 50f, @@ -19,6 +21,7 @@ class Presets { angle = Angle.TOP, spread = 45, size = listOf(Size.SMALL, Size.LARGE, Size.LARGE), + shapes = listOf(Shape.Square, Shape.Circle, drawable).filterNotNull(), timeToLive = 3000L, rotation = Rotation(), colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def), diff --git a/samples/xml-kotlin/src/main/java/nl/dionsegijn/xml/kotlin/MainActivity.kt b/samples/xml-kotlin/src/main/java/nl/dionsegijn/xml/kotlin/MainActivity.kt index ce43a7e1..d1426acf 100644 --- a/samples/xml-kotlin/src/main/java/nl/dionsegijn/xml/kotlin/MainActivity.kt +++ b/samples/xml-kotlin/src/main/java/nl/dionsegijn/xml/kotlin/MainActivity.kt @@ -3,7 +3,9 @@ package nl.dionsegijn.xml.kotlin import android.os.Bundle import android.widget.Button import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.content.res.AppCompatResources import nl.dionsegijn.konfetti.xml.KonfettiView +import nl.dionsegijn.konfetti.xml.image.ImageUtil import nl.dionsegijn.samples.shared.Presets class MainActivity : AppCompatActivity() { @@ -22,10 +24,13 @@ class MainActivity : AppCompatActivity() { } private fun festive() { + val drawable = AppCompatResources.getDrawable(applicationContext, R.drawable.ic_heart) + val drawableShape = ImageUtil.loadDrawable(drawable!!) + /** * See [Presets] for this configuration */ - viewKonfetti.start(Presets.festive()) + viewKonfetti.start(Presets.festive(drawableShape)) } private fun explode() { diff --git a/samples/xml-kotlin/src/main/res/drawable/ic_heart.xml b/samples/xml-kotlin/src/main/res/drawable/ic_heart.xml new file mode 100644 index 00000000..cfba5d84 --- /dev/null +++ b/samples/xml-kotlin/src/main/res/drawable/ic_heart.xml @@ -0,0 +1,9 @@ + + + From 52dd72e0334060fb58ae69c14260a915ce6cb15f Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 11:31:51 +0100 Subject: [PATCH 04/11] Make pixelDensity no longer default to ensure its set --- .../src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt index 67ff96f3..03b871ae 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/PartySystem.kt @@ -15,7 +15,7 @@ import nl.dionsegijn.konfetti.core.models.CoreRect class PartySystem( val party: Party, val createdAt: Long = System.currentTimeMillis(), - pixelDensity: Float = 1f + pixelDensity: Float ) { var enabled = true From 2393f55f7e50fe8e017ab806f87b47e9eefe43cf Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 11:32:06 +0100 Subject: [PATCH 05/11] Fix compile issues for compose module --- .../dionsegijn/konfetti/compose/KonfettiView.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt index 426c0799..d4db17eb 100644 --- a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt @@ -1,5 +1,6 @@ package nl.dionsegijn.konfetti.compose +import android.content.res.Resources import android.graphics.Rect import androidx.compose.foundation.Canvas import androidx.compose.runtime.Composable @@ -14,6 +15,8 @@ import androidx.compose.ui.layout.onGloballyPositioned import nl.dionsegijn.konfetti.core.Particle import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.PartySystem +import nl.dionsegijn.konfetti.core.models.CoreRect +import nl.dionsegijn.konfetti.core.models.CoreRectImpl @Composable fun KonfettiView( @@ -37,10 +40,15 @@ fun KonfettiView( /** * Area in which the particles are being drawn */ - val drawArea = remember { mutableStateOf(Rect()) } + val drawArea = remember { mutableStateOf(CoreRectImpl()) } LaunchedEffect(Unit) { - partySystems = parties.map { PartySystem(it) } + partySystems = parties.map { + PartySystem( + party = it, + pixelDensity = Resources.getSystem().displayMetrics.density + ) + } while (true) { withFrameMillis { frameMs -> // Calculate time between frames, fallback to 0 when previous frame doesn't exist @@ -69,7 +77,8 @@ fun KonfettiView( Canvas( modifier = modifier .onGloballyPositioned { - drawArea.value = Rect(0, 0, it.size.width, it.size.height) + drawArea.value = + CoreRectImpl(0f, 0f, it.size.width.toFloat(), it.size.height.toFloat()) }, onDraw = { particles.value.forEach { particle -> From b7bb1ddad53f15fde2690eae64caae7cd1059ed0 Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 13:51:18 +0100 Subject: [PATCH 06/11] Support the new core implementation in the compose module --- .../dionsegijn/konfetti/compose/DrawShapes.kt | 42 ++++++++++------- .../konfetti/compose/KonfettiView.kt | 46 ++++++++++++++++++- .../konfetti/compose/image/DrawableImage.kt | 10 ++++ .../konfetti/compose/image/ImageStore.kt | 29 ++++++++++++ .../konfetti/compose/image/ImageUtil.kt | 19 ++++++++ .../konfetti/core/models/CoreImageStore.kt | 6 +++ .../konfetti/xml/image/ImageStore.kt | 11 +++-- .../dionsegijn/xml/compose/ComposeActivity.kt | 3 +- .../xml/compose/KonfettiViewModel.kt | 5 +- 9 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/DrawableImage.kt create mode 100644 konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt create mode 100644 konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageUtil.kt create mode 100644 konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImageStore.kt diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/DrawShapes.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/DrawShapes.kt index 88500130..67f0cadc 100644 --- a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/DrawShapes.kt +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/DrawShapes.kt @@ -12,18 +12,20 @@ import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.nativeCanvas import nl.dionsegijn.konfetti.core.Particle +import nl.dionsegijn.konfetti.core.models.ReferenceImage import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.konfetti.core.models.Shape.Circle import nl.dionsegijn.konfetti.core.models.Shape.DrawableShape import nl.dionsegijn.konfetti.core.models.Shape.Rectangle import nl.dionsegijn.konfetti.core.models.Shape.Square +import nl.dionsegijn.konfetti.xml.image.ImageStore /** * Draw a shape to `compose canvas`. Implementations are expected to draw within a square of size * `size` and must vertically/horizontally center their asset if it does not have an equal width * and height. */ -fun Shape.draw(drawScope: DrawScope, particle: Particle, imageResource: ImageBitmap? = null) { +fun Shape.draw(drawScope: DrawScope, particle: Particle, imageResource: ImageBitmap? = null, imageStore: ImageStore) { when (this) { Circle -> { val offsetMiddle = particle.width / 2 @@ -50,25 +52,31 @@ fun Shape.draw(drawScope: DrawScope, particle: Particle, imageResource: ImageBit ) } is DrawableShape -> { - drawScope.drawIntoCanvas { - if (tint) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - drawable.colorFilter = BlendModeColorFilter(particle.color, BlendMode.SRC_IN) - } else { - drawable.setColorFilter(particle.color, PorterDuff.Mode.SRC_IN) + val referenceImage = image + if (referenceImage is ReferenceImage) { + val drawable = imageStore.getImage(referenceImage.reference) ?: return + + drawScope.drawIntoCanvas { + // Making use of the ImageStore for performance reasons, see ImageStore for more info + if (tint) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + drawable.colorFilter = BlendModeColorFilter(particle.color, BlendMode.SRC_IN) + } else { + drawable.setColorFilter(particle.color, PorterDuff.Mode.SRC_IN) + } + } else if (applyAlpha) { + drawable.alpha = particle.alpha } - } else if (applyAlpha) { - drawable.alpha = particle.alpha - } - val size = particle.width - val height = (size * heightRatio).toInt() - val top = ((size - height) / 2f).toInt() + val size = particle.width + val height = (size * heightRatio).toInt() + val top = ((size - height) / 2f).toInt() - val x = particle.y.toInt() - val y = particle.x.toInt() - drawable.setBounds(y, top + x, size.toInt() + y, top + height + x) - drawable.draw(it.nativeCanvas) + val x = particle.y.toInt() + val y = particle.x.toInt() + drawable.setBounds(y, top + x, size.toInt() + y, top + height + x) + drawable.draw(it.nativeCanvas) + } } } } diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt index d4db17eb..e8cbba09 100644 --- a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt @@ -2,6 +2,7 @@ package nl.dionsegijn.konfetti.compose import android.content.res.Resources import android.graphics.Rect +import android.graphics.drawable.Drawable import androidx.compose.foundation.Canvas import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -17,6 +18,10 @@ import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.PartySystem import nl.dionsegijn.konfetti.core.models.CoreRect import nl.dionsegijn.konfetti.core.models.CoreRectImpl +import nl.dionsegijn.konfetti.core.models.ReferenceImage +import nl.dionsegijn.konfetti.core.models.Shape +import nl.dionsegijn.konfetti.xml.image.DrawableImage +import nl.dionsegijn.konfetti.xml.image.ImageStore @Composable fun KonfettiView( @@ -42,10 +47,15 @@ fun KonfettiView( */ val drawArea = remember { mutableStateOf(CoreRectImpl()) } + /** + * Store for drawable images + */ + val imageStore = remember { ImageStore() } + LaunchedEffect(Unit) { partySystems = parties.map { PartySystem( - party = it, + party = storeImages(it, imageStore), pixelDensity = Resources.getSystem().displayMetrics.density ) } @@ -96,14 +106,46 @@ fun KonfettiView( pivot = Offset(particle.x + (particle.width / 2), particle.y) ) }) { - particle.shape.draw(this, particle) + particle.shape.draw(drawScope = this, particle = particle, imageStore = imageStore) } } } ) } +/** + * Transforms the shapes in the given [Party] object. If a shape is a [Shape.DrawableShape], + * it replaces the [DrawableImage] with a [ReferenceImage] and stores the [Drawable] in the [ImageStore]. + * + * @param party The Party object containing the shapes to be transformed. + * @return A new Party object with the transformed shapes. + */ +fun storeImages(party: Party, imageStore: ImageStore): Party { + val transformedShapes = party.shapes.map { shape -> + when (shape) { + is Shape.DrawableShape -> { + val referenceImage = drawableToReferenceImage(shape.image as DrawableImage, imageStore) + shape.copy(image = referenceImage) + } + else -> shape + } + } + return party.copy(shapes = transformedShapes) +} + +/** + * Converts a [DrawableImage] to a [ReferenceImage] and stores the [Drawable] in the [ImageStore]. + * + * @param drawableImage The DrawableImage to be converted. + * @return A ReferenceImage with the same dimensions as the DrawableImage and a reference to the stored Drawable. + */ +fun drawableToReferenceImage(drawableImage: DrawableImage, imageStore: ImageStore): ReferenceImage { + val id = imageStore.storeImage(drawableImage.drawable) + return ReferenceImage(id, drawableImage.width, drawableImage.height) +} + fun getTotalTimeRunning(startTime: Long): Long { val currentTime = System.currentTimeMillis() return (currentTime - startTime) } + diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/DrawableImage.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/DrawableImage.kt new file mode 100644 index 00000000..1e38a9fc --- /dev/null +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/DrawableImage.kt @@ -0,0 +1,10 @@ +package nl.dionsegijn.konfetti.xml.image + +import android.graphics.drawable.Drawable +import nl.dionsegijn.konfetti.core.models.CoreImage + +data class DrawableImage( + val drawable: Drawable, + override val width: Int, + override val height: Int +) : CoreImage diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt new file mode 100644 index 00000000..63b7813e --- /dev/null +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt @@ -0,0 +1,29 @@ +package nl.dionsegijn.konfetti.xml.image + +import android.graphics.drawable.Drawable +import nl.dionsegijn.konfetti.core.models.CoreImageStore + +/** + * The ImageStore class is used to store Drawable objects and provide a way to reference them. + * This is done for performance reasons and to allow the core library, which can't use Android Drawables, + * to work with images. + * + * Instead of converting a Drawable to a ByteBuffer, then to a Bitmap, and then back to a Drawable, + * which is inefficient for the render code, the Drawable is stored in the ImageStore. + * The rest of the application can then work with a simple integer reference to the Drawable. + * + * The ImageStore provides methods to store a Drawable and retrieve it using its reference. + */ +class ImageStore: CoreImageStore { + private val images = mutableMapOf() + + override fun storeImage(image: Drawable): Int { + val id = image.hashCode() + images[id] = image + return id + } + + override fun getImage(id: Int): Drawable? { + return images[id] + } +} diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageUtil.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageUtil.kt new file mode 100644 index 00000000..f49300d0 --- /dev/null +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageUtil.kt @@ -0,0 +1,19 @@ +package nl.dionsegijn.konfetti.xml.image + +import android.graphics.drawable.Drawable +import nl.dionsegijn.konfetti.core.models.Shape + +object ImageUtil { + + @JvmStatic + fun loadDrawable( + drawable: Drawable, + tint: Boolean = true, + applyAlpha: Boolean = true + ): Shape.DrawableShape { + val width = drawable.intrinsicWidth + val height = drawable.intrinsicHeight + val drawableImage = DrawableImage(drawable, width, height) + return Shape.DrawableShape(drawableImage, tint, applyAlpha) + } +} diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImageStore.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImageStore.kt new file mode 100644 index 00000000..c48c838c --- /dev/null +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImageStore.kt @@ -0,0 +1,6 @@ +package nl.dionsegijn.konfetti.core.models + +interface CoreImageStore { + fun storeImage(image: T): Int + fun getImage(id: Int): T? +} diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt index 36ce7fb9..44a10eea 100644 --- a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt @@ -2,6 +2,7 @@ package nl.dionsegijn.konfetti.xml.image import android.graphics.drawable.Drawable import nl.dionsegijn.konfetti.core.models.CoreImage +import nl.dionsegijn.konfetti.core.models.CoreImageStore import nl.dionsegijn.konfetti.core.models.Shape /** @@ -15,16 +16,16 @@ import nl.dionsegijn.konfetti.core.models.Shape * * The ImageStore provides methods to store a Drawable and retrieve it using its reference. */ -class ImageStore { +class ImageStore: CoreImageStore { private val images = mutableMapOf() - fun storeImage(drawable: Drawable): Int { - val id = drawable.hashCode() - images[id] = drawable + override fun storeImage(image: Drawable): Int { + val id = image.hashCode() + images[id] = image return id } - fun getImage(id: Int): Drawable? { + override fun getImage(id: Int): Drawable? { return images[id] } } diff --git a/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/ComposeActivity.kt b/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/ComposeActivity.kt index 728098db..efe1adfb 100644 --- a/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/ComposeActivity.kt +++ b/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/ComposeActivity.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.platform.LocalContext import nl.dionsegijn.konfetti.compose.KonfettiView import nl.dionsegijn.konfetti.compose.OnParticleSystemUpdateListener import nl.dionsegijn.konfetti.core.PartySystem +import nl.dionsegijn.konfetti.xml.image.ImageUtil import nl.dionsegijn.xml.compose.ui.theme.KonfettiTheme class ComposeActivity : ComponentActivity() { @@ -59,7 +60,7 @@ fun KonfettiUI(viewModel: KonfettiViewModel = KonfettiViewModel()) { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Button(onClick = { viewModel.festive() }) { + Button(onClick = { viewModel.festive(ImageUtil.loadDrawable(drawable!!)) }) { Text( text = "Festive", ) diff --git a/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/KonfettiViewModel.kt b/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/KonfettiViewModel.kt index e542e265..78f97978 100644 --- a/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/KonfettiViewModel.kt +++ b/samples/compose-kotlin/src/main/java/nl/dionsegijn/xml/compose/KonfettiViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import nl.dionsegijn.konfetti.core.Party +import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.samples.shared.Presets class KonfettiViewModel : ViewModel() { @@ -11,11 +12,11 @@ class KonfettiViewModel : ViewModel() { private val _state = MutableLiveData(State.Idle) val state: LiveData = _state - fun festive() { + fun festive(drawable: Shape.DrawableShape) { /** * See [Presets] for this configuration */ - _state.value = State.Started(Presets.festive()) + _state.value = State.Started(Presets.festive(drawable)) } fun explode() { From c01733bfbc1df63dbd3be3d63a5629aa3038d2e5 Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 14:04:02 +0100 Subject: [PATCH 07/11] Run spotless --- .../main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt | 3 --- .../java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt | 2 +- .../main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt | 4 +--- .../main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt | 2 +- .../main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt | 4 +--- 5 files changed, 4 insertions(+), 11 deletions(-) diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt index e8cbba09..b55f7cd4 100644 --- a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/KonfettiView.kt @@ -1,7 +1,6 @@ package nl.dionsegijn.konfetti.compose import android.content.res.Resources -import android.graphics.Rect import android.graphics.drawable.Drawable import androidx.compose.foundation.Canvas import androidx.compose.runtime.Composable @@ -16,7 +15,6 @@ import androidx.compose.ui.layout.onGloballyPositioned import nl.dionsegijn.konfetti.core.Particle import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.PartySystem -import nl.dionsegijn.konfetti.core.models.CoreRect import nl.dionsegijn.konfetti.core.models.CoreRectImpl import nl.dionsegijn.konfetti.core.models.ReferenceImage import nl.dionsegijn.konfetti.core.models.Shape @@ -148,4 +146,3 @@ fun getTotalTimeRunning(startTime: Long): Long { val currentTime = System.currentTimeMillis() return (currentTime - startTime) } - diff --git a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt index 63b7813e..e2a5679c 100644 --- a/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt +++ b/konfetti/compose/src/main/java/nl/dionsegijn/konfetti/compose/image/ImageStore.kt @@ -14,7 +14,7 @@ import nl.dionsegijn.konfetti.core.models.CoreImageStore * * The ImageStore provides methods to store a Drawable and retrieve it using its reference. */ -class ImageStore: CoreImageStore { +class ImageStore : CoreImageStore { private val images = mutableMapOf() override fun storeImage(image: Drawable): Int { diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt index ac5872ec..bb04960a 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreImage.kt @@ -1,7 +1,5 @@ package nl.dionsegijn.konfetti.core.models -import java.nio.ByteBuffer - interface CoreImage { val width: Int val height: Int @@ -11,4 +9,4 @@ data class ReferenceImage( val reference: Int, override val width: Int, override val height: Int -): CoreImage \ No newline at end of file +) : CoreImage diff --git a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt index 762635e5..62e29549 100644 --- a/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt +++ b/konfetti/core/src/main/java/nl/dionsegijn/konfetti/core/models/CoreRect.kt @@ -31,4 +31,4 @@ class CoreRectImpl( override fun contains(px: Int, py: Int): Boolean { return super.contains(px, py) } -} \ No newline at end of file +} diff --git a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt index 44a10eea..e2a5679c 100644 --- a/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt +++ b/konfetti/xml/src/main/java/nl/dionsegijn/konfetti/xml/image/ImageStore.kt @@ -1,9 +1,7 @@ package nl.dionsegijn.konfetti.xml.image import android.graphics.drawable.Drawable -import nl.dionsegijn.konfetti.core.models.CoreImage import nl.dionsegijn.konfetti.core.models.CoreImageStore -import nl.dionsegijn.konfetti.core.models.Shape /** * The ImageStore class is used to store Drawable objects and provide a way to reference them. @@ -16,7 +14,7 @@ import nl.dionsegijn.konfetti.core.models.Shape * * The ImageStore provides methods to store a Drawable and retrieve it using its reference. */ -class ImageStore: CoreImageStore { +class ImageStore : CoreImageStore { private val images = mutableMapOf() override fun storeImage(image: Drawable): Int { From 6bd7dfcf9c0e5857bbd49b0d174b2ea3bde22e7e Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 15:10:49 +0100 Subject: [PATCH 08/11] Update tests in core module --- .../konfetti/core/PartySystemTest.kt | 24 ++++--------------- .../konfetti/core/emitter/PartyEmitterTest.kt | 20 ++++++++-------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/PartySystemTest.kt b/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/PartySystemTest.kt index 1bfe2220..82305be9 100644 --- a/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/PartySystemTest.kt +++ b/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/PartySystemTest.kt @@ -1,7 +1,8 @@ package nl.dionsegijn.konfetti.core -import android.graphics.Rect import nl.dionsegijn.konfetti.core.emitter.Emitter +import nl.dionsegijn.konfetti.core.models.CoreRect +import nl.dionsegijn.konfetti.core.models.CoreRectImpl import org.junit.Assert import org.junit.Test import org.mockito.ArgumentMatchers.anyInt @@ -9,8 +10,8 @@ import org.mockito.Mockito class PartySystemTest { - private val rect: Rect = Mockito.mock(Rect::class.java).apply { - Mockito.`when`(height()).thenReturn(1000) + private val rect: CoreRect = Mockito.mock(CoreRectImpl::class.java).apply { + Mockito.`when`(height).thenReturn(1000f) Mockito.`when`(contains(anyInt(), anyInt())).thenReturn(true) } @@ -37,21 +38,6 @@ class PartySystemTest { Assert.assertEquals(2, r3.size) // expected 2, one for every 0.025ms } - @Test - fun `Test creating Particles with high initial deltaTime`() { - val party = Party( - emitter = Emitter(100L).max(2) - ) - val system = PartySystem(party, pixelDensity = 1f) - - Assert.assertTrue(system.enabled) - Assert.assertFalse(system.isDoneEmitting()) - - // Particles are removed because alpha is 0 with so much time passed - val r1 = system.render(60f, rect) - Assert.assertEquals(0, r1.size) - } - @Test fun `Test PartySystem set to disabled stops generating particles`() { val party = Party( @@ -87,7 +73,7 @@ class PartySystemTest { val system = PartySystem(party, pixelDensity = 1f) // Set drawArea to 1 pixel to let every particle directly disappear for this test - Mockito.`when`(rect.height()).thenReturn(1) + Mockito.`when`(rect.height).thenReturn(1f) Assert.assertTrue(system.enabled) system.render(deltaTime, rect) // dt: 0.017f diff --git a/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitterTest.kt b/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitterTest.kt index 6eba182b..ce1f7b1b 100644 --- a/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitterTest.kt +++ b/konfetti/core/src/test/java/nl/dionsegijn/konfetti/core/emitter/PartyEmitterTest.kt @@ -1,11 +1,11 @@ package nl.dionsegijn.konfetti.core.emitter -import android.graphics.Color -import android.graphics.Rect import nl.dionsegijn.konfetti.core.Angle import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.Position import nl.dionsegijn.konfetti.core.Rotation +import nl.dionsegijn.konfetti.core.models.CoreRect +import nl.dionsegijn.konfetti.core.models.CoreRectImpl import nl.dionsegijn.konfetti.core.models.Shape import nl.dionsegijn.konfetti.core.models.Size import nl.dionsegijn.konfetti.core.models.Vector @@ -16,13 +16,13 @@ import org.mockito.Mockito import java.util.Random class PartyEmitterTest { - - private val drawArea: Rect = Mockito.mock(Rect::class.java).apply { - Mockito.`when`(height()).thenReturn(1000) - Mockito.`when`(width()).thenReturn(1000) - Mockito.`when`(contains(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())) - .thenReturn(true) - } + private val drawArea: CoreRect = + Mockito.mock(CoreRectImpl::class.java).apply { + Mockito.`when`(height).thenReturn(1000f) + Mockito.`when`(width).thenReturn(1000f) + Mockito.`when`(contains(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())) + .thenReturn(true) + } // Average time between for each frame private val deltaTime = 0.017f @@ -35,7 +35,7 @@ class PartyEmitterTest { maxSpeed = -1f, damping = 0.9f, size = listOf(Size(sizeInDp = 6, mass = 5f, massVariance = 0f)), - colors = listOf(Color.RED), + colors = listOf(0xFF0000), shapes = listOf(Shape.Square), timeToLive = 1000L, fadeOutEnabled = false, From 545b4c5c45dd71ac8f05aa939d6b975cd8fc2ec5 Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 15:12:18 +0100 Subject: [PATCH 09/11] Make gradle file jvm only --- konfetti/core/build.gradle.kts | 37 ++++------------------------------ 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/konfetti/core/build.gradle.kts b/konfetti/core/build.gradle.kts index 7a7d1468..8831a36e 100644 --- a/konfetti/core/build.gradle.kts +++ b/konfetti/core/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("com.android.library") - id("kotlin-android") + id("java-library") + id("kotlin") id("com.diffplug.spotless") } @@ -9,47 +9,18 @@ apply(from = "../../scripts/publish-module.gradle.kts") spotless { kotlin { - ktlint("0.37.2") + ktlint("1.1.0") target("src/**/*.kt") } java { removeUnusedImports() - googleJavaFormat("1.5") + googleJavaFormat("1.15.0") target("**/*.java") } } -android { - compileSdk = buildVersions.compileSdk - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - } - - defaultConfig { - minSdk = 15 - } - buildTypes { - release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") - } - } - namespace = "nl.dionsegijn.konfetti.core" - lint { - abortOnError = true - baseline = file("lint-baseline.xml") - } -} - dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib:${Constants.kotlinVersion}") testImplementation(libs.test.junit) testImplementation(libs.test.mockito) - } From 1cef7013625e6fc72eab99c0601234845d58d5d9 Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 15:12:44 +0100 Subject: [PATCH 10/11] Bump version --- buildSrc/src/main/kotlin/Constants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt index 838b343d..c97f2dbc 100644 --- a/buildSrc/src/main/kotlin/Constants.kt +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -5,7 +5,7 @@ object buildVersions { object Constants { const val composeVersion = "1.4.3" - const val konfettiVersion = "2.0.4" + const val konfettiVersion = "2.1.0-beta01" const val kotlinVersion = "1.8.10" } From 5a4ba383fad86a3079300cbcc0f899b236479049 Mon Sep 17 00:00:00 2001 From: Dion Segijn Date: Thu, 28 Dec 2023 16:16:39 +0100 Subject: [PATCH 11/11] Include javadoc for sonatype Looks like this was no longer included as core module transitioned to a jvm only module --- build.gradle.kts | 2 ++ scripts/publish-module.gradle.kts | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index b7d80db6..3f98de27 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("io.github.gradle-nexus.publish-plugin") version "1.3.0" + id("org.jetbrains.dokka") version "1.7.0" } buildscript { @@ -14,6 +15,7 @@ buildscript { classpath("com.github.dcendents:android-maven-gradle-plugin:2.1") classpath("com.diffplug.spotless:spotless-plugin-gradle:5.14.2") classpath("io.github.gradle-nexus:publish-plugin:1.3.0") + classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.7.0") } } diff --git a/scripts/publish-module.gradle.kts b/scripts/publish-module.gradle.kts index 56b295aa..8e54ea9a 100644 --- a/scripts/publish-module.gradle.kts +++ b/scripts/publish-module.gradle.kts @@ -1,5 +1,6 @@ apply(plugin = "maven-publish") apply(plugin = "signing") +apply(plugin = "org.jetbrains.dokka") val PLUGIN_ANDROID_LIBRARY = "com.android.library" val PUBLICATION_NAME = "release" @@ -20,6 +21,15 @@ val sourcesJar by tasks.registering(Jar::class) { } } +val javadocJar by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + + val dokkaJavadocTask = tasks.getByName("dokkaJavadoc") + + from(dokkaJavadocTask) + dependsOn(dokkaJavadocTask) +} + val group = NexusConfig.PUBLISH_GROUP_ID val version = NexusConfig.PUBLISH_VERSION @@ -41,6 +51,7 @@ afterEvaluate { } artifact(sourcesJar.get()) + artifact(javadocJar.get()) // Mostly self-explanatory metadata pom {