Skip to content

Conversation

@m-sasha
Copy link

@m-sasha m-sasha commented Aug 27, 2025

Provide access to semanticsOwners in ComposeWindow, ComposePanel and ImageComposeScene.

This is useful to allow external tools to examine the structure of the UI.

Fixes https://youtrack.jetbrains.com/issue/CMP-8824/Expose-semantics-tree-in-ComposePanel-and-ComposeWindow

Example usage
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.ComposePanel
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsOwner
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import java.awt.BorderLayout
import javax.swing.JFrame
import javax.swing.SwingUtilities

@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
fun main() = singleWindowApplication {
    SemanticsApp()

    LaunchedEffect(Unit) {
        val strings = mutableListOf<AnnotatedString>()
        window.semanticsOwners.forEach {
            it.rootSemanticsNode.collectTextRecursive(strings)
        }
        println(strings.joinToString("\n"))
    }
}

@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
fun main_panel() = SwingUtilities.invokeLater {
    val window = JFrame()
    val composePanel = ComposePanel()
    composePanel.setContent {
        SemanticsApp()
    }

    window.contentPane.add(composePanel, BorderLayout.CENTER)
    window.pack()
    window.isVisible = true

    println(composePanel.semanticsOwners.collectText().joinToString("\n"))
}

@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
fun main_image_compose_scene() {
    val imageComposeScene = ImageComposeScene(800, 600) {
        SemanticsApp()
    }
    imageComposeScene.render(0L)

    println(imageComposeScene.semanticsOwners.collectText().joinToString("\n"))
}


@Composable
fun SemanticsApp() {
    Column(
        verticalArrangement = Arrangement.spacedBy(10.dp),
        modifier = Modifier.padding(64.dp)
    ) {
        Text("Hello")
        OutlinedTextField(rememberTextFieldState("World"))
    }
}

private fun Collection<SemanticsOwner>.collectText(): List<AnnotatedString> {
    val result = mutableListOf<AnnotatedString>()
    forEach {
        it.rootSemanticsNode.collectTextRecursive(result)
    }
    return result
}

private fun SemanticsNode.collectTextRecursive(result: MutableList<AnnotatedString>) {
    result.addAll(config.getOrNull(SemanticsProperties.Text) ?: emptyList())
    config.getOrNull(SemanticsProperties.EditableText)?.let {
        result.add(it)
    }
    for (child in children) {
        child.collectTextRecursive(result)
    }
}

Testing

Tested manually and added unit tests.

Release Notes

Features - Desktop

  • The Compose entry points on the desktop (ComposeWindow, ComposePanel and ImageComposeScene) now expose val semanticsOwners: Collection<SemanticsOwner>.

@m-sasha m-sasha requested review from MatkovIvan and igordmn August 27, 2025 11:46
@m-sasha m-sasha force-pushed the m-sasha/expose-semanticsOwners branch 2 times, most recently from daef224 to e9c6fa0 Compare August 27, 2025 12:17
@m-sasha m-sasha force-pushed the m-sasha/expose-semanticsOwners branch from e9c6fa0 to e3eb4c0 Compare August 27, 2025 12:35
@m-sasha
Copy link
Author

m-sasha commented Aug 27, 2025

Needs another approve from @igordmn

@m-sasha
Copy link
Author

m-sasha commented Aug 29, 2025

Merging without the snapshot state tests; will fix them later.

@m-sasha m-sasha merged commit 87b1de7 into jb-main Aug 29, 2025
10 checks passed
@m-sasha m-sasha deleted the m-sasha/expose-semanticsOwners branch August 29, 2025 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants