Skip to content

Commit

Permalink
Fix FullscreenAdapter to be independent of the order of its listener …
Browse files Browse the repository at this point in the history
…calls (#689)
  • Loading branch information
m-sasha authored Apr 19, 2023
1 parent 7cdd091 commit 8b73b67
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 16 deletions.
44 changes: 31 additions & 13 deletions skiko/src/awtMain/kotlin/org/jetbrains/skiko/PlatformOperations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,52 @@ import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.SwingUtilities

internal open class FullscreenAdapter(
/**
* A proxy between [SkiaLayer] and [HardwareLayer] that keeps the assigned "fullscreen" state while the layer doesn't
* exist, and applies it once it does.
*/
internal class FullscreenAdapter(
val backedLayer: HardwareLayer
): ComponentAdapter() {
private var _isFullscreenDispatched = false
private var _isFullscreen: Boolean = false

// Keep a `localFullscreen` flag which stores the virtual fullscreen state when the window is not actually
// visible, and apply it when the window does become visible.
private var localFullscreen = false

// Additionally, keep an `isWindowShown` flag that says whether `backedLayer` or `localFullscreen` is currently
// the source of truth. This flag must be used, rather than the real state (e.g. with `window.isVisible)
// because otherwise the code becomes dependent on the order of listener calls. For example, `componentResized`
// can be called when the window is already visible but before `componentShown` has been called. If the test
// in `componentResized` was on `window.isVisible`, it would reset the value of `localFullscreen` before it had
// applied in `componentShown`.
private var isWindowShown = false

var fullscreen: Boolean
get() = _isFullscreen
// If window is shown, return backedLayer.fullscreen; localFullscreen may not have updated yet
get() = if (isWindowShown) backedLayer.fullscreen else localFullscreen
set(value) {
_isFullscreen = value
val window = SwingUtilities.getRoot(backedLayer)
if ( window == null || !window.isVisible) {
_isFullscreenDispatched = value
} else {
localFullscreen = value
if (isWindowShown) {
backedLayer.fullscreen = value
}
}

override fun componentShown(e: ComponentEvent) {
backedLayer.fullscreen = _isFullscreenDispatched
isWindowShown = true
backedLayer.fullscreen = localFullscreen
}

override fun componentHidden(e: ComponentEvent) {
_isFullscreenDispatched = _isFullscreen
override fun componentHidden(e: ComponentEvent?) {
isWindowShown = false
localFullscreen = backedLayer.fullscreen
}

override fun componentResized(e: ComponentEvent) {
_isFullscreen = backedLayer.fullscreen
if (isWindowShown) {
localFullscreen = backedLayer.fullscreen
}
}

}

internal interface PlatformOperations {
Expand Down
5 changes: 2 additions & 3 deletions skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import org.jetbrains.skia.*
import org.jetbrains.skiko.redrawer.Redrawer
import java.awt.Color
import java.awt.Component
import java.awt.Window
import java.awt.event.*
import java.awt.geom.AffineTransform
import java.awt.im.InputMethodRequests
Expand Down Expand Up @@ -146,7 +145,7 @@ actual open class SkiaLayer internal constructor(

override fun removeNotify() {
Logger.debug { "SkiaLayer.awt#removeNotify $this" }
val window = SwingUtilities.getRoot(this) as Window
val window = SwingUtilities.getWindowAncestor(this)
window.removeComponentListener(fullscreenAdapter)
dispose()
super.removeNotify()
Expand All @@ -155,7 +154,7 @@ actual open class SkiaLayer internal constructor(
override fun addNotify() {
Logger.debug { "SkiaLayer.awt#addNotify $this" }
super.addNotify()
val window = SwingUtilities.getRoot(this) as Window
val window = SwingUtilities.getWindowAncestor(this)
window.addComponentListener(fullscreenAdapter)
checkShowing()
init(isInited)
Expand Down
30 changes: 30 additions & 0 deletions skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import org.junit.Rule
import org.junit.Test
import java.awt.Color
import java.awt.Dimension
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.WindowEvent
import javax.swing.JFrame
import javax.swing.JLayeredPane
Expand Down Expand Up @@ -244,6 +246,34 @@ class SkiaLayerTest {
}
}

@Test
fun `window fullscreen state in componentResized`() = uiTest {
val window = UiTestWindow()
try {
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.fullscreen = true
var stateRemainsFullscreen = true
window.addComponentListener(object: ComponentAdapter(){
override fun componentResized(e: ComponentEvent?) {
if (!window.layer.fullscreen)
stateRemainsFullscreen = false
}
})
window.isVisible = true

delay(1000)
assertEquals(true, stateRemainsFullscreen)
} finally {
window.close()

// Delay before starting next test to let the window animation to complete, and allow the next window
// to become fullscreen
if (hostOs == OS.MacOS) {
delay(1000)
}
}
}

@Test
fun `should call onRender after init, after resize, and only once after needRedraw`() = uiTest {
var renderCount = 0
Expand Down

0 comments on commit 8b73b67

Please sign in to comment.