Skip to content

Commit

Permalink
Add more KDocs
Browse files Browse the repository at this point in the history
  • Loading branch information
MatkovIvan committed Feb 28, 2024
1 parent 156b2ee commit 244947e
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ internal object ComposeFeatureFlags {
* Indicates whether interop blending is enabled.
* It allows drawing compose elements above interop and apply clip/shape modifiers to it.
*
* Note that it currently works only with Metal, DirectX and offscreen rendering.
* Known limitations:
* - Works only with Metal, DirectX and offscreen rendering
* - On DirectX, it cannot overlay another DirectX component (due to OS blending limitation)
* - On macOS, render and event dispatching order differs. It means that interop view might
* catch the mouse event even if visually it renders below Compose content
*/
val useInteropBlending: Boolean
get() = System.getProperty("compose.interop.blending").toBoolean()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,52 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.InteropContainer
import androidx.compose.ui.node.LayoutNode
import androidx.compose.ui.node.TrackInteropContainer
import androidx.compose.ui.node.TrackInteropModifierElement
import androidx.compose.ui.node.TrackInteropModifierNode
import androidx.compose.ui.node.countInteropComponentsBefore
import androidx.compose.ui.scene.ComposeSceneMediator
import java.awt.Component
import java.awt.Container

/**
* Providing interop container as composition local, so [SwingPanel] can use it to add
* native views to the hierarchy.
*/
internal val LocalSwingInteropContainer = staticCompositionLocalOf<SwingInteropContainer> {
error("LocalSwingInteropContainer not provided")
}

/**
* A container that controls interop views/components.
*
* It receives [container] native view to use it as parent for all interop views. It should be
* the same component that is used in [ComposeSceneMediator] to avoid issues with transparency.
*
* @property container The Swing container to add the interop views to.
* @property placeInteropAbove Whether to place interop components above non-interop components.
*/
internal class SwingInteropContainer(
val container: Container,
private val placeInteropAbove: Boolean
): InteropContainer<Component> {
/**
* Represents the count of interop components in [container].
*
* This variable is required to add interop components to right indexes independently of
* already existing children of [container].
*
* @see SwingInteropContainer.addInteropView
* @see SwingInteropContainer.removeInteropView
*/
private var interopComponentsCount = 0

override var rootModifier: TrackInteropModifierNode<Component>? = null

override fun addInteropView(nativeView: Component) {
val nonInteropComponents = container.componentCount - interopComponentsCount
// AWT uses the reverse order for drawing and events, so index = size - count
val index = interopComponentsCount - countInteropComponentsBefore(nativeView)
container.add(nativeView, if (placeInteropAbove) {
index
Expand Down Expand Up @@ -68,6 +94,11 @@ internal class SwingInteropContainer(
}
}

/**
* Modifier to track interop component inside [LayoutNode] hierarchy.
*
* @param component The Swing component that matches the current node.
*/
internal fun Modifier.trackSwingInterop(
component: Component
): Modifier = this then TrackInteropModifierElement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.currentCompositeKeyHash
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateObserver
import androidx.compose.ui.ComposeFeatureFlags
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusDirection
Expand Down Expand Up @@ -67,18 +68,15 @@ import kotlinx.atomicfu.atomic
val NoOpUpdate: Component.() -> Unit = {}

/**
* Composes an AWT/Swing component obtained from [factory]. The [factory]
* block will be called to obtain the [Component] to be composed.
* Composes an AWT/Swing component obtained from [factory]. The [factory] block will be called
* to obtain the [Component] to be composed.
*
* The Swing component is placed on
* top of the Compose layer (that means that Compose content can't overlap or clip it).
* This can be changed in the future, when the better interop with Swing will be implemented. See related issues:
* https://github.com/JetBrains/compose-jb/issues/1521
* https://github.com/JetBrains/compose-jb/issues/1202
* https://github.com/JetBrains/compose-jb/issues/1449
* By default, the Swing component is placed on top of the Compose layer (that means that Compose
* content can't overlap or clip it). It might be changed by `compose.interop.blending` system
* property. See [ComposeFeatureFlags.useInteropBlending].
*
* The [update] block runs due to recomposition, this is the place to set [Component] properties
* depending on state. When state changes, the block will be reexecuted to set the new properties.
* depending on state. When state changes, the block will be re-executed to set the new properties.
*
* @param background Background color of SwingPanel
* @param factory The block creating the [Component] to be composed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,25 @@ internal class ComposeSceneMediator(
val windowHandle by skiaLayerComponent::windowHandle
val renderApi by skiaLayerComponent::renderApi

// Applying layer on macOS makes our bridge non-transparent
// But it draws always on top, so we can just add it as-is
// TODO: Figure out why it makes difference in transparency
/**
* @see ComposeFeatureFlags.useInteropBlending
*/
private val useInteropBlending: Boolean
get() = ComposeFeatureFlags.useInteropBlending && skiaLayerComponent.interopBlendingSupported

/**
* Adding any components below [contentComponent] makes our bridge non-transparent on macOS.
* But as it draws always on top, so we can just add it as-is.
* TODO: Figure out why it makes difference in transparency
*/
@OptIn(ExperimentalSkikoApi::class)
private val metalOrderHack
get() = renderApi == GraphicsApi.METAL && contentComponent !is SkiaSwingLayer

/**
* A container that controls interop views/components. It is used to add and remove
* native views/components to [container].
*/
private val interopContainer = SwingInteropContainer(
container = container,
placeInteropAbove = !useInteropBlending || metalOrderHack
Expand Down Expand Up @@ -285,9 +297,6 @@ internal class ComposeSceneMediator(
*/
private var keyboardModifiersRequireUpdate = false

private val useInteropBlending: Boolean
get() = ComposeFeatureFlags.useInteropBlending && skiaLayerComponent.interopBlendingSupported

init {
// Transparency is used during redrawer creation that triggered by [addNotify], so
// it must be set to correct value before adding to the hierarchy to handle cases
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ package androidx.compose.ui.layout
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

/**
* Creates a layout with no content. It's usually used to add modifier to the hierarchy.
*
* @param modifier The modifier to be applied to the layout.
*/
@Composable
internal fun EmptyLayout(modifier: Modifier) = Layout(
content = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ package androidx.compose.ui.layout
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

/**
* Creates an overlay layout that places the content on top of each other.
*
* @param modifier The modifier to be applied to the layout.
* @param content The composable content to be placed in the overlay layout.
*/
@Composable
internal fun OverlayLayout(modifier: Modifier, content: @Composable () -> Unit) = Layout(
content = content,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,24 @@ import androidx.compose.ui.layout.OverlayLayout
import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction.CancelTraversal
import androidx.compose.ui.node.TraversableNode.Companion.TraverseDescendantsAction.ContinueTraversal

/**
* An interface for container that controls interop views/components.
*
* @param T The type of the native view.
*/
internal interface InteropContainer<T> {
var rootModifier: TrackInteropModifierNode<T>?

fun addInteropView(nativeView: T)
fun removeInteropView(nativeView: T)
}

/**
* Counts the number of interop components before the given native view in the container.
*
* @param nativeView The native view to count interop components before.
* @return The number of interop components before the given native view.
*/
internal fun <T> InteropContainer<T>.countInteropComponentsBefore(nativeView: T): Int {
var componentsBefore = 0
rootModifier?.traverseDescendants {
Expand All @@ -42,6 +53,10 @@ internal fun <T> InteropContainer<T>.countInteropComponentsBefore(nativeView: T)
return componentsBefore
}

/**
* Wrapper of Compose content that might contain interop views. It adds a helper modifier to root
* that allows to traverse interop views in the tree with the right order.
*/
@Composable
internal fun <T> InteropContainer<T>.TrackInteropContainer(container: T, content: @Composable () -> Unit) {
OverlayLayout(
Expand All @@ -52,14 +67,25 @@ internal fun <T> InteropContainer<T>.TrackInteropContainer(container: T, content
)
}

/**
* A helper modifier element that tracks an interop view inside a [LayoutNode] hierarchy.
*
* @property nativeView The native view associated with this modifier element.
* @property onModifierNodeCreated An optional block of code that to receive the reference to
* [TrackInteropModifierNode].
* @param T The type of the native view.
*
* @see TrackInteropModifierNode
* @see ModifierNodeElement
*/
internal data class TrackInteropModifierElement<T>(
var nativeView: T,
val block: ((TrackInteropModifierNode<T>) -> Unit)? = null
val onModifierNodeCreated: ((TrackInteropModifierNode<T>) -> Unit)? = null
) : ModifierNodeElement<TrackInteropModifierNode<T>>() {
override fun create() = TrackInteropModifierNode(
nativeView = nativeView
).also {
block?.invoke(it)
onModifierNodeCreated?.invoke(it)
}

override fun update(node: TrackInteropModifierNode<T>) {
Expand All @@ -70,6 +96,14 @@ internal data class TrackInteropModifierElement<T>(
private const val TRAVERSAL_NODE_KEY =
"androidx.compose.ui.node.TRACK_INTEROP_TRAVERSAL_NODE_KEY"

/**
* A modifier node for tracking and traversing interop purposes.
*
* @param T the type of the native view
* @property nativeView the native view that matches the current node.
*
* @see TraversableNode
*/
internal class TrackInteropModifierNode<T>(
var nativeView: T
) : Modifier.Node(), TraversableNode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package androidx.compose.ui.interop
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.InteropContainer
import androidx.compose.ui.node.LayoutNode
import androidx.compose.ui.node.TrackInteropModifierElement
import androidx.compose.ui.node.TrackInteropModifierNode
import androidx.compose.ui.node.countInteropComponentsBefore
Expand All @@ -29,12 +30,16 @@ import platform.CoreGraphics.CGRectZero
import platform.UIKit.UIEvent
import platform.UIKit.UIView

/**
* Providing interop container as composition local, so [UIKitView]/[UIKitViewController] can use it
* to add native views to the hierarchy.
*/
internal val LocalUIKitInteropContainer = staticCompositionLocalOf<UIKitInteropContainer> {
error("UIKitInteropContainer not provided")
}

/**
* This InteropContainer in UIView. And needs to add UIKitView interop views.
* A container that controls interop views/components.
*/
internal class UIKitInteropContainer: InteropContainer<UIView> {
val containerView: UIView = UIKitInteropContainerView()
Expand All @@ -61,8 +66,13 @@ private class UIKitInteropContainerView: UIView(CGRectZero.readValue()) {
}
}

/**
* Modifier to track interop view inside [LayoutNode] hierarchy.
*
* @param view The [UIView] that matches the current node.
*/
internal fun Modifier.trackUIKitInterop(
component: UIView
view: UIView
): Modifier = this then TrackInteropModifierElement(
nativeView = component
nativeView = view
)

0 comments on commit 244947e

Please sign in to comment.