Skip to content

Commit

Permalink
Add DrawScope.drawCircle
Browse files Browse the repository at this point in the history
  • Loading branch information
EpicDima committed Nov 17, 2024
1 parent 15b566f commit 6e6208a
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[Unreleased]: https://github.com/JakeWharton/mosaic/compare/0.14.0...HEAD

New:
- Nothing yet!
- Added `DrawScope.drawCircle` for drawing circles, including filled and outlined ones.

Changed:
- Nothing yet!
Expand Down
7 changes: 7 additions & 0 deletions mosaic-runtime/api/mosaic-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public final class com/jakewharton/mosaic/layout/DrawModifierKt {
}

public abstract interface class com/jakewharton/mosaic/layout/DrawScope {
public abstract fun drawCircle-04TKKdk (CIIIIJLcom/jakewharton/mosaic/layout/DrawStyle;)V
public abstract fun drawCircle-04TKKdk (IIIIIJLcom/jakewharton/mosaic/layout/DrawStyle;)V
public static synthetic fun drawCircle-04TKKdk$default (Lcom/jakewharton/mosaic/layout/DrawScope;CIIIIJLcom/jakewharton/mosaic/layout/DrawStyle;ILjava/lang/Object;)V
public static synthetic fun drawCircle-04TKKdk$default (Lcom/jakewharton/mosaic/layout/DrawScope;IIIIIJLcom/jakewharton/mosaic/layout/DrawStyle;ILjava/lang/Object;)V
public abstract fun drawRect-K-Y9Gx4 (CIIIJJLcom/jakewharton/mosaic/layout/DrawStyle;)V
public abstract fun drawRect-K-Y9Gx4 (IIIIJJLcom/jakewharton/mosaic/layout/DrawStyle;)V
public static synthetic fun drawRect-K-Y9Gx4$default (Lcom/jakewharton/mosaic/layout/DrawScope;CIIIJJLcom/jakewharton/mosaic/layout/DrawStyle;ILjava/lang/Object;)V
Expand Down Expand Up @@ -667,6 +671,8 @@ public final class com/jakewharton/mosaic/ui/unit/IntSize {
public static fun equals-impl (JLjava/lang/Object;)Z
public static final fun equals-impl0 (JJ)Z
public static final fun getHeight-impl (J)I
public static final fun getMaxDimension-impl (J)I
public static final fun getMinDimension-impl (J)I
public static final fun getWidth-impl (J)I
public fun hashCode ()I
public static fun hashCode-impl (J)I
Expand All @@ -682,5 +688,6 @@ public final class com/jakewharton/mosaic/ui/unit/IntSize$Companion {

public final class com/jakewharton/mosaic/ui/unit/IntSizeKt {
public static final fun IntSize (II)J
public static final fun getCenter-MSzmzZ8 (J)J
}

8 changes: 8 additions & 0 deletions mosaic-runtime/api/mosaic-runtime.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ abstract interface com.jakewharton.mosaic.layout/DrawScope { // com.jakewharton.
abstract val width // com.jakewharton.mosaic.layout/DrawScope.width|{}width[0]
abstract fun <get-width>(): kotlin/Int // com.jakewharton.mosaic.layout/DrawScope.width.<get-width>|<get-width>(){}[0]

abstract fun drawCircle(kotlin/Char, com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/TextStyle = ..., kotlin/Int = ..., com.jakewharton.mosaic.ui.unit/IntOffset = ..., com.jakewharton.mosaic.layout/DrawStyle = ...) // com.jakewharton.mosaic.layout/DrawScope.drawCircle|drawCircle(kotlin.Char;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.TextStyle;kotlin.Int;com.jakewharton.mosaic.ui.unit.IntOffset;com.jakewharton.mosaic.layout.DrawStyle){}[0]
abstract fun drawCircle(kotlin/Int = ..., com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/TextStyle = ..., kotlin/Int = ..., com.jakewharton.mosaic.ui.unit/IntOffset = ..., com.jakewharton.mosaic.layout/DrawStyle = ...) // com.jakewharton.mosaic.layout/DrawScope.drawCircle|drawCircle(kotlin.Int;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.TextStyle;kotlin.Int;com.jakewharton.mosaic.ui.unit.IntOffset;com.jakewharton.mosaic.layout.DrawStyle){}[0]
abstract fun drawRect(kotlin/Char, com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/TextStyle = ..., com.jakewharton.mosaic.ui.unit/IntOffset = ..., com.jakewharton.mosaic.ui.unit/IntSize = ..., com.jakewharton.mosaic.layout/DrawStyle = ...) // com.jakewharton.mosaic.layout/DrawScope.drawRect|drawRect(kotlin.Char;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.TextStyle;com.jakewharton.mosaic.ui.unit.IntOffset;com.jakewharton.mosaic.ui.unit.IntSize;com.jakewharton.mosaic.layout.DrawStyle){}[0]
abstract fun drawRect(kotlin/Int = ..., com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/TextStyle = ..., com.jakewharton.mosaic.ui.unit/IntOffset = ..., com.jakewharton.mosaic.ui.unit/IntSize = ..., com.jakewharton.mosaic.layout/DrawStyle = ...) // com.jakewharton.mosaic.layout/DrawScope.drawRect|drawRect(kotlin.Int;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.TextStyle;com.jakewharton.mosaic.ui.unit.IntOffset;com.jakewharton.mosaic.ui.unit.IntSize;com.jakewharton.mosaic.layout.DrawStyle){}[0]
abstract fun drawText(kotlin/Int, kotlin/Int, com.jakewharton.mosaic.text/AnnotatedString, com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/Color = ..., com.jakewharton.mosaic.ui/TextStyle = ...) // com.jakewharton.mosaic.layout/DrawScope.drawText|drawText(kotlin.Int;kotlin.Int;com.jakewharton.mosaic.text.AnnotatedString;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.TextStyle){}[0]
Expand Down Expand Up @@ -426,6 +428,10 @@ final value class com.jakewharton.mosaic.ui.unit/IntOffset { // com.jakewharton.
final value class com.jakewharton.mosaic.ui.unit/IntSize { // com.jakewharton.mosaic.ui.unit/IntSize|null[0]
final val height // com.jakewharton.mosaic.ui.unit/IntSize.height|{}height[0]
final fun <get-height>(): kotlin/Int // com.jakewharton.mosaic.ui.unit/IntSize.height.<get-height>|<get-height>(){}[0]
final val maxDimension // com.jakewharton.mosaic.ui.unit/IntSize.maxDimension|{}maxDimension[0]
final fun <get-maxDimension>(): kotlin/Int // com.jakewharton.mosaic.ui.unit/IntSize.maxDimension.<get-maxDimension>|<get-maxDimension>(){}[0]
final val minDimension // com.jakewharton.mosaic.ui.unit/IntSize.minDimension|{}minDimension[0]
final fun <get-minDimension>(): kotlin/Int // com.jakewharton.mosaic.ui.unit/IntSize.minDimension.<get-minDimension>|<get-minDimension>(){}[0]
final val packedValue // com.jakewharton.mosaic.ui.unit/IntSize.packedValue|{}packedValue[0]
final fun <get-packedValue>(): kotlin/Long // com.jakewharton.mosaic.ui.unit/IntSize.packedValue.<get-packedValue>|<get-packedValue>(){}[0]
final val width // com.jakewharton.mosaic.ui.unit/IntSize.width|{}width[0]
Expand Down Expand Up @@ -592,6 +598,8 @@ final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_AnnotatedStrin
final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_SpanStyle$stableprop // com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_SpanStyle$stableprop|#static{}com_jakewharton_mosaic_text_SpanStyle$stableprop[0]
final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_StringTextLayout$stableprop // com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_StringTextLayout$stableprop|#static{}com_jakewharton_mosaic_text_StringTextLayout$stableprop[0]
final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_TextLayout$stableprop // com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_TextLayout$stableprop|#static{}com_jakewharton_mosaic_text_TextLayout$stableprop[0]
final val com.jakewharton.mosaic.ui.unit/center // com.jakewharton.mosaic.ui.unit/center|@com.jakewharton.mosaic.ui.unit.IntSize{}center[0]
final fun (com.jakewharton.mosaic.ui.unit/IntSize).<get-center>(): com.jakewharton.mosaic.ui.unit/IntOffset // com.jakewharton.mosaic.ui.unit/center.<get-center>|<get-center>@com.jakewharton.mosaic.ui.unit.IntSize(){}[0]
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement$stableprop|#static{}com_jakewharton_mosaic_ui_Arrangement$stableprop[0]
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_Absolute$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_Absolute$stableprop|#static{}com_jakewharton_mosaic_ui_Arrangement_Absolute$stableprop[0]
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_SpacedAligned$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_SpacedAligned$stableprop|#static{}com_jakewharton_mosaic_ui_Arrangement_SpacedAligned$stableprop[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,61 @@ import com.jakewharton.mosaic.ui.isUnspecifiedColor
import com.jakewharton.mosaic.ui.isUnspecifiedTextStyle
import com.jakewharton.mosaic.ui.unit.IntOffset
import com.jakewharton.mosaic.ui.unit.IntSize
import com.jakewharton.mosaic.ui.unit.center
import de.cketti.codepoints.codePointAt
import kotlin.math.max
import kotlin.math.min

public interface DrawScope {
public val width: Int
public val height: Int

private val size: IntSize get() = IntSize(width, height)

/**
* Draws a circle at the provided center coordinate and radius. If no center point is provided
* the center of the bounds is used.
*
* @param char Char to be applied to the circle
* @param foreground Foreground color to be applied to the circle
* @param background Background color to be applied to the circle
* @param textStyle Text style to be applied to the circle
* @param radius Radius of the circle
* @param center Center coordinate where the circle is to be drawn
* @param drawStyle Whether or not the circle is stroked or filled in
*/
public fun drawCircle(
char: Char,
foreground: Color = Color.Unspecified,
background: Color = Color.Unspecified,
textStyle: TextStyle = TextStyle.Unspecified,
radius: Int = size.minDimension / 2,
center: IntOffset = size.center,
drawStyle: DrawStyle = DrawStyle.Fill,
)

/**
* Draws a circle at the provided center coordinate and radius. If no center point is provided
* the center of the bounds is used.
*
* @param codePoint Code point to be applied to the circle
* @param foreground Foreground color to be applied to the circle
* @param background Background color to be applied to the circle
* @param textStyle Text style to be applied to the circle
* @param radius Radius of the circle
* @param center Center coordinate where the circle is to be drawn
* @param drawStyle Whether or not the circle is stroked or filled in
*/
public fun drawCircle(
codePoint: Int = UnspecifiedCodePoint,
foreground: Color = Color.Unspecified,
background: Color = Color.Unspecified,
textStyle: TextStyle = TextStyle.Unspecified,
radius: Int = size.minDimension / 2,
center: IntOffset = size.center,
drawStyle: DrawStyle = DrawStyle.Fill,
)

/**
* Draws a rectangle with the given offset and size. If no offset from the top left is provided,
* it is drawn starting from the origin of the current translation. If no size is provided,
Expand Down Expand Up @@ -119,6 +165,240 @@ internal open class TextCanvasDrawScope(
override val width: Int,
override val height: Int,
) : DrawScope {
override fun drawCircle(
char: Char,
foreground: Color,
background: Color,
textStyle: TextStyle,
radius: Int,
center: IntOffset,
drawStyle: DrawStyle,
) {
drawCircle(char.code, foreground, background, textStyle, radius, center, drawStyle)
}

override fun drawCircle(
codePoint: Int,
foreground: Color,
background: Color,
textStyle: TextStyle,
radius: Int,
center: IntOffset,
drawStyle: DrawStyle,
) {
if (codePoint.isUnspecifiedCodePoint &&
foreground.isUnspecifiedColor &&
background.isUnspecifiedColor &&
textStyle.isUnspecifiedTextStyle ||
radius <= 0 ||
center.x + radius < 0 &&
center.y + radius < 0 ||
center.x - radius >= width &&
center.y - radius >= height
) {
// exit: circle with the specified parameters cannot be seen
return
}

when (drawStyle) {
is DrawStyle.Fill -> drawSolidCircle(
codePoint,
foreground,
background,
textStyle,
radius,
center,
)

is DrawStyle.Stroke -> {
val clippedStrokeWidth = max(1, drawStyle.width)
val adaptedForStrokeRadius = clippedStrokeWidth / 2 + radius
if (clippedStrokeWidth / 2 >= radius) {
drawSolidCircle(
codePoint,
foreground,
background,
textStyle,
adaptedForStrokeRadius,
center,
)
return
}

val strokeWidth = clippedStrokeWidth - 1
var x = 0
var y = adaptedForStrokeRadius
var d = 3 - 2 * adaptedForStrokeRadius

drawStrokedCirclePart(
x,
y,
codePoint,
foreground,
background,
textStyle,
center,
strokeWidth,
)
while (y >= x) {
x++
if (d > 0) {
y--
d += 4 * (x - y) + 10
} else {
d += 4 * x + 6
}
drawStrokedCirclePart(
x,
y,
codePoint,
foreground,
background,
textStyle,
center,
strokeWidth,
)
}
}
}
}

private inline fun drawStrokedCirclePart(
x: Int,
y: Int,
codePoint: Int,
foreground: Color,
background: Color,
textStyle: TextStyle,
center: IntOffset,
strokeWidth: Int,
) {
val x1 = center.x + x
val x2 = center.x - x
val x1Correct = x1 in 0..<width
val x2Correct = x2 in 0..<width
if (x1Correct || x2Correct) {
val start1 = min(height - 1, center.y + y)
val end1 = max(0, center.y + y - strokeWidth)
for (yy in start1 downTo end1) {
if (x1Correct) {
drawTextPixel(x1, yy, codePoint, foreground, background, textStyle)
}
if (x2Correct) {
drawTextPixel(x2, yy, codePoint, foreground, background, textStyle)
}
}

val start2 = min(height - 1, center.y - y + strokeWidth)
val end2 = max(0, center.y - y)
for (yy in start2 downTo end2) {
if (x1Correct) {
drawTextPixel(x1, yy, codePoint, foreground, background, textStyle)
}
if (x2Correct) {
drawTextPixel(x2, yy, codePoint, foreground, background, textStyle)
}
}
}

val y1 = center.y + x
val y2 = center.y - x
val y1Correct = y1 in 0..<height
val y2Correct = y2 in 0..<height
if (y1Correct || y2Correct) {
val start3 = min(width - 1, center.x + y)
val end3 = max(0, center.x + y - strokeWidth)
for (xx in start3 downTo end3) {
if (y1Correct) {
drawTextPixel(xx, y1, codePoint, foreground, background, textStyle)
}
if (y2Correct) {
drawTextPixel(xx, y2, codePoint, foreground, background, textStyle)
}
}

val start4 = min(width - 1, center.x - y + strokeWidth)
val end4 = max(0, center.x - y)
for (xx in start4 downTo end4) {
if (y1Correct) {
drawTextPixel(xx, y1, codePoint, foreground, background, textStyle)
}
if (y2Correct) {
drawTextPixel(xx, y2, codePoint, foreground, background, textStyle)
}
}
}
}

private fun drawSolidCircle(
codePoint: Int,
foreground: Color,
background: Color,
textStyle: TextStyle,
radius: Int,
center: IntOffset,
) {
var x = 0
var y = radius
var d = 3 - 2 * radius

drawSolidCirclePart(x, y, codePoint, foreground, background, textStyle, center)
while (y >= x) {
x++
if (d > 0) {
y--
d += 4 * (x - y) + 10
} else {
d += 4 * x + 6
}
drawSolidCirclePart(x, y, codePoint, foreground, background, textStyle, center)
}
}

private inline fun drawSolidCirclePart(
x: Int,
y: Int,
codePoint: Int,
foreground: Color,
background: Color,
textStyle: TextStyle,
center: IntOffset,
) {
val y11 = center.y + y
val y12 = center.y - y
val y11Correct = y11 in 0..<height
val y12Correct = y12 in 0..<height
if (y11Correct || y12Correct) {
val start1 = max(0, center.x - x)
val end1 = min(width - 1, center.x + x)
for (xx in start1..end1) {
if (y11Correct) {
drawTextPixel(xx, y11, codePoint, foreground, background, textStyle)
}
if (y12Correct) {
drawTextPixel(xx, y12, codePoint, foreground, background, textStyle)
}
}
}

val y21 = center.y + x
val y22 = center.y - x
val y21Correct = y21 in 0..<height
val y22Correct = y22 in 0..<height
if (y21Correct || y22Correct) {
val start2 = max(0, center.x - y)
val end2 = min(width - 1, center.x + y)
for (xx in start2..end2) {
if (y21Correct) {
drawTextPixel(xx, y21, codePoint, foreground, background, textStyle)
}
if (y22Correct) {
drawTextPixel(xx, y22, codePoint, foreground, background, textStyle)
}
}
}
}

override fun drawRect(
char: Char,
foreground: Color,
Expand Down Expand Up @@ -156,7 +436,7 @@ internal open class TextCanvasDrawScope(
}

when (drawStyle) {
DrawStyle.Fill -> drawSolidRect(codePoint, foreground, background, textStyle, topLeft, size)
is DrawStyle.Fill -> drawSolidRect(codePoint, foreground, background, textStyle, topLeft, size)
is DrawStyle.Stroke -> {
val strokeWidth = max(1, drawStyle.width)
if (strokeWidth * 2 >= size.width || strokeWidth * 2 >= size.height) {
Expand Down
Loading

0 comments on commit 6e6208a

Please sign in to comment.