Unverified Commit 10629dff authored by Alexander Maryanovsky's avatar Alexander Maryanovsky Committed by GitHub

Apply `clipComponents` to the layer background (#1159)

parent a11256b2
...@@ -29,7 +29,7 @@ actual open class SkiaLayer { ...@@ -29,7 +29,7 @@ actual open class SkiaLayer {
/** /**
* The background color of the layer. * The background color of the layer.
*/ */
actual var backgroundColor: Int = Color.WHITE actual internal var backgroundColor: Int = Color.WHITE
set(value) { set(value) {
field = value field = value
needRender() needRender()
...@@ -81,5 +81,8 @@ actual open class SkiaLayer { ...@@ -81,5 +81,8 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.container get() = this.container
actual internal val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
internal actual fun draw(canvas: Canvas): Unit = TODO() internal actual fun draw(canvas: Canvas): Unit = TODO()
} }
\ No newline at end of file
...@@ -200,7 +200,7 @@ actual open class SkiaLayer internal constructor( ...@@ -200,7 +200,7 @@ actual open class SkiaLayer internal constructor(
configureBackground(value, _background) configureBackground(value, _background)
} }
actual var backgroundColor: Int internal actual var backgroundColor: Int
get() = background.rgb // Will return an ancestor's non-null background after setBackground(null). get() = background.rgb // Will return an ancestor's non-null background after setBackground(null).
set(value) { set(value) {
configureBackground(_transparency, Color(value, true)) configureBackground(_transparency, Color(value, true))
...@@ -335,6 +335,9 @@ actual open class SkiaLayer internal constructor( ...@@ -335,6 +335,9 @@ actual open class SkiaLayer internal constructor(
val clipComponents = mutableListOf<ClipRectangle>() val clipComponents = mutableListOf<ClipRectangle>()
internal actual val cutoutRectangles: List<ClipRectangle>
get() = clipComponents
@Volatile @Volatile
private var isDisposed = false private var isDisposed = false
...@@ -608,12 +611,6 @@ actual open class SkiaLayer internal constructor( ...@@ -608,12 +611,6 @@ actual open class SkiaLayer internal constructor(
val pictureRecorder = pictureRecorder!! val pictureRecorder = pictureRecorder!!
val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth, pictureHeight) val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth, pictureHeight)
// clipping
for (index in clipComponents.indices) {
val item = clipComponents[index]
canvas.clipRectBy(item, contentScale)
}
try { try {
isRendering = true isRendering = true
renderDelegate?.onRender(canvas, intWidth, intHeight, nanoTime) renderDelegate?.onRender(canvas, intWidth, intHeight, nanoTime)
...@@ -730,17 +727,6 @@ internal fun defaultFPSCounter( ...@@ -730,17 +727,6 @@ internal fun defaultFPSCounter(
logOnTick = true logOnTick = true
) )
} }
@Suppress("NOTHING_TO_INLINE")
internal inline fun Canvas.clipRectBy(rectangle: ClipRectangle, scale: Float) {
clipRect(
left = rectangle.x * scale,
top = rectangle.y * scale,
right = (rectangle.x + rectangle.width) * scale,
bottom = (rectangle.y + rectangle.height) * scale,
mode = ClipMode.DIFFERENCE,
antiAlias = true
)
}
// TODO Recheck this method validity in 2 cases - full Window content, and a Panel content // TODO Recheck this method validity in 2 cases - full Window content, and a Panel content
// issue: https://youtrack.jetbrains.com/issue/CMP-5447/Window-white-line-on-the-bottom-before-resizing // issue: https://youtrack.jetbrains.com/issue/CMP-5447/Window-white-line-on-the-bottom-before-resizing
......
package org.jetbrains.skiko.swing package org.jetbrains.skiko.swing
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.cutoutFromClip
import org.jetbrains.skiko.redrawer.RedrawerManager import org.jetbrains.skiko.redrawer.RedrawerManager
import java.awt.Component import java.awt.Component
import java.awt.Graphics import java.awt.Graphics
...@@ -46,7 +47,7 @@ open class SkiaSwingLayer( ...@@ -46,7 +47,7 @@ open class SkiaSwingLayer(
// clipping // clipping
for (index in clipComponents.indices) { for (index in clipComponents.indices) {
val item = clipComponents[index] val item = clipComponents[index]
canvas.clipRectBy(item, scale) canvas.cutoutFromClip(item, scale)
} }
renderDelegate.onRender(canvas, width, height, nanoTime) renderDelegate.onRender(canvas, width, height, nanoTime)
} }
......
...@@ -27,11 +27,13 @@ import org.junit.Rule ...@@ -27,11 +27,13 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.awt.* import java.awt.*
import java.awt.Color import java.awt.Color
import java.awt.Graphics
import java.awt.Point import java.awt.Point
import java.awt.event.* import java.awt.event.*
import java.util.concurrent.Semaphore import java.util.concurrent.Semaphore
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.Box import javax.swing.Box
import javax.swing.JComponent
import javax.swing.JFrame import javax.swing.JFrame
import javax.swing.JLayeredPane import javax.swing.JLayeredPane
import javax.swing.JPanel import javax.swing.JPanel
...@@ -1348,6 +1350,66 @@ class SkiaLayerTest { ...@@ -1348,6 +1350,66 @@ class SkiaLayerTest {
} }
} }
@Test
fun `layer background is clipped`() = uiTest {
val window = JFrame()
try {
// Simulate how Swing Interop in Compose for Desktop works
val layeredPane = JLayeredPane()
layeredPane.layout = null
val swingComponent = object : JComponent() {
override fun paint(g: Graphics) {
g.color = Color.GREEN
g.fillRect(0, 0, width, height)
}
}
val layer = SkiaLayer(
properties = SkiaLayerProperties(renderApi = renderApi),
)
layer.bounds = Rectangle(0, 0, 300, 300)
layer.background = Color.YELLOW
val layerContentPaint = Paint().also { it.color = Color.RED.rgb }
layer.renderDelegate = SkikoRenderDelegate { canvas, width, height, _ ->
canvas.drawRect(Rect(0f, 0f, width.toFloat(), height/3f), layerContentPaint)
}
swingComponent.bounds = Rectangle(0, 200, 300, 100)
layer.clipComponents.add(
ClipRectangle(
x = swingComponent.x.toFloat(),
y = swingComponent.y.toFloat(),
width = swingComponent.width.toFloat(),
height = swingComponent.height.toFloat()
)
)
layeredPane.add(layer, BorderLayout.CENTER)
layeredPane.add(swingComponent, BorderLayout.CENTER, 0)
window.contentPane.layout = BorderLayout()
window.contentPane.add(layeredPane, BorderLayout.CENTER)
window.setLocation(200, 200)
window.size = layer.size
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.isUndecorated = true
window.isVisible = true
withContext(Dispatchers.Default) {
Robot().waitForIdle()
}
// Expect to see three layers:
// - Red, from the layer content
// - Yellow, from the layer background
// - Green, from the Swing component
screenshots.assert(window.bounds, "frame")
} finally {
window.close()
}
}
private class RectRenderer( private class RectRenderer(
private val getContentScale: () -> Float, private val getContentScale: () -> Float,
var rectWidth: Int, var rectWidth: Int,
......
...@@ -9,3 +9,13 @@ interface ClipRectangle { ...@@ -9,3 +9,13 @@ interface ClipRectangle {
val width: Float val width: Float
val height: Float val height: Float
} }
/**
* Returns a [ClipRectangle] with the specified values.
*/
internal fun ClipRectangle(x: Float, y: Float, width: Float, height: Float) = object : ClipRectangle {
override val x: Float = x
override val y: Float = y
override val width: Float = width
override val height: Float = height
}
\ No newline at end of file
...@@ -36,13 +36,18 @@ expect open class SkiaLayer { ...@@ -36,13 +36,18 @@ expect open class SkiaLayer {
/** /**
* The color, in ARGB format, with which the layer is cleared before rendering. * The color, in ARGB format, with which the layer is cleared before rendering.
*/ */
var backgroundColor: Int internal var backgroundColor: Int
/** /**
* Underlying platform component. * Underlying platform component.
*/ */
val component: Any? val component: Any?
/**
* A list of rectangles to cut out from the rendered content; No content will be drawn inside them.
*/
internal val cutoutRectangles: List<ClipRectangle>
/** /**
* Current view used for rendering. * Current view used for rendering.
*/ */
......
...@@ -41,6 +41,13 @@ internal abstract class ContextHandler( ...@@ -41,6 +41,13 @@ internal abstract class ContextHandler(
} }
initCanvas() initCanvas()
canvas?.apply { canvas?.apply {
clear(Color.TRANSPARENT)
val scale = layer.contentScale
for (clip in layer.cutoutRectangles) {
cutoutFromClip(clip, scale)
}
val layerBg = layer.backgroundColor val layerBg = layer.backgroundColor
clear( clear(
if (layer.transparency && isTransparentBackgroundSupported()) { if (layer.transparency && isTransparentBackgroundSupported()) {
...@@ -49,6 +56,7 @@ internal abstract class ContextHandler( ...@@ -49,6 +56,7 @@ internal abstract class ContextHandler(
layerBg or 0xFF000000.toInt() layerBg or 0xFF000000.toInt()
} }
) )
drawContent() drawContent()
} }
flush() flush()
...@@ -56,11 +64,23 @@ internal abstract class ContextHandler( ...@@ -56,11 +64,23 @@ internal abstract class ContextHandler(
protected open fun isTransparentBackgroundSupported(): Boolean { protected open fun isTransparentBackgroundSupported(): Boolean {
if (hostOs == OS.MacOS) { if (hostOs == OS.MacOS) {
// MacOS transparency is always supported // macOS transparency is always supported
return true return true
} }
// for non-MacOS in fullscreen transparency is not supported // for non-macOS in fullscreen transparency is not supported
return !layer.fullscreen return !layer.fullscreen
} }
} }
@Suppress("NOTHING_TO_INLINE")
internal inline fun Canvas.cutoutFromClip(rectangle: ClipRectangle, scale: Float) {
clipRect(
left = rectangle.x * scale,
top = rectangle.y * scale,
right = (rectangle.x + rectangle.width) * scale,
bottom = (rectangle.y + rectangle.height) * scale,
mode = ClipMode.DIFFERENCE,
antiAlias = true
)
}
\ No newline at end of file
...@@ -15,11 +15,13 @@ actual open class SkiaLayer { ...@@ -15,11 +15,13 @@ actual open class SkiaLayer {
actual var transparency: Boolean actual var transparency: Boolean
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
set(value) {} set(value) {}
actual var backgroundColor: Int internal actual var backgroundColor: Int
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
set(value) {} set(value) {}
actual val component: Any? actual val component: Any?
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
actual fun needRender(throttledToVsync: Boolean) { actual fun needRender(throttledToVsync: Boolean) {
TODO("unimplemented") TODO("unimplemented")
} }
......
...@@ -61,7 +61,7 @@ actual open class SkiaLayer { ...@@ -61,7 +61,7 @@ actual open class SkiaLayer {
/** /**
* The background color of the layer. * The background color of the layer.
*/ */
actual var backgroundColor: Int = Color.WHITE internal actual var backgroundColor: Int = Color.WHITE
set(value) { set(value) {
field = value field = value
needRender() needRender()
...@@ -76,6 +76,9 @@ actual open class SkiaLayer { ...@@ -76,6 +76,9 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.nsView get() = this.nsView
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
/** /**
* Implements rendering logic and events processing. * Implements rendering logic and events processing.
*/ */
......
...@@ -27,7 +27,7 @@ actual open class SkiaLayer { ...@@ -27,7 +27,7 @@ actual open class SkiaLayer {
/** /**
* The background color of the layer, as transparency is not supported. * The background color of the layer, as transparency is not supported.
*/ */
actual var backgroundColor: Int = Color.WHITE internal actual var backgroundColor: Int = Color.WHITE
set(value) { set(value) {
field = value field = value
needRender() needRender()
...@@ -46,6 +46,9 @@ actual open class SkiaLayer { ...@@ -46,6 +46,9 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.view get() = this.view
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
val width: Float val width: Float
get() = view!!.frame.useContents { get() = view!!.frame.useContents {
return@useContents size.width.toFloat() return@useContents size.width.toFloat()
......
...@@ -49,7 +49,7 @@ actual open class SkiaLayer { ...@@ -49,7 +49,7 @@ actual open class SkiaLayer {
/** /**
* The background color of the layer, as transparency is not supported. * The background color of the layer, as transparency is not supported.
*/ */
actual var backgroundColor: Int = Color.WHITE internal actual var backgroundColor: Int = Color.WHITE
set(value) { set(value) {
field = value field = value
needRender() needRender()
...@@ -88,6 +88,9 @@ actual open class SkiaLayer { ...@@ -88,6 +88,9 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.htmlCanvas get() = this.htmlCanvas
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
private var htmlCanvas: HTMLCanvasElement? = null private var htmlCanvas: HTMLCanvasElement? = null
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment