-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
294 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
core/src/main/kotlin/org/jetbrains/jewel/painter/BadgePainter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package org.jetbrains.jewel.painter | ||
|
||
import androidx.compose.ui.geometry.Offset | ||
import androidx.compose.ui.geometry.Rect | ||
import androidx.compose.ui.geometry.Size | ||
import androidx.compose.ui.graphics.BlendMode | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.ColorFilter | ||
import androidx.compose.ui.graphics.Paint | ||
import androidx.compose.ui.graphics.drawOutline | ||
import androidx.compose.ui.graphics.drawscope.DrawScope | ||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas | ||
import androidx.compose.ui.graphics.painter.Painter | ||
import androidx.compose.ui.graphics.withSaveLayer | ||
import androidx.compose.ui.unit.Density | ||
import androidx.compose.ui.unit.LayoutDirection | ||
import org.jetbrains.jewel.painter.badge.BadgeShape | ||
|
||
class BadgePainter(source: Painter, private val color: Color, private val shape: BadgeShape) : DelegatePainter(source) { | ||
|
||
/** | ||
* Optional [Paint] used to draw contents into an offscreen layer in order to apply | ||
* alpha or [ColorFilter] parameters accordingly. If no alpha or [ColorFilter] is | ||
* provided or the [Painter] implementation implements [applyAlpha] and | ||
* [applyColorFilter] then this paint is not used | ||
*/ | ||
private var layerPaint: Paint? = null | ||
|
||
/** | ||
* Lazily create a [Paint] object or return the existing instance if it is already allocated | ||
*/ | ||
private fun obtainPaint(): Paint { | ||
var target = layerPaint | ||
if (target == null) { | ||
target = Paint() | ||
layerPaint = target | ||
} | ||
return target | ||
} | ||
|
||
private fun DrawScope.drawHole() { | ||
val badge = shape.createHoleOutline(size, layoutDirection, Density(density)) | ||
drawOutline(badge, Color.White, alpha, blendMode = BlendMode.Clear) | ||
} | ||
|
||
private fun DrawScope.drawBadge() { | ||
val badge = shape.createOutline(size, layoutDirection, Density(density)) | ||
drawOutline(badge, color, alpha) | ||
} | ||
|
||
override fun DrawScope.onDraw() { | ||
val layerRect = Rect(Offset.Zero, Size(size.width, size.height)) | ||
drawIntoCanvas { canvas -> | ||
canvas.withSaveLayer(layerRect, obtainPaint()) { | ||
drawDelegate() | ||
drawHole() | ||
drawBadge() | ||
} | ||
} | ||
} | ||
} | ||
|
||
open class DelegatePainter(private val delegate: Painter) : Painter() { | ||
|
||
override val intrinsicSize: Size | ||
get() = delegate.intrinsicSize | ||
|
||
protected var alpha: Float = 1f | ||
|
||
protected var filter: ColorFilter? = null | ||
|
||
protected var layoutDirection: LayoutDirection = LayoutDirection.Ltr | ||
|
||
override fun applyAlpha(alpha: Float): Boolean { | ||
this.alpha = alpha | ||
return true | ||
} | ||
|
||
override fun applyColorFilter(colorFilter: ColorFilter?): Boolean { | ||
this.filter = colorFilter | ||
return true | ||
} | ||
|
||
override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean { | ||
this.layoutDirection = layoutDirection | ||
return true | ||
} | ||
|
||
protected fun DrawScope.drawDelegate() { | ||
with(delegate) { | ||
draw(this@drawDelegate.size, alpha, filter) | ||
} | ||
} | ||
|
||
override fun DrawScope.onDraw() { | ||
drawDelegate() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
core/src/main/kotlin/org/jetbrains/jewel/painter/badge/BadgeShape.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package org.jetbrains.jewel.painter.badge | ||
|
||
import androidx.compose.ui.geometry.Rect | ||
import androidx.compose.ui.geometry.Size | ||
import androidx.compose.ui.graphics.Outline | ||
import androidx.compose.ui.graphics.Shape | ||
import androidx.compose.ui.unit.Density | ||
import androidx.compose.ui.unit.LayoutDirection | ||
|
||
interface BadgeShape : Shape { | ||
|
||
fun createHoleOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline | ||
} | ||
|
||
internal val emptyOutline = Outline.Rectangle(Rect.Zero) |
64 changes: 64 additions & 0 deletions
64
core/src/main/kotlin/org/jetbrains/jewel/painter/badge/DotBadgeShape.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.jetbrains.jewel.painter.badge | ||
|
||
import androidx.compose.ui.geometry.CornerRadius | ||
import androidx.compose.ui.geometry.RoundRect | ||
import androidx.compose.ui.geometry.Size | ||
import androidx.compose.ui.graphics.Outline | ||
import androidx.compose.ui.unit.Density | ||
import androidx.compose.ui.unit.LayoutDirection | ||
import org.jetbrains.jewel.GenerateDataFunctions | ||
|
||
/** | ||
* @see com.intellij.ui.BadgeDotProvider | ||
*/ | ||
@GenerateDataFunctions | ||
class DotBadgeShape( | ||
val x: Float = 16.5f / 20, | ||
val y: Float = 3.5f / 20, | ||
val radius: Float = 3.5f / 20, | ||
val border: Float = 1.5f / 20, | ||
) : BadgeShape { | ||
|
||
override fun createHoleOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline = | ||
createOutline(size, layoutDirection, density, hole = true) | ||
|
||
override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline = | ||
createOutline(size, layoutDirection, density, hole = false) | ||
|
||
private fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density, hole: Boolean): Outline { | ||
val dotSize = size.width.coerceAtMost(size.height) | ||
|
||
if (dotSize <= 0) return emptyOutline | ||
|
||
val radius = dotSize * radius | ||
if (radius <= 0) return emptyOutline | ||
|
||
val x = size.width * x | ||
if (0 > x + radius || x - radius > size.width) return emptyOutline | ||
|
||
val y = size.height * y | ||
if (0 > y + radius || y - radius > size.height) return emptyOutline | ||
|
||
val border = when { | ||
hole -> dotSize * border | ||
else -> 0.0f | ||
} | ||
|
||
val r = radius + border.coerceAtLeast(0.0f) | ||
|
||
return Outline.Rounded( | ||
RoundRect( | ||
left = x - r, | ||
top = y - r, | ||
right = x + r, | ||
bottom = y + r, | ||
cornerRadius = CornerRadius(r), | ||
), | ||
) | ||
} | ||
|
||
companion object { | ||
|
||
val Default = DotBadgeShape() | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
core/src/main/kotlin/org/jetbrains/jewel/painter/hints/Badge.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.jetbrains.jewel.painter.hints | ||
|
||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.isSpecified | ||
import androidx.compose.ui.graphics.painter.Painter | ||
import org.jetbrains.jewel.painter.BadgePainter | ||
import org.jetbrains.jewel.painter.PainterHint | ||
import org.jetbrains.jewel.painter.PainterWrapperHint | ||
import org.jetbrains.jewel.painter.badge.BadgeShape | ||
|
||
private class BadgeImpl(private val color: Color, private val shape: BadgeShape) : PainterWrapperHint { | ||
|
||
override fun wrap(painter: Painter): Painter = BadgePainter(painter, color, shape) | ||
|
||
override fun toString(): String = "Badge(color=$color, shape=$shape)" | ||
|
||
override fun hashCode(): Int { | ||
var result = color.hashCode() | ||
result = 31 * result + shape.hashCode() | ||
return result | ||
} | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (this === other) return true | ||
if (other !is BadgeImpl) return false | ||
|
||
if (color != other.color) return false | ||
if (shape != other.shape) return false | ||
|
||
return true | ||
} | ||
} | ||
|
||
fun Badge(color: Color, shape: BadgeShape): PainterHint = if (color.isSpecified) { | ||
BadgeImpl(color, shape) | ||
} else { | ||
PainterHint.None | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters