Skip to content

Commit

Permalink
Badge painter hint support (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
devkanro authored and rock3r committed Oct 23, 2023
1 parent beb0c16 commit a123dfc
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons
import org.jetbrains.jewel.ui.component.GroupHeader
import org.jetbrains.jewel.ui.component.Icon
import org.jetbrains.jewel.ui.painter.badge.DotBadgeShape
import org.jetbrains.jewel.ui.painter.hints.Badge
import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider

@Composable
Expand All @@ -38,5 +40,8 @@ internal fun Icons() {
ColorFilter.tint(Color.Magenta, BlendMode.Multiply),
Modifier.size(128.dp),
)

val badged by iconProvider.getPainter(Badge(Color.Red, DotBadgeShape.Default))
Icon(badged, "Jewel Logo", Modifier.size(20.dp))
}
}
59 changes: 59 additions & 0 deletions ui/api/ui.api
Original file line number Diff line number Diff line change
Expand Up @@ -2010,6 +2010,11 @@ public final class org/jetbrains/jewel/ui/component/styling/TooltipStylingKt {
public static final fun getLocalTooltipStyle ()Landroidx/compose/runtime/ProvidableCompositionLocal;
}

public final class org/jetbrains/jewel/ui/painter/BadgePainter : org/jetbrains/jewel/ui/painter/DelegatePainter {
public static final field $stable I
public synthetic fun <init> (Landroidx/compose/ui/graphics/painter/Painter;JLorg/jetbrains/jewel/ui/painter/badge/BadgeShape;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public abstract class org/jetbrains/jewel/ui/painter/BasePainterHintsProvider : org/jetbrains/jewel/ui/painter/PainterHintsProvider {
public static final field $stable I
public fun <init> (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
Expand All @@ -2032,6 +2037,23 @@ public final class org/jetbrains/jewel/ui/painter/CommonPainterHintsProvider : o
public fun priorityHints (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Ljava/util/List;
}

public class org/jetbrains/jewel/ui/painter/DelegatePainter : androidx/compose/ui/graphics/painter/Painter {
public static final field $stable I
public fun <init> (Landroidx/compose/ui/graphics/painter/Painter;)V
protected fun applyAlpha (F)Z
protected fun applyColorFilter (Landroidx/compose/ui/graphics/ColorFilter;)Z
protected fun applyLayoutDirection (Landroidx/compose/ui/unit/LayoutDirection;)Z
protected final fun drawDelegate (Landroidx/compose/ui/graphics/drawscope/DrawScope;)V
protected final fun getAlpha ()F
protected final fun getFilter ()Landroidx/compose/ui/graphics/ColorFilter;
public fun getIntrinsicSize-NH-jbRc ()J
protected final fun getLayoutDirection ()Landroidx/compose/ui/unit/LayoutDirection;
protected fun onDraw (Landroidx/compose/ui/graphics/drawscope/DrawScope;)V
protected final fun setAlpha (F)V
protected final fun setFilter (Landroidx/compose/ui/graphics/ColorFilter;)V
protected final fun setLayoutDirection (Landroidx/compose/ui/unit/LayoutDirection;)V
}

public abstract interface class org/jetbrains/jewel/ui/painter/PainterHint {
public static final field None Lorg/jetbrains/jewel/ui/painter/PainterHint$None;
public abstract fun canApplyTo (Ljava/lang/String;)Z
Expand Down Expand Up @@ -2103,6 +2125,14 @@ public final class org/jetbrains/jewel/ui/painter/PainterSvgPatchHint$DefaultImp
public static fun canApplyTo (Lorg/jetbrains/jewel/ui/painter/PainterSvgPatchHint;Ljava/lang/String;)Z
}

public abstract interface class org/jetbrains/jewel/ui/painter/PainterWrapperHint : org/jetbrains/jewel/ui/painter/PainterHint {
public abstract fun wrap (Landroidx/compose/ui/graphics/painter/Painter;)Landroidx/compose/ui/graphics/painter/Painter;
}

public final class org/jetbrains/jewel/ui/painter/PainterWrapperHint$DefaultImpls {
public static fun canApplyTo (Lorg/jetbrains/jewel/ui/painter/PainterWrapperHint;Ljava/lang/String;)Z
}

public final class org/jetbrains/jewel/ui/painter/ResourcePainterProvider : org/jetbrains/jewel/ui/painter/PainterProvider {
public static final field $stable I
public fun <init> (Ljava/lang/String;[Ljava/lang/ClassLoader;)V
Expand All @@ -2129,6 +2159,35 @@ public final class org/jetbrains/jewel/ui/painter/XmlPainterHint$DefaultImpls {
public static fun canApplyTo (Lorg/jetbrains/jewel/ui/painter/XmlPainterHint;Ljava/lang/String;)Z
}

public abstract interface class org/jetbrains/jewel/ui/painter/badge/BadgeShape : androidx/compose/ui/graphics/Shape {
public abstract fun createHoleOutline-Pq9zytI (JLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Landroidx/compose/ui/graphics/Outline;
}

public final class org/jetbrains/jewel/ui/painter/badge/DotBadgeShape : org/jetbrains/jewel/ui/painter/badge/BadgeShape {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/ui/painter/badge/DotBadgeShape$Companion;
public fun <init> ()V
public fun <init> (FFFF)V
public synthetic fun <init> (FFFFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun createHoleOutline-Pq9zytI (JLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Landroidx/compose/ui/graphics/Outline;
public fun createOutline-Pq9zytI (JLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Landroidx/compose/ui/graphics/Outline;
public fun equals (Ljava/lang/Object;)Z
public final fun getBorder ()F
public final fun getRadius ()F
public final fun getX ()F
public final fun getY ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class org/jetbrains/jewel/ui/painter/badge/DotBadgeShape$Companion {
public final fun getDefault ()Lorg/jetbrains/jewel/ui/painter/badge/DotBadgeShape;
}

public final class org/jetbrains/jewel/ui/painter/hints/BadgeKt {
public static final fun Badge-DxMtmZc (JLorg/jetbrains/jewel/ui/painter/badge/BadgeShape;)Lorg/jetbrains/jewel/ui/painter/PainterHint;
}

public final class org/jetbrains/jewel/ui/painter/hints/DarkKt {
public static final fun Dark (Z)Lorg/jetbrains/jewel/ui/painter/PainterHint;
public static synthetic fun Dark$default (ZILjava/lang/Object;)Lorg/jetbrains/jewel/ui/painter/PainterHint;
Expand Down
60 changes: 60 additions & 0 deletions ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/BadgePainter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.jetbrains.jewel.ui.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 org.jetbrains.jewel.ui.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()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.jetbrains.jewel.ui.painter

import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.LayoutDirection

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()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jetbrains.jewel.ui.painter

import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.painter.Painter
import org.w3c.dom.Element

/**
Expand Down Expand Up @@ -102,6 +103,12 @@ interface PainterSvgPatchHint : SvgPainterHint {
fun patch(element: Element)
}

@Immutable
interface PainterWrapperHint : PainterHint {

fun wrap(painter: Painter): Painter
}

/**
* A [PainterHint] that adds a prefix to a resource file name, without
* changing the rest of the path.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,18 @@ class ResourcePainterProvider(

val extension = basePath.substringAfterLast(".").lowercase()

return when (extension) {
var painter = when (extension) {
"svg" -> createSvgPainter(url, density, hints)
"xml" -> createVectorDrawablePainter(url, density)
else -> createBitmapPainter(url, density)
}

for (hint in hints) {
if (hint !is PainterWrapperHint) continue
painter = hint.wrap(painter)
}

return painter
}

private fun resolveResource(path: String): URL? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.jetbrains.jewel.ui.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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.jetbrains.jewel.ui.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.foundation.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, hole = true)

override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline =
createOutline(size, hole = false)

private fun createOutline(size: Size, 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 ui/src/main/kotlin/org/jetbrains/jewel/ui/painter/hints/Badge.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.jetbrains.jewel.ui.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.ui.painter.BadgePainter
import org.jetbrains.jewel.ui.painter.PainterHint
import org.jetbrains.jewel.ui.painter.PainterWrapperHint
import org.jetbrains.jewel.ui.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
}

0 comments on commit a123dfc

Please sign in to comment.