Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use z-sorted traverse in InteropContainer #1340

Merged
merged 6 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import androidx.compose.ui.scene.ComposeSceneMediator
import androidx.compose.ui.unit.IntRect
import java.awt.Component
import java.awt.Container
import java.awt.event.ContainerAdapter
import java.awt.event.ContainerEvent
import java.awt.event.ContainerListener
import javax.swing.SwingUtilities
import org.jetbrains.skiko.ClipRectangle

/**
Expand All @@ -54,7 +58,7 @@ internal class SwingInteropContainer(
private val placeInteropAbove: Boolean
): InteropContainer<InteropComponent> {
/**
* @see SwingInteropContainer.addInteropView
* @see SwingInteropContainer.placeInteropView
* @see SwingInteropContainer.removeInteropView
*/
private var interopComponents = mutableMapOf<Component, InteropComponent>()
Expand All @@ -63,12 +67,13 @@ internal class SwingInteropContainer(
override val interopViews: Set<InteropComponent>
get() = interopComponents.values.toSet()

override fun addInteropView(nativeView: InteropComponent) {
override fun placeInteropView(nativeView: InteropComponent) = SwingUtilities.invokeLater {
igordmn marked this conversation as resolved.
Show resolved Hide resolved
val component = nativeView.container
val nonInteropComponents = container.componentCount - interopComponents.size
// AWT uses the reverse order for drawing and events, so index = size - count
val index = interopComponents.size - countInteropComponentsBefore(nativeView)
interopComponents[component] = nativeView
container.remove(component)
igordmn marked this conversation as resolved.
Show resolved Hide resolved
container.add(component, if (placeInteropAbove) {
igordmn marked this conversation as resolved.
Show resolved Hide resolved
index
} else {
Expand All @@ -81,7 +86,7 @@ internal class SwingInteropContainer(
container.repaint()
}

override fun removeInteropView(nativeView: InteropComponent) {
override fun removeInteropView(nativeView: InteropComponent) = SwingUtilities.invokeLater {
val component = nativeView.container
container.remove(component)
interopComponents.remove(component)
Expand All @@ -92,6 +97,11 @@ internal class SwingInteropContainer(
container.repaint()
}

fun validateComponentsOrder() = SwingUtilities.invokeLater {
container.validate()
container.repaint()
}

fun getClipRectForComponent(component: Component): ClipRectangle =
requireNotNull(interopComponents[component])

Expand All @@ -113,8 +123,10 @@ internal class SwingInteropContainer(
* @param component The Swing component that matches the current node.
*/
internal fun Modifier.trackSwingInterop(
container: SwingInteropContainer,
component: InteropComponent
): Modifier = this then TrackInteropModifierElement(
container = container,
nativeView = component
)

Expand All @@ -126,7 +138,7 @@ internal fun Modifier.trackSwingInterop(
*/
internal open class InteropComponent(
val container: Container,
var clipBounds: IntRect? = null
protected var clipBounds: IntRect? = null
) : ClipRectangle {
override val x: Float
get() = (clipBounds?.left ?: container.x).toFloat()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,60 +89,43 @@ public fun <T : Component> SwingPanel(
) {
val interopContainer = LocalSwingInteropContainer.current
val compositeKey = currentCompositeKeyHash
val componentInfo = remember {
ComponentInfo<T>(
val interopComponent = remember {
SwingInteropComponent(
container = SwingPanelContainer(
key = compositeKey,
focusComponent = interopContainer.container,
)
),
update = update,
)
}

val density = LocalDensity.current
val focusManager = LocalFocusManager.current
val focusSwitcher = remember { FocusSwitcher(componentInfo, focusManager) }
val focusSwitcher = remember { FocusSwitcher(interopComponent, focusManager) }

OverlayLayout(
modifier = modifier.onGloballyPositioned { coordinates ->
val rootCoordinates = coordinates.findRootCoordinates()
val clipedBounds = rootCoordinates
val clippedBounds = rootCoordinates
.localBoundingBoxOf(coordinates, clipBounds = true).round(density)
val bounds = rootCoordinates
.localBoundingBoxOf(coordinates, clipBounds = false).round(density)

// Take care about clipped bounds
componentInfo.clipBounds = clipedBounds // Clipping area for skia canvas
componentInfo.container.isVisible = !clipedBounds.isEmpty // Hide if it's fully clipped
// Swing clips children based on parent's bounds, so use our container for clipping
componentInfo.container.setBounds(
/* x = */ clipedBounds.left,
/* y = */ clipedBounds.top,
/* width = */ clipedBounds.width,
/* height = */ clipedBounds.height
)

// The real size and position should be based on not-clipped bounds
componentInfo.component.setBounds(
/* x = */ bounds.left - clipedBounds.left, // Local position relative to container
/* y = */ bounds.top - clipedBounds.top,
/* width = */ bounds.width,
/* height = */ bounds.height
)
componentInfo.container.validate()
componentInfo.container.repaint()
interopComponent.setBounds(bounds, clippedBounds)
interopContainer.validateComponentsOrder()
}.drawBehind {
// Clear interop area to make visible the component under our canvas.
drawRect(Color.Transparent, blendMode = BlendMode.Clear)
}.trackSwingInterop(componentInfo)
.then(InteropPointerInputModifier(componentInfo))
}.trackSwingInterop(interopContainer, interopComponent)
.then(InteropPointerInputModifier(interopComponent))
) {
focusSwitcher.Content()
}

DisposableEffect(Unit) {
val focusListener = object : FocusListener {
override fun focusGained(e: FocusEvent) {
if (componentInfo.container.isParentOf(e.oppositeComponent)) {
if (interopComponent.container.isParentOf(e.oppositeComponent)) {
when (e.cause) {
FocusEvent.Cause.TRAVERSAL_FORWARD -> focusSwitcher.moveForward()
FocusEvent.Cause.TRAVERSAL_BACKWARD -> focusSwitcher.moveBackward()
Expand All @@ -154,26 +137,22 @@ public fun <T : Component> SwingPanel(
override fun focusLost(e: FocusEvent) = Unit
}
interopContainer.container.addFocusListener(focusListener)
interopContainer.addInteropView(componentInfo)
onDispose {
interopContainer.removeInteropView(componentInfo)
interopContainer.removeInteropView(interopComponent)
MatkovIvan marked this conversation as resolved.
Show resolved Hide resolved
interopContainer.container.removeFocusListener(focusListener)
}
}

DisposableEffect(factory) {
componentInfo.component = factory()
componentInfo.container.add(componentInfo.component)
componentInfo.updater = Updater(componentInfo.component, update)
interopComponent.setComponent(factory())
onDispose {
componentInfo.container.remove(componentInfo.component)
componentInfo.updater.dispose()
interopComponent.dispose()
}
}

SideEffect {
componentInfo.container.background = background.toAwtColor()
componentInfo.updater.update = update
interopComponent.container.background = background.toAwtColor()
interopComponent.update = update
}
}

Expand Down Expand Up @@ -212,7 +191,7 @@ private class SwingPanelContainer(
}

private class FocusSwitcher<T : Component>(
private val info: ComponentInfo<T>,
private val interopComponent: SwingInteropComponent<T>,
private val focusManager: FocusManager,
) {
private val backwardRequester = FocusRequester()
Expand Down Expand Up @@ -248,7 +227,9 @@ private class FocusSwitcher<T : Component>(
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)

val component = info.container.focusTraversalPolicy.getFirstComponent(info.container)
val component = interopComponent.container.let { container ->
MatkovIvan marked this conversation as resolved.
Show resolved Hide resolved
container.focusTraversalPolicy.getFirstComponent(container)
}
if (component != null) {
component.requestFocus(FocusEvent.Cause.TRAVERSAL_FORWARD)
} else {
Expand All @@ -265,7 +246,7 @@ private class FocusSwitcher<T : Component>(
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)

val component = info.container.focusTraversalPolicy.getLastComponent(info.container)
val component = interopComponent.container.focusTraversalPolicy.getLastComponent(interopComponent.container)
if (component != null) {
component.requestFocus(FocusEvent.Cause.TRAVERSAL_BACKWARD)
} else {
Expand All @@ -278,11 +259,54 @@ private class FocusSwitcher<T : Component>(
}
}

private class ComponentInfo<T : Component>(
container: SwingPanelContainer
private class SwingInteropComponent<T : Component>(
container: SwingPanelContainer,
var update: (T) -> Unit
): InteropComponent(container) {
lateinit var component: T
lateinit var updater: Updater<T>
private var component: T? = null
private var updater: Updater<T>? = null

fun dispose() {
container.remove(component)
updater?.dispose()
component = null
updater = null
}

fun setComponent(component: T) {
MatkovIvan marked this conversation as resolved.
Show resolved Hide resolved
this.component = component
container.add(component)
updater = Updater(component, update)
}

fun setBounds(
bounds: IntRect,
clippedBounds: IntRect = bounds
) {
clipBounds = clippedBounds // Clipping area for skia canvas
container.isVisible = !clippedBounds.isEmpty // Hide if it's fully clipped
// Swing clips children based on parent's bounds, so use our container for clipping
container.setBounds(
/* x = */ clippedBounds.left,
/* y = */ clippedBounds.top,
/* width = */ clippedBounds.width,
/* height = */ clippedBounds.height
)

// The real size and position should be based on not-clipped bounds
component?.setBounds(
/* x = */ bounds.left - clippedBounds.left, // Local position relative to container
/* y = */ bounds.top - clippedBounds.top,
/* width = */ bounds.width,
/* height = */ bounds.height
)
}

fun getDeepestComponentForEvent(event: MouseEvent): Component? {
if (component == null) return null
val point = SwingUtilities.convertPoint(event.component, event.point, component)
return SwingUtilities.getDeepestComponentAt(component, point.x, point.y)
}
}

private class Updater<T : Component>(
Expand Down Expand Up @@ -343,7 +367,7 @@ private fun Rect.round(density: Density): IntRect {
}

private class InteropPointerInputModifier<T : Component>(
private val componentInfo: ComponentInfo<T>,
private val interopComponent: SwingInteropComponent<T>,
) : PointerInputFilter(), PointerInputModifier {
override val pointerInputFilter: PointerInputFilter = this

Expand Down Expand Up @@ -380,23 +404,18 @@ private class InteropPointerInputModifier<T : Component>(
// to original component.
MouseEvent.MOUSE_ENTERED, MouseEvent.MOUSE_EXITED -> return
}
if (SwingUtilities.isDescendingFrom(e.component, componentInfo.container)) {
if (SwingUtilities.isDescendingFrom(e.component, interopComponent.container)) {
// Do not redispatch the event if it originally from this interop view.
return
}
val component = getDeepestComponentForEvent(componentInfo.component, e)
val component = interopComponent.getDeepestComponentForEvent(e)
if (component != null) {
component.dispatchEvent(SwingUtilities.convertMouseEvent(e.component, e, component))
pointerEvent.changes.fastForEach {
it.consume()
}
}
}

private fun getDeepestComponentForEvent(parent: Component, event: MouseEvent): Component? {
val point = SwingUtilities.convertPoint(event.component, event.point, parent)
return SwingUtilities.getDeepestComponentAt(parent, point.x, point.y)
}
}

/**
Expand Down
Loading
Loading