From d7df60c7d65ec5545d64e795085923bf5516f6f6 Mon Sep 17 00:00:00 2001 From: Nikolay Rykunov Date: Thu, 29 Jun 2023 12:48:32 +0200 Subject: [PATCH 1/2] Add test for z ordering with SkiaSwingLayer --- .../jetbrains/skiko/swing/SkiaSwingLayer.kt | 10 +- .../org/jetbrains/skiko/SkiaLayerTest.kt | 2 +- .../org/jetbrains/skiko/SkiaSwingLayerTest.kt | 87 ++++++++++++++++++ ...ko_SkiaSwingLayerTest_overlapped_popup.png | Bin 0 -> 1957 bytes 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt create mode 100644 skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_overlapped_popup.png diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/swing/SkiaSwingLayer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/swing/SkiaSwingLayer.kt index 8124d1059..ad3e5c28c 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/swing/SkiaSwingLayer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/swing/SkiaSwingLayer.kt @@ -21,8 +21,9 @@ import javax.swing.SwingUtilities.isEventDispatchThread */ @Suppress("unused") // used in Compose Multiplatform @ExperimentalSkikoApi -open class SkiaSwingLayer( +open class SkiaSwingLayer internal constructor( skikoView: SkikoView, + private val properties: SkiaLayerProperties, analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty, ) : JComponent() { internal companion object { @@ -31,8 +32,6 @@ open class SkiaSwingLayer( } } - private val properties = SkiaLayerProperties() - private var isInitialized = false @Volatile @@ -73,6 +72,11 @@ open class SkiaSwingLayer( val renderApi: GraphicsApi get() = redrawerManager.renderApi + constructor( + skikoView: SkikoView, + analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty, + ) : this(skikoView, SkiaLayerProperties(), analytics) + init { isOpaque = false layout = null diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt index 8070c06a0..c74ffc35a 100644 --- a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt @@ -835,4 +835,4 @@ class SkiaLayerTest { } } -private fun JFrame.close() = dispatchEvent(WindowEvent(this, WindowEvent.WINDOW_CLOSING)) +internal fun JFrame.close() = dispatchEvent(WindowEvent(this, WindowEvent.WINDOW_CLOSING)) diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt new file mode 100644 index 000000000..8afea4d9b --- /dev/null +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt @@ -0,0 +1,87 @@ +package org.jetbrains.skiko + +import kotlinx.coroutines.delay +import org.jetbrains.skia.Canvas +import org.jetbrains.skia.Paint +import org.jetbrains.skia.Rect +import org.jetbrains.skiko.swing.SkiaSwingLayer +import org.jetbrains.skiko.util.ScreenshotTestRule +import org.jetbrains.skiko.util.uiTest +import org.junit.Rule +import java.awt.Color +import java.awt.Dimension +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.RenderingHints +import javax.swing.JComponent +import javax.swing.JFrame +import javax.swing.JLayeredPane +import javax.swing.WindowConstants +import kotlin.test.Test + +@OptIn(ExperimentalSkikoApi::class) +class SkiaSwingLayerTest { + + @get:Rule + val screenshots = ScreenshotTestRule() + + @Test + fun `overlapped popup`() = uiTest { + val skikoView = fillSkikoView() + val skiaLayer = SkiaSwingLayer(skikoView, SkiaLayerProperties().copy(renderApi = renderApi)).apply { + setBounds(0, 0, 200, 200) + } + + val window = object : JFrame() { + override fun dispose() { + skiaLayer.dispose() + super.dispose() + } + } + try { + val popup = object : JComponent() { + init { + isOpaque = false + setBounds(50, 50, 50, 50) + } + + override fun paintComponent(g: Graphics?) { + val scratchGraphics = g?.create() as? Graphics2D ?: return + try { + scratchGraphics.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON + ); + scratchGraphics.color = Color.GREEN + scratchGraphics.fillRoundRect(5, 5, 40, 40, 16, 16) + } finally { + scratchGraphics.dispose() + } + } + } + + val layeredPane = JLayeredPane() + layeredPane.add(popup, Integer.valueOf(1)) + layeredPane.add(skiaLayer, Integer.valueOf(2)) + + window.isUndecorated = true + window.size = Dimension(400, 400) + window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE + window.contentPane.add(layeredPane) + window.isVisible = true + + delay(1000) + screenshots.assert(window.bounds) + } finally { + window.close() + } + } + + private fun fillSkikoView(color: Color = Color.RED): SkikoView = object : SkikoView { + override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { + canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), Paint().apply { + this.color = color.rgb + }) + } + } +} \ No newline at end of file diff --git a/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_overlapped_popup.png b/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_overlapped_popup.png new file mode 100644 index 0000000000000000000000000000000000000000..64396b95f274f31b34c18a1caea8914e86186fa9 GIT binary patch literal 1957 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgF}%C(jTL zAgJL;>0n@B_xE&h45^s&_U6W{xvUb$ADSQaynFks?u2h#OUf1AidwWZl?cqKlD?}Y z>mta$e!)>|jlE537kt%st8^EhDYW2gvQ_(g^TXUZcKj*-{-yt{+Y|pS%y>29y;onJ zq`c(Zur*J|mZ9Obrvrlr3rmB`(4*{DG|0R^Z&PJc{5m7;eNI|_>CyW1*Xg!Zd+N*% z=-rGyyJnhP;ngtX&ks(X@sB@Wbkb(NWsTM6$L7Y>+`bR)&Yd?$$Ch#1wR7w#rpD#- z&+gf?{mZ1S=jWf_vqN9$dTYahH0%GIuMbXs+j;y<)#+w+@AV8n{+In?vb;5aXR*!g z=_fzW3}-jq9RpumkBY5Na*>~Bl;&wLBi&beDw zfMH!v1H%Lk4u(#uP$%=)pX9t`{GT{`J7dftKlUfj^1bzM=xe;~yTUPD*y#4bmYj%C< zs<*Duk~{Eq`(^PHXMP>OTWj|D)4faH*X-);K0o?*=H4ae9U|f!IorU}u780-54Rf^ z?|U%e^H;Wy3}r{p#Vh3(vzc}-oyd|#j!Sk*Fky85}Sb4q9e0PM-8M*si- literal 0 HcmV?d00001 From a9a040ee8b5e3d5f49cccd4327cd9c8ca04e3d76 Mon Sep 17 00:00:00 2001 From: Nikolay Rykunov Date: Fri, 30 Jun 2023 16:12:32 +0200 Subject: [PATCH 2/2] Add more tests for SkiaSwingLayer --- .../org/jetbrains/skiko/SkiaSwingLayerTest.kt | 184 +++++++++++++++--- ...aSwingLayerTest_layer_resize_position1.png | Bin 0 -> 1782 bytes ...aSwingLayerTest_layer_resize_position2.png | Bin 0 -> 1780 bytes ...iko_SkiaSwingLayerTest_multiple_layers.png | Bin 0 -> 1589 bytes ...ko_SkiaSwingLayerTest_overlapped_popup.png | Bin 1957 -> 1838 bytes ...iaSwingLayerTest_overlapped_skia_layer.png | Bin 0 -> 1615 bytes 6 files changed, 158 insertions(+), 26 deletions(-) create mode 100644 skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_layer_resize_position1.png create mode 100644 skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_layer_resize_position2.png create mode 100644 skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_multiple_layers.png create mode 100644 skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_overlapped_skia_layer.png diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt index 8afea4d9b..db4ed2ca7 100644 --- a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaSwingLayerTest.kt @@ -8,15 +8,10 @@ import org.jetbrains.skiko.swing.SkiaSwingLayer import org.jetbrains.skiko.util.ScreenshotTestRule import org.jetbrains.skiko.util.uiTest import org.junit.Rule -import java.awt.Color -import java.awt.Dimension -import java.awt.Graphics -import java.awt.Graphics2D -import java.awt.RenderingHints -import javax.swing.JComponent -import javax.swing.JFrame -import javax.swing.JLayeredPane -import javax.swing.WindowConstants +import java.awt.* +import javax.swing.* +import javax.swing.JLayeredPane.DEFAULT_LAYER +import javax.swing.JLayeredPane.POPUP_LAYER import kotlin.test.Test @OptIn(ExperimentalSkikoApi::class) @@ -32,13 +27,7 @@ class SkiaSwingLayerTest { setBounds(0, 0, 200, 200) } - val window = object : JFrame() { - override fun dispose() { - skiaLayer.dispose() - super.dispose() - } - } - try { + singleWindowTest(skiaLayer) { contentPane -> val popup = object : JComponent() { init { isOpaque = false @@ -48,10 +37,9 @@ class SkiaSwingLayerTest { override fun paintComponent(g: Graphics?) { val scratchGraphics = g?.create() as? Graphics2D ?: return try { - scratchGraphics.setRenderingHint( - RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON - ); + scratchGraphics.color = Color(0, 0, 0, 50) + scratchGraphics.fillRect(0, 0, 50, 50) + scratchGraphics.color = Color.GREEN scratchGraphics.fillRoundRect(5, 5, 40, 40, 16, 16) } finally { @@ -61,17 +49,157 @@ class SkiaSwingLayerTest { } val layeredPane = JLayeredPane() - layeredPane.add(popup, Integer.valueOf(1)) - layeredPane.add(skiaLayer, Integer.valueOf(2)) + layeredPane.add(skiaLayer) + layeredPane.add(popup) + + layeredPane.setLayer(skiaLayer, DEFAULT_LAYER) + layeredPane.setLayer(popup, POPUP_LAYER) + + contentPane.add(layeredPane) + + delay(1000) + checkScreenshot() + } + } + + @Test + fun `multiple layers`() = uiTest { + val skikoView1 = fillSkikoView(color = Color.CYAN) + val skiaLayer1 = SkiaSwingLayer(skikoView1, SkiaLayerProperties().copy(renderApi = renderApi)).apply { + setBounds(0, 0, 200, 200) + } + val skikoView2 = fillSkikoView(color = Color.GREEN) + val skiaLayer2 = SkiaSwingLayer(skikoView2, SkiaLayerProperties().copy(renderApi = renderApi)).apply { + setBounds(50, 50, 200, 200) + } + + val skikoView3 = fillSkikoView(color = Color.RED) + val skiaLayer3 = SkiaSwingLayer(skikoView3, SkiaLayerProperties().copy(renderApi = renderApi)).apply { + setBounds(100, 100, 25, 25) + } + + singleWindowTest( + dispose = { + skiaLayer1.dispose() + skiaLayer2.dispose() + skiaLayer3.dispose() + } + ) { contentPane -> + val layeredPane = JLayeredPane() + + layeredPane.add(skiaLayer1) + layeredPane.add(skiaLayer2) + layeredPane.add(skiaLayer3) + + layeredPane.setLayer(skiaLayer1, 0) + layeredPane.setLayer(skiaLayer2, 1) + layeredPane.setLayer(skiaLayer3, 2) + + contentPane.add(layeredPane) + + delay(1000) + checkScreenshot() + } + } + + @Test + fun `layer resize`() = uiTest { + val skikoView = fillSkikoView() + val skiaLayer = SkiaSwingLayer(skikoView, SkiaLayerProperties().copy(renderApi = renderApi)) + + singleWindowTest(skiaLayer) { contentPane -> + val splitPane = JSplitPane() + splitPane.leftComponent = skiaLayer + splitPane.rightComponent = JPanel() + + splitPane.dividerLocation = 50 + + contentPane.add(splitPane) + + delay(1000) + checkScreenshot("position1") + + splitPane.dividerLocation = 100 + delay(1000) + checkScreenshot("position2") + } + } + + @Test + fun `overlapped skia layer`() = uiTest { + val skikoView = object : SkikoView { + override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { + val rect = Rect(0f, 0f, width.toFloat() - 10f, height.toFloat() - 10f) + canvas.drawRect(rect, Paint().apply { + this.color = Color.CYAN.rgb + }) + + canvas.drawRectShadow( + rect, + dx = 3f, dy = 3f, + blur = 0.5f, + Color(0, 0, 0, 50).rgb + ) + } + } + val skiaLayer = SkiaSwingLayer(skikoView, SkiaLayerProperties().copy(renderApi = renderApi)).apply { + setBounds(50, 50, 50, 50) + } + + singleWindowTest(skiaLayer) { contentPane -> + val panel = JPanel().apply { + background = Color.GREEN + setBounds(0, 0, 200, 200) + } + + val layeredPane = JLayeredPane() + layeredPane.add(panel) + layeredPane.add(skiaLayer) + + layeredPane.setLayer(panel, DEFAULT_LAYER) + layeredPane.setLayer(skiaLayer, POPUP_LAYER) + contentPane.add(layeredPane) + + delay(1000) + checkScreenshot() + } + } + + private inline fun singleWindowTest( + layer: SkiaSwingLayer, + block: SingleWindowTestScope.(contentPane: Container) -> Unit + ) { + singleWindowTest( + dispose = { + layer.dispose() + }, + block + ) + } + + private inline fun singleWindowTest( + crossinline dispose: () -> Unit, + block: SingleWindowTestScope.(contentPane: Container) -> Unit + ) { + val window = object : JFrame() { + override fun dispose() { + dispose() + super.dispose() + } + } + + try { window.isUndecorated = true window.size = Dimension(400, 400) window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE - window.contentPane.add(layeredPane) window.isVisible = true - - delay(1000) - screenshots.assert(window.bounds) + val scope = object : SingleWindowTestScope { + override fun checkScreenshot(id: String) { + screenshots.assert(window.bounds, id) + } + } + scope.block(window.contentPane) } finally { window.close() } @@ -84,4 +212,8 @@ class SkiaSwingLayerTest { }) } } + + private interface SingleWindowTestScope { + fun checkScreenshot(id: String = "") + } } \ No newline at end of file diff --git a/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_layer_resize_position1.png b/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_layer_resize_position1.png new file mode 100644 index 0000000000000000000000000000000000000000..6277ab981ae8d1093ac9ea8067cdb37fe952cd12 GIT binary patch literal 1782 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgF}%C(jTL zAgJL;>0n@BTj1&97*a9k?JY+y;9F#_>(I6Ofso-=l{|}Ax=g+^NXK8Q$zeSbd z{^1;^qT1TO?`F2QxBEvheXu>Frf}J$*YH?HWu;_)fB)mJUrTkSpDwJdv*4gHPD*BfB)`I*VEIpOXNK8 z-C%Y@2lIjFv(DNJFxX9RV3@$c!O%HMjRwJ>O9gZ8-n-|wT#oU;_2O?|z5uI>KU?R| YnNw8u?oIn?VBN*w>FVdQ&MBb@0AA6m2mk;8 literal 0 HcmV?d00001 diff --git a/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_layer_resize_position2.png b/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_layer_resize_position2.png new file mode 100644 index 0000000000000000000000000000000000000000..0b9ca6b71173ae3aa74a26337604a4800fbd78ca GIT binary patch literal 1780 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgF}%C(jTL zAgJL;>0n@Bo9F4`7*a9k?JYwuok)hZi`l^@Y)wARjk^on^-h}Zuv%P~q@cU)uIa(q zg=tO8KkqxB_fTfVf(LB%-vY1L?%un1E<3}YWiOvSOS^OL-n=SyhWjV}{{1`SU)|5- z&6ZEa{(buK`1ne7hClkNgZUXAR0|0(2r)7$2#!*tK`_`-!K9a0n18IFK7G3X`8o6F z*B=yW=;u6fC?-C>zUtY>j~}_GG1T}th^5VYZg@;${rdHfU%t%Tvv+TA=B#5+pFWLP ze_cI}{R97h2eB6g7CmpvtiS#KJL7rfoA19j-pxDyu)yN0Nkci)CWFs5{de=s`;I@J z@%-msOS%5!?YH;VFJzcAt3l_&dL>1<1CuyT96Iy)Pu=NP_v00k|Lmz%RXPv^7PARk zef36;S@Nv9dxbNWW#*X8-Wau(g(ZS#`v$SBy>b1kSFa8}&+tQ)F>*$Gd%OSnn3$Lv zs4qI0Kg9lEXL3+dU{GNirAC8bFs6d^^78V}E8Q7B{GDTKZ_mxau>Y-pbYW4MFk8f7 QV9mwg>FVdQ&MBb@09lDLA^-pY literal 0 HcmV?d00001 diff --git a/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_multiple_layers.png b/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_multiple_layers.png new file mode 100644 index 0000000000000000000000000000000000000000..843ba156b25deaf420dcddff556c55686b6f1ac2 GIT binary patch literal 1589 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgF}%C(jTL zAgJL;>0n@Bz3=Jb7*a9k?e){S%!UH22V;|kH+=4&$kS-Y{o!DS#{#=h&NI7BpV?fC zV*jw*@cBE=4_o$?@iROy<>X-KY+#tcG1#d&$D^{pcl@>}=WkeU`s{{nqxlnNMg>6u z1|h~FK!{1+Qavi!=P`=VxhfabWOZ8Nt*#U>fHw z|M+=tHRFNwkBvhhfkSg3FdFZio{{{WrJ?`P5K7=fB_j?^cm8A*5fCqSKi6UgEczKd MUHx3vIVCg!00u(~8UO$Q literal 0 HcmV?d00001 diff --git a/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_overlapped_popup.png b/skiko/src/jvmTest/screenshots/org_jetbrains_skiko_SkiaSwingLayerTest_overlapped_popup.png index 64396b95f274f31b34c18a1caea8914e86186fa9..0214edb01f2eb3ac74e360ad432f8a5a52a575a6 100644 GIT binary patch delta 526 zcmV+p0`dK&53UZ7Kvf3VNkl*;dvj?2Wo7n>Zj^qjifJ8t5NCX6cL_h#YR`U@3SYPiB@Ao%jJlvl5w>H}` z-hMxP{&08R-vW4PSNk#cvqzl5|NRT=;dYEKpUxkR=X`s2h{|*P3u{>(ce~jWV6k3B zKpt+#c>niHEqUH%dkD!>1Y}tR1fI6~XG$PTipLm-J^$WD1Gr?X56G8Kvlsko_N_F4 zQ&@FCzTPeOpkC@OfMcyXAWL?CGkXERpILQ4mV8LN0s$`FssrMX1~{Kp2Lyogld%I$ ze_3@vHrv??0M={O0eSm<_5y(QT6I8{?BnOz3jqGissr-jZuWwwxxEHlx>W~cj4{5w zo4w#yo2`HVmv8j}S>j>6#Ha!MuGI%*jKhJwgl7($?HE6s1Bh5(vn$2namwRoub?;PeCpfJ8t5 zNCX6c0N?_-0<%E{P6CsF18Eld2a<$U#!A_r QAOHXW07*qoM6N<$g3ki@CIA2c delta 682 zcmZ3-x0HW^Lk7FQr;B4q#hkY{H)hRcl{o&;{HW*M+h=treB)YDuJBgWqNS-sU{00v zT`gG`LH6|vj#_K%ZBo17tG-*MyYNh*1z(e`+TWWW=FYL>Px<#R{b${t_-|pxs~PXT z`tl^@CFh2%c{;WX4X-^XUd);J!MEOSMT5-y^EOpB#ji8c-shy{mmaN8f1PeywWrSP zfZomMvumcw6^8er#@B&F%Z(?%a8EbZi;7T|38~VrpDI z|LmSU+rLcOdVc=-Jv;Q3uD3QENVERW`TF4Gx1Gn&RGn^C_g>HN!V-ESTKY5n#t$!m2#5)G$osORt zx8bH)KJ$*Ay;sj*S2+<~v#FN>cz^XvHCTC>le?p^Y}W>;_b`O&{K_bxf_5E19d*#?$& z{R-rh> zU+GZx^prwfgF}%C(jTL zAgJL;>0n@B{psoA7*a9k?ahsTPL3k37rPf8{{P=yN~7k=jew{bx@k2#!i@gDEX#Xl z@O>x$hoz^#ZmMGX6Pdr7oneDN6O)6I0)xt+r;cA`p7(rTl|w;z|Y3GcK zqitChR4(LBN#Ew~ke24>J)J>CPk=#)kx@Z#kWiBzzhd++y`8o#_iNYtowaM?7(OWL zi%d9t#?1KnRcnTS?c!V~&g@w)C-A1w_`seh2fu#y1M5F`@(qnx8T82pp97DBG6FM$ s^%JJSlUxQ<(j9fnpeji)qvI#b0(&W~>afE*ft3P-r>mdKI;Vst00KY1#Q*>R literal 0 HcmV?d00001