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

Refactor ComposeScene #908

Merged
merged 67 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
3cfd52e
Move input logic from scene into CombinedRootNodeOwner
MatkovIvan Oct 20, 2023
da3597a
Fix attachedOwners list cache in case of recursive calls
MatkovIvan Oct 26, 2023
9e2dec2
Fix missing setting constrains to attached owners
MatkovIvan Oct 26, 2023
f9353e4
Make RootNodeOwner skia independent
MatkovIvan Oct 26, 2023
2d4149c
Fix root owner initialization
MatkovIvan Oct 26, 2023
8dd1575
Fix missing measuring pass in tests
MatkovIvan Nov 10, 2023
914eb54
BaseComposeScene, ComposeSceneInputHandler, SnapshotInvalidationTracker
MatkovIvan Nov 13, 2023
850a8aa
Hide RootNodeOwnerImpl
MatkovIvan Nov 14, 2023
438c5dd
Remove unused expect/actual createSkiaLayer
MatkovIvan Nov 14, 2023
cf6c20d
ComposeSceneRecomposer
MatkovIvan Nov 14, 2023
b804aad
PlatformContext, ComposeSceneContext
MatkovIvan Nov 14, 2023
561dafb
New ComposeScene
MatkovIvan Nov 14, 2023
c4333f5
CombinedComposeScene
MatkovIvan Nov 14, 2023
21afbf9
AttachedComposeSceneLayer
MatkovIvan Nov 14, 2023
403382d
Focus TODOs
MatkovIvan Nov 14, 2023
919968f
Another moving round
MatkovIvan Nov 14, 2023
4909ef1
Update usage in ImageComposeScene
MatkovIvan Nov 14, 2023
e81eadd
Dialog scrim
MatkovIvan Nov 14, 2023
7ddf9f4
DesktopAccessibilityListener
MatkovIvan Nov 15, 2023
5bfd70f
Fix closing order
MatkovIvan Nov 15, 2023
703ebe1
Revert rememberCursorPosition
MatkovIvan Nov 15, 2023
a490d87
Bounds instead of constrains, AttachedComposeSceneLayer callbacks
MatkovIvan Nov 15, 2023
d062317
Update API usage in desktop tests
MatkovIvan Nov 15, 2023
1e13361
Fix window onKeyEvent, Move isWindowTransparent
MatkovIvan Nov 15, 2023
7f5b6cd
Split RootNodeOwner and Owner
MatkovIvan Nov 15, 2023
5d7a857
Fix Popup/Dialog keyboard handling
MatkovIvan Nov 15, 2023
8bcc410
Fix scene invalidation in tests
MatkovIvan Nov 15, 2023
a51e724
Adopt DepthSortedSetTest
MatkovIvan Nov 15, 2023
9ec9abc
Initialize focusable before invalidation
MatkovIvan Nov 16, 2023
17b9353
Fix incorrect expectations in Popup test
MatkovIvan Nov 16, 2023
f4710d6
Update API usage for native
MatkovIvan Nov 16, 2023
7160050
ComposeSceneFocusManager
MatkovIvan Nov 16, 2023
c541835
Initialize focus for main owner
MatkovIvan Nov 16, 2023
ff9eaf5
Merge remote-tracking branch 'origin/jb-main' into ivan.matkov/scene-…
MatkovIvan Nov 17, 2023
f9a3e2c
Fix focusRequester and add test for empty window recomposition
MatkovIvan Nov 17, 2023
8af95a0
Split function in SemanticsOwner listener
MatkovIvan Nov 17, 2023
52ec670
Remove outdated FIXMEs
MatkovIvan Nov 17, 2023
6a9a139
Add focusable to createLayer params
MatkovIvan Nov 17, 2023
4a077ed
Replace bounds to size in scene
MatkovIvan Nov 17, 2023
641dfd7
Move EmptyLayout to Popup file
MatkovIvan Nov 17, 2023
d7c45e2
Revert DefaultViewConfiguration
MatkovIvan Nov 17, 2023
92276bd
Fix DesktopTextInputService init
MatkovIvan Nov 17, 2023
eff553c
Double closing guards
MatkovIvan Nov 17, 2023
40cc81f
Infinity scene size by default
MatkovIvan Nov 17, 2023
c98e18a
Add missing param to FocusOwnerImpl
MatkovIvan Nov 20, 2023
1d4cad1
Use empty FocusManager for additional layers
MatkovIvan Nov 20, 2023
1be2d4d
Update API dump
MatkovIvan Nov 21, 2023
efc9c56
[WIP] Document new API
MatkovIvan Nov 21, 2023
c6d4826
Remove setKeyEventListener from ComposeScene
MatkovIvan Nov 21, 2023
6c4c6b3
Opt in scene usage in foundation tests
MatkovIvan Nov 21, 2023
45a5623
[WIP] Document new API
MatkovIvan Nov 21, 2023
311270a
Pass rootForTestListener to new windows
MatkovIvan Nov 21, 2023
6611735
Fix deprecation warnings
MatkovIvan Nov 21, 2023
a7eb3bc
[WIP] Document new API
MatkovIvan Nov 21, 2023
fdbaf10
Document new API
MatkovIvan Nov 22, 2023
2f217d5
Add comment about onSemanticsOwnerCreated order
MatkovIvan Nov 22, 2023
11639cc
Move isFocused flag to ComposeSceneFocusManagerImpl
MatkovIvan Nov 22, 2023
e227446
Add safe-check in onSemanticsOwnerCreated
MatkovIvan Nov 22, 2023
1d672b5
Merge branch 'jb-main' into ivan.matkov/scene-refactor-2
MatkovIvan Nov 22, 2023
68a8dd0
Renaming in SemanticsOwnerListener
MatkovIvan Nov 22, 2023
bac2bba
Address feedback
MatkovIvan Nov 22, 2023
53c7d5a
Add TODOs about lastKnownPointerPosition
MatkovIvan Nov 22, 2023
628d1bc
Remove composeSceneContext from ComposeScene interface
MatkovIvan Nov 22, 2023
fce8a2c
Rename file according to convention
MatkovIvan Nov 22, 2023
360c030
layout multiple owners on input from tests
MatkovIvan Nov 22, 2023
e096c3f
Rename CombinedComposeScene to MultiLayerComposeScene
MatkovIvan Nov 22, 2023
92ce1a1
Update API dump
MatkovIvan Nov 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
Expand Down Expand Up @@ -1216,7 +1217,7 @@ class ScrollbarTest {
}


@OptIn(InternalTestApi::class)
@OptIn(InternalTestApi::class, InternalComposeUiApi::class)
private fun ComposeTestRule.performMouseScroll(x: Int, y: Int, delta: Float) {
(this as DesktopComposeTestRule).scene.sendPointerEvent(
PointerEventType.Scroll,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
Expand All @@ -49,6 +50,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(InternalComposeUiApi::class)
@ExperimentalTestApi
@RunWith(JUnit4::class)
class DesktopScrollableTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ComposeScene
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerButton
Expand All @@ -43,7 +44,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest

@ExperimentalCoroutinesApi
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, InternalComposeUiApi::class)
class OnClickTest {

private fun testClick(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.ui.ImageComposeScene
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.input.pointer.PointerButtons
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.DefaultViewConfiguration
import androidx.compose.ui.platform.PlatformContext
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.use
Expand All @@ -38,7 +40,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest

@Suppress("DEPRECATION")
@OptIn(ExperimentalFoundationApi::class)
class DragGestureTest {

Expand Down Expand Up @@ -462,3 +463,9 @@ class DragGestureTest {
}
}
}

@OptIn(InternalComposeUiApi::class)
private class DefaultViewConfiguration(private val density: Density) : ViewConfiguration by PlatformContext.Empty.viewConfiguration {
override val touchSlop: Float
get() = with(density) { PlatformContext.Empty.viewConfiguration.touchSlop.dp.toPx() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
package androidx.compose.ui.test.junit4

import androidx.compose.runtime.Composable
import androidx.compose.ui.ComposeScene
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.scene.ComposeScene
import androidx.compose.ui.test.DesktopComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.IdlingResource
Expand Down Expand Up @@ -57,6 +58,7 @@ class DesktopComposeTestRule private constructor(
effectContext: CoroutineContext = EmptyCoroutineContext
) : this(DesktopComposeUiTest(effectContext = effectContext))

@InternalComposeUiApi
var scene: ComposeScene
get() = composeTest.scene
set(value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@
package androidx.compose.ui.test

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.ComposeScene
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asComposeCanvas
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.node.RootForTest
import androidx.compose.ui.platform.InfiniteAnimationPolicy
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.platform.PlatformContext
import androidx.compose.ui.platform.WindowInfo
import androidx.compose.ui.scene.MultiLayerComposeScene
import androidx.compose.ui.scene.ComposeScene
import androidx.compose.ui.scene.ComposeSceneContext
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.junit4.ComposeRootRegistry
import androidx.compose.ui.test.junit4.MainTestClockImpl
Expand All @@ -41,7 +43,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.PlatformTextInputService
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -90,7 +91,7 @@ private const val IDLING_RESOURCES_CHECK_INTERVAL_MS = 20L
* `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context.
*/
@ExperimentalTestApi
@OptIn(ExperimentalCoroutinesApi::class, InternalTestApi::class)
@OptIn(ExperimentalCoroutinesApi::class, InternalTestApi::class, InternalComposeUiApi::class)
class SkikoComposeUiTest(
width: Int = 1024,
height: Int = 768,
Expand All @@ -111,31 +112,6 @@ class SkikoComposeUiTest(
var onImeActionPerformed: (ImeAction) -> Unit,
)

private val textInputService = object : PlatformTextInputService {
var session: Session? = null

override fun startInput(
value: TextFieldValue,
imeOptions: ImeOptions,
onEditCommand: (List<EditCommand>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
) {
session = Session(
imeOptions = imeOptions,
onEditCommand = onEditCommand,
onImeActionPerformed = onImeActionPerformed
)
}

override fun stopInput() {
session = null
}

override fun showSoftwareKeyboard() = Unit
override fun hideSoftwareKeyboard() = Unit
override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) = Unit
}

private val composeRootRegistry = ComposeRootRegistry()

private val coroutineDispatcher = UnconfinedTestDispatcher()
Expand All @@ -157,7 +133,9 @@ class SkikoComposeUiTest(
private val coroutineContext =
coroutineDispatcher + uncaughtExceptionHandler + infiniteAnimationPolicy
private val surface = Surface.makeRasterN32Premul(width, height)
private val size = IntSize(width, height)

@InternalComposeUiApi
lateinit var scene: ComposeScene
internal set

Expand Down Expand Up @@ -188,7 +166,7 @@ class SkikoComposeUiTest(

private fun render(timeMillis: Long) {
scene.render(
surface.canvas,
surface.canvas.asComposeCanvas(),
timeMillis * NanoSecondsPerMilliSecond
)
}
Expand All @@ -200,14 +178,13 @@ class SkikoComposeUiTest(
}
}

private fun createUi() = ComposeScene(
textInputService = textInputService,
private fun createUi() = MultiLayerComposeScene(
density = density,
coroutineContext = coroutineContext,
composeSceneContext = TestComposeSceneContext(),
invalidate = { }
).apply {
constraints = Constraints(maxWidth = surface.width, maxHeight = surface.height)
// TODO: Initialize WindowInfo once public way is available
).also {
it.size = size
}

private fun shouldPumpTime(): Boolean {
Expand Down Expand Up @@ -303,22 +280,11 @@ class SkikoComposeUiTest(

@OptIn(ExperimentalComposeUiApi::class)
override fun setContent(composable: @Composable () -> Unit) {
// TODO: Remove override once it's properly initialized
val overriddenComposable: @Composable () -> Unit = {
val windowInfo = object : WindowInfo by LocalWindowInfo.current {
override val containerSize: IntSize
get() = IntSize(surface.width, surface.height)
}
CompositionLocalProvider(
LocalWindowInfo provides windowInfo,
content = composable
)
}
if (isOnUiThread()) {
scene.setContent(content = overriddenComposable)
scene.setContent(content = composable)
} else {
runOnUiThread {
scene.setContent(content = overriddenComposable)
scene.setContent(content = composable)
}

// Only wait for idleness if not on the UI thread. If we are on the UI thread, the
Expand Down Expand Up @@ -375,6 +341,54 @@ class SkikoComposeUiTest(
override fun captureToImage(semanticsNode: SemanticsNode): ImageBitmap =
this@SkikoComposeUiTest.captureToImage(semanticsNode)
}

private inner class TestWindowInfo : WindowInfo {
override val isWindowFocused: Boolean
get() = true

@ExperimentalComposeUiApi
override val containerSize: IntSize
get() = size
}

private inner class TestTextInputService : PlatformTextInputService {
var session: Session? = null

override fun startInput(
value: TextFieldValue,
imeOptions: ImeOptions,
onEditCommand: (List<EditCommand>) -> Unit,
onImeActionPerformed: (ImeAction) -> Unit
) {
session = Session(
imeOptions = imeOptions,
onEditCommand = onEditCommand,
onImeActionPerformed = onImeActionPerformed
)
}

override fun stopInput() {
session = null
}

override fun showSoftwareKeyboard() = Unit
override fun hideSoftwareKeyboard() = Unit
override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) = Unit
}

private inner class TestContext : PlatformContext by PlatformContext.Empty {
override val windowInfo: WindowInfo =
TestWindowInfo()
override val textInputService: PlatformTextInputService =
TestTextInputService()

override val rootForTestListener: PlatformContext.RootForTestListener?
get() = composeRootRegistry
}

private inner class TestComposeSceneContext : ComposeSceneContext {
override val platformContext = TestContext()
}
}

@ExperimentalTestApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,63 @@
package androidx.compose.ui.test.junit4

import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.platform.SkiaRootForTest
import androidx.compose.ui.platform.PlatformContext
import androidx.compose.ui.platform.PlatformRootForTest

/**
* Registry where all views implementing [SkiaRootForTest] should be registered while they
* Registry where all views implementing [PlatformRootForTest] should be registered while they
* are attached to the window. This registry is used by the testing library to query the roots'
* state.
*/
@OptIn(InternalComposeUiApi::class)
internal class ComposeRootRegistry {
internal class ComposeRootRegistry : PlatformContext.RootForTestListener {
private val lock = Any()
private val roots = mutableSetOf<SkiaRootForTest>()
private val roots = mutableSetOf<PlatformRootForTest>()

/**
* Returns if the registry is setup to receive registrations from [SkiaRootForTest]s
* Returns if the registry is setup to receive registrations from [PlatformRootForTest]s
*/
val isSetUp: Boolean
get() = SkiaRootForTest.onRootCreatedCallback == ::onRootCreated
private var isTracking = false

/**
* Sets up this registry to be notified of any [SkiaRootForTest] created
* Sets up this registry to be notified of any [PlatformRootForTest] created
*/
private fun setupRegistry() {
SkiaRootForTest.onRootCreatedCallback = ::onRootCreated
SkiaRootForTest.onRootDisposedCallback = ::onRootDisposed
isTracking = true
}

/**
* Cleans up the changes made by [setupRegistry]. Call this after your test has run.
*/
private fun tearDownRegistry() {
// Stop accepting new roots
SkiaRootForTest.onRootCreatedCallback = null
SkiaRootForTest.onRootDisposedCallback = null
isTracking = false
synchronized(lock) {
// Clear all references
roots.clear()
}
}

private fun onRootCreated(root: SkiaRootForTest) {
override fun onRootForTestCreated(root: PlatformRootForTest) {
synchronized(lock) {
if (isSetUp) {
if (isTracking) {
roots.add(root)
}
}
}

private fun onRootDisposed(root: SkiaRootForTest) {
override fun onRootForTestDisposed(root: PlatformRootForTest) {
synchronized(lock) {
if (isSetUp) {
if (isTracking) {
roots.remove(root)
}
}
}

/**
* Returns a copy of the set of all registered [SkiaRootForTest]s that can be interacted with.
* Returns a copy of the set of all registered [PlatformRootForTest]s that can be interacted with.
*/
fun getComposeRoots(): Set<SkiaRootForTest> {
fun getComposeRoots(): Set<PlatformRootForTest> {
return synchronized(lock) { roots.toSet() }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.nativeKeyCode
import androidx.compose.ui.input.key.nativeKeyLocation
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.node.RootForTest
import androidx.compose.ui.platform.SkiaRootForTest
import java.awt.Component
import java.awt.event.InputEvent

Expand Down