diff --git a/CHANGELOG.md b/CHANGELOG.md index 865f2babbd..ad6918103d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,20 @@ # Change Log ## [Unreleased] -[Unreleased]: https://github.com/cashapp/redwood/compare/0.13.0...HEAD +[Unreleased]: https://github.com/cashapp/redwood/compare/0.15.0...HEAD + +New: +- Nothing yet! + +Changed: +- Drop support for non-incremental layouts in `Row` and `Column`. + +Fixed: +- Fix a layout bug where children of fixed-with `Row` containers were assigned the wrong width. + + +## [0.15.0] - 2024-09-30 +[0.15.0]: https://github.com/cashapp/redwood/releases/tag/0.15.0 New: - Default expressions can now be used directly in the schema rather than using the `@Default` annotation. The annotation has been deprecated, and will be removed in the next release. @@ -16,6 +29,7 @@ Fixed: - Breaking the last remaining retain cycle in `UIViewLazyList`. - Don't leak the `DisplayLink` when a `TreehouseApp` is stopped on iOS. - Correctly handle dynamic size changes for child widgets of `Box`, `Column`, and `Row`. +- Don't clip elements of `Column` and `Row` layouts whose unbounded size exceeds the container size. - Correctly implement margins for `Box` on iOS. - Correctly handle dynamic updates to modifiers on `Column` and `Row`. diff --git a/build-support-ksp-processor/build.gradle.kts b/build-support-ksp-processor/build.gradle.kts new file mode 100644 index 0000000000..7d91f9f205 --- /dev/null +++ b/build-support-ksp-processor/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + kotlin("jvm") +} + +dependencies { + implementation(libs.kotlin.kspApi) +} diff --git a/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/RedwoodSymbolProcessor.kt b/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/RedwoodSymbolProcessor.kt new file mode 100644 index 0000000000..67b28a8b2e --- /dev/null +++ b/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/RedwoodSymbolProcessor.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.buildsupportksp + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSAnnotated + +class RedwoodSymbolProcessor( + private val environment: SymbolProcessorEnvironment, +) : SymbolProcessor { + override fun process(resolver: Resolver): List { + SnapshotTestProcessor(environment).process(resolver) + + return listOf() // No more rounds of annotation processing. + } +} diff --git a/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/RedwoodSymbolProcessorProvider.kt b/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/RedwoodSymbolProcessorProvider.kt new file mode 100644 index 0000000000..55b492df92 --- /dev/null +++ b/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/RedwoodSymbolProcessorProvider.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.buildsupportksp + +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class RedwoodSymbolProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment) = + RedwoodSymbolProcessor(environment) +} diff --git a/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/SnapshotTestProcessor.kt b/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/SnapshotTestProcessor.kt new file mode 100644 index 0000000000..c1eca2be0b --- /dev/null +++ b/build-support-ksp-processor/src/main/kotlin/app/cash/redwood/buildsupportksp/SnapshotTestProcessor.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.buildsupportksp + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSFunctionDeclaration + +/** Confirm all snapshot `@Test` functions also have names starting with `test`. */ +internal class SnapshotTestProcessor( + private val environment: SymbolProcessorEnvironment, +) { + fun process(resolver: Resolver) { + // Only run on the first round. + if (!resolver.getNewFiles().iterator().hasNext()) return + + checkAllTests(resolver) + } + + private fun checkAllTests(resolver: Resolver) { + for (symbol in resolver.getSymbolsWithAnnotation("org.junit.Test")) { + when { + symbol !is KSFunctionDeclaration -> { + environment.logger.info("Unexpected @Test", symbol) + } + + !symbol.simpleName.asString().startsWith("test") -> { + environment.logger.error("Expected @Test to start with 'test'", symbol) + } + } + } + } +} diff --git a/build-support-ksp-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/build-support-ksp-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000000..f92cf20e0e --- /dev/null +++ b/build-support-ksp-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +app.cash.redwood.buildsupportksp.RedwoodSymbolProcessorProvider diff --git a/build-support/build.gradle b/build-support/build.gradle index d74fb4b045..abd2f050e6 100644 --- a/build-support/build.gradle +++ b/build-support/build.gradle @@ -28,6 +28,7 @@ apply plugin: 'com.android.lint' dependencies { compileOnly gradleApi() implementation libs.kotlin.gradlePlugin + implementation libs.kotlin.kspGradlePlugin implementation libs.gradleMavenPublishPlugin implementation libs.dokkaPlugin implementation libs.spotlessPlugin diff --git a/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildExtension.kt b/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildExtension.kt index 5d51036d20..e116ba0734 100644 --- a/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildExtension.kt +++ b/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildExtension.kt @@ -46,6 +46,9 @@ interface RedwoodBuildExtension { fun TaskContainer.generateComposeHelpers(packageName: String): TaskProvider fun TaskContainer.generateFlexboxHelpers(packageName: String): TaskProvider + + /** Confirm all snapshot `@Test` functions also have names starting with `test`. */ + fun sharedSnapshotTests() } enum class TargetGroup { diff --git a/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildPlugin.kt b/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildPlugin.kt index 0e294876b7..d412bf6632 100644 --- a/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildPlugin.kt +++ b/build-support/src/main/kotlin/app/cash/redwood/buildsupport/RedwoodBuildPlugin.kt @@ -75,7 +75,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile private const val REDWOOD_GROUP_ID = "app.cash.redwood" // HEY! If you change the major version update release.yaml doc folder. -private const val REDWOOD_VERSION = "0.15.0-SNAPSHOT" +private const val REDWOOD_VERSION = "0.16.0-SNAPSHOT" private val isCiEnvironment = System.getenv("CI") == "true" @@ -524,6 +524,8 @@ private class RedwoodBuildExtensionImpl(private val project: Project) : RedwoodB klib.strictValidation = isCiEnvironment nonPublicMarkers += listOf( + // Codegen-specific API only supports the exact same version as which did the generation. + "app.cash.redwood.RedwoodCodegenApi", // The yoga module is an implementation detail of our layouts. "app.cash.redwood.yoga.RedwoodYogaApi", ) @@ -647,6 +649,11 @@ private class RedwoodBuildExtensionImpl(private val project: Project) : RedwoodB it.packageName.set(packageName) } } + + override fun sharedSnapshotTests() { + project.plugins.apply("com.google.devtools.ksp") + project.dependencies.add("kspJvm", project.project(":build-support-ksp-processor")) + } } private val ziplineAttribute = Attribute.of("zipline", String::class.java) diff --git a/build-support/src/main/resources/app/cash/redwood/buildsupport/flexboxHelpers.kt b/build-support/src/main/resources/app/cash/redwood/buildsupport/flexboxHelpers.kt index d10c2d34e5..57f7b4922e 100644 --- a/build-support/src/main/resources/app/cash/redwood/buildsupport/flexboxHelpers.kt +++ b/build-support/src/main/resources/app/cash/redwood/buildsupport/flexboxHelpers.kt @@ -98,32 +98,36 @@ internal fun CrossAxisAlignment.toAlignSelf() = when (this) { * * Also also note that `Float.NaN` is used by these properties, and that `Float.NaN != Float.NaN`. * So even deciding whether a value has changed is tricky. + * + * Returns true if the node became dirty as a consequence of this call. */ -internal fun Node.applyModifier(parentModifier: Modifier, density: Density) { +internal fun Node.applyModifier(parentModifier: Modifier, density: Density): Boolean { + val wasDirty = isDirty() + // Avoid unnecessary mutations to the Node because it marks itself dirty its properties change. - var oldMarginStart = marginStart + val oldMarginStart = marginStart var newMarginStart = Float.NaN - var oldMarginEnd = marginEnd + val oldMarginEnd = marginEnd var newMarginEnd = Float.NaN - var oldMarginTop = marginTop + val oldMarginTop = marginTop var newMarginTop = Float.NaN - var oldMarginBottom = marginBottom + val oldMarginBottom = marginBottom var newMarginBottom = Float.NaN - var oldAlignSelf = alignSelf + val oldAlignSelf = alignSelf var newAlignSelf = AlignSelf.Auto - var oldRequestedMinWidth = requestedMinWidth + val oldRequestedMinWidth = requestedMinWidth var newRequestedMinWidth = Float.NaN - var oldRequestedMaxWidth = requestedMaxWidth + val oldRequestedMaxWidth = requestedMaxWidth var newRequestedMaxWidth = Float.NaN - var oldRequestedMinHeight = requestedMinHeight + val oldRequestedMinHeight = requestedMinHeight var newRequestedMinHeight = Float.NaN - var oldRequestedMaxHeight = requestedMaxHeight + val oldRequestedMaxHeight = requestedMaxHeight var newRequestedMaxHeight = Float.NaN - var oldFlexGrow = flexGrow + val oldFlexGrow = flexGrow var newFlexGrow = 0f - var oldFlexShrink = flexShrink + val oldFlexShrink = flexShrink var newFlexShrink = 0f - var oldFlexBasis = flexBasis + val oldFlexBasis = flexBasis var newFlexBasis = -1f parentModifier.forEachScoped { childModifier -> @@ -193,6 +197,8 @@ internal fun Node.applyModifier(parentModifier: Modifier, density: Density) { if (newFlexGrow neq oldFlexGrow) flexGrow = newFlexGrow if (newFlexShrink neq oldFlexShrink) flexShrink = newFlexShrink if (newFlexBasis neq oldFlexBasis) flexBasis = newFlexBasis + + return !wasDirty && isDirty() } /** diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 624bc935a9..f7f4e1c26a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,9 +3,10 @@ kotlin = "2.0.20" kotlinx-coroutines = "1.9.0" kotlinx-serialization = "1.7.3" androidx-activity = "1.9.2" -androidx-compose-ui = "1.7.2" +androidx-compose-ui = "1.7.3" jbCompose = "1.6.11" -lint = "31.6.1" +ksp = "2.0.20-1.0.25" +lint = "31.7.0" paparazzi = "1.3.2" zipline = "1.17.0" coil = "3.0.0-alpha10" @@ -16,6 +17,8 @@ kotlin-compiler = { module = "org.jetbrains.kotlin:kotlin-compiler", version.ref kotlin-compilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" } kotlin-composePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +kotlin-kspApi = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } +kotlin-kspGradlePlugin = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" } kotlin-serializationPlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -65,7 +68,7 @@ jetbrains-compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = " jetbrains-compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "jbCompose" } jetbrains-compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "jbCompose" } -androidGradlePlugin = { module = "com.android.tools.build:gradle", version = "8.6.1" } +androidGradlePlugin = { module = "com.android.tools.build:gradle", version = "8.7.0" } kotlinPoet = { module = "com.squareup:kotlinpoet", version = "1.18.1" } clikt = "com.github.ajalt.clikt:clikt:5.0.0" junit = { module = "junit:junit", version = "4.13.2" } @@ -87,7 +90,7 @@ coil-core = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } turbine = "app.cash.turbine:turbine:1.1.0" ktlint = "com.pinterest.ktlint:ktlint-cli:1.3.1" -ktlintComposeRules = "io.nlopez.compose.rules:ktlint:0.4.12" +ktlintComposeRules = "io.nlopez.compose.rules:ktlint:0.4.15" googleJavaFormat = "com.google.googlejavaformat:google-java-format:1.23.0" poko-gradlePlugin = "dev.drewhamilton.poko:poko-gradle-plugin:0.17.1" diff --git a/redwood-compose/api/android/redwood-compose.api b/redwood-compose/api/android/redwood-compose.api index 1301b4608e..f091344ed4 100644 --- a/redwood-compose/api/android/redwood-compose.api +++ b/redwood-compose/api/android/redwood-compose.api @@ -26,32 +26,12 @@ public final class app/cash/redwood/compose/BackHandlerKt { public static final fun getLocalOnBackPressedDispatcher ()Landroidx/compose/runtime/ProvidableCompositionLocal; } -public abstract interface class app/cash/redwood/compose/Node { -} - -public abstract interface class app/cash/redwood/compose/RedwoodApplier { - public abstract fun getWidgetSystem ()Lapp/cash/redwood/widget/WidgetSystem; - public abstract fun recordChanged (Lapp/cash/redwood/widget/Widget;)V -} - -public final class app/cash/redwood/compose/RedwoodComposeContent { - public static final field $stable I - public static final field Companion Lapp/cash/redwood/compose/RedwoodComposeContent$Companion; - public fun ()V - public final fun Children (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V -} - -public final class app/cash/redwood/compose/RedwoodComposeContent$Companion { - public final fun getInstance ()Lapp/cash/redwood/compose/RedwoodComposeContent; -} - public abstract interface class app/cash/redwood/compose/RedwoodComposition { public abstract fun cancel ()V public abstract fun setContent (Lkotlin/jvm/functions/Function2;)V } public final class app/cash/redwood/compose/RedwoodCompositionKt { - public static final fun RedwoodComposeNode (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V public static final fun RedwoodComposition (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/redwood/widget/RedwoodView;Lapp/cash/redwood/widget/WidgetSystem;Lkotlin/jvm/functions/Function0;)Lapp/cash/redwood/compose/RedwoodComposition; public static final fun RedwoodComposition (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/redwood/widget/Widget$Children;Lapp/cash/redwood/ui/OnBackPressedDispatcher;Landroidx/compose/runtime/saveable/SaveableStateRegistry;Lkotlinx/coroutines/flow/StateFlow;Lapp/cash/redwood/widget/WidgetSystem;Lkotlin/jvm/functions/Function0;)Lapp/cash/redwood/compose/RedwoodComposition; public static synthetic fun RedwoodComposition$default (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/redwood/widget/RedwoodView;Lapp/cash/redwood/widget/WidgetSystem;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lapp/cash/redwood/compose/RedwoodComposition; @@ -63,22 +43,6 @@ public final class app/cash/redwood/compose/UiConfigurationKt { public static final fun getLocalUiConfiguration ()Landroidx/compose/runtime/ProvidableCompositionLocal; } -public final class app/cash/redwood/compose/WidgetNode : app/cash/redwood/compose/Node { - public static final field $stable I - public static final field Companion Lapp/cash/redwood/compose/WidgetNode$Companion; - public fun (Lapp/cash/redwood/compose/RedwoodApplier;Lapp/cash/redwood/widget/Widget;)V - public final fun getContainer ()Lapp/cash/redwood/widget/Widget$Children; - public final fun getIndex ()I - public final fun getWidget ()Lapp/cash/redwood/widget/Widget; - public final fun recordChanged ()V - public final fun setContainer (Lapp/cash/redwood/widget/Widget$Children;)V - public final fun setIndex (I)V -} - -public final class app/cash/redwood/compose/WidgetNode$Companion { - public final fun getSetModifiers ()Lkotlin/jvm/functions/Function2; -} - public final class app/cash/redwood/compose/WidgetVersionKt { public static final fun getLocalWidgetVersion ()Landroidx/compose/runtime/ProvidableCompositionLocal; public static final fun getWidgetVersion (Landroidx/compose/runtime/Composer;I)I diff --git a/redwood-compose/api/jvm/redwood-compose.api b/redwood-compose/api/jvm/redwood-compose.api index 373cf500dc..5c4dab8d6a 100644 --- a/redwood-compose/api/jvm/redwood-compose.api +++ b/redwood-compose/api/jvm/redwood-compose.api @@ -4,32 +4,12 @@ public final class app/cash/redwood/compose/BackHandlerKt { public static final fun getLocalOnBackPressedDispatcher ()Landroidx/compose/runtime/ProvidableCompositionLocal; } -public abstract interface class app/cash/redwood/compose/Node { -} - -public abstract interface class app/cash/redwood/compose/RedwoodApplier { - public abstract fun getWidgetSystem ()Lapp/cash/redwood/widget/WidgetSystem; - public abstract fun recordChanged (Lapp/cash/redwood/widget/Widget;)V -} - -public final class app/cash/redwood/compose/RedwoodComposeContent { - public static final field $stable I - public static final field Companion Lapp/cash/redwood/compose/RedwoodComposeContent$Companion; - public fun ()V - public final fun Children (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V -} - -public final class app/cash/redwood/compose/RedwoodComposeContent$Companion { - public final fun getInstance ()Lapp/cash/redwood/compose/RedwoodComposeContent; -} - public abstract interface class app/cash/redwood/compose/RedwoodComposition { public abstract fun cancel ()V public abstract fun setContent (Lkotlin/jvm/functions/Function2;)V } public final class app/cash/redwood/compose/RedwoodCompositionKt { - public static final fun RedwoodComposeNode (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V public static final fun RedwoodComposition (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/redwood/widget/RedwoodView;Lapp/cash/redwood/widget/WidgetSystem;Lkotlin/jvm/functions/Function0;)Lapp/cash/redwood/compose/RedwoodComposition; public static final fun RedwoodComposition (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/redwood/widget/Widget$Children;Lapp/cash/redwood/ui/OnBackPressedDispatcher;Landroidx/compose/runtime/saveable/SaveableStateRegistry;Lkotlinx/coroutines/flow/StateFlow;Lapp/cash/redwood/widget/WidgetSystem;Lkotlin/jvm/functions/Function0;)Lapp/cash/redwood/compose/RedwoodComposition; public static synthetic fun RedwoodComposition$default (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/redwood/widget/RedwoodView;Lapp/cash/redwood/widget/WidgetSystem;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lapp/cash/redwood/compose/RedwoodComposition; @@ -41,22 +21,6 @@ public final class app/cash/redwood/compose/UiConfigurationKt { public static final fun getLocalUiConfiguration ()Landroidx/compose/runtime/ProvidableCompositionLocal; } -public final class app/cash/redwood/compose/WidgetNode : app/cash/redwood/compose/Node { - public static final field $stable I - public static final field Companion Lapp/cash/redwood/compose/WidgetNode$Companion; - public fun (Lapp/cash/redwood/compose/RedwoodApplier;Lapp/cash/redwood/widget/Widget;)V - public final fun getContainer ()Lapp/cash/redwood/widget/Widget$Children; - public final fun getIndex ()I - public final fun getWidget ()Lapp/cash/redwood/widget/Widget; - public final fun recordChanged ()V - public final fun setContainer (Lapp/cash/redwood/widget/Widget$Children;)V - public final fun setIndex (I)V -} - -public final class app/cash/redwood/compose/WidgetNode$Companion { - public final fun getSetModifiers ()Lkotlin/jvm/functions/Function2; -} - public final class app/cash/redwood/compose/WidgetVersionKt { public static final fun getLocalWidgetVersion ()Landroidx/compose/runtime/ProvidableCompositionLocal; public static final fun getWidgetVersion (Landroidx/compose/runtime/Composer;I)I diff --git a/redwood-compose/api/redwood-compose.klib.api b/redwood-compose/api/redwood-compose.klib.api index d4cabe2c36..984eed4805 100644 --- a/redwood-compose/api/redwood-compose.klib.api +++ b/redwood-compose/api/redwood-compose.klib.api @@ -7,52 +7,11 @@ // - Show declarations: true // Library unique name: -abstract interface <#A: kotlin/Any> app.cash.redwood.compose/RedwoodApplier { // app.cash.redwood.compose/RedwoodApplier|null[0] - abstract val widgetSystem // app.cash.redwood.compose/RedwoodApplier.widgetSystem|{}widgetSystem[0] - abstract fun (): app.cash.redwood.widget/WidgetSystem<#A> // app.cash.redwood.compose/RedwoodApplier.widgetSystem.|(){}[0] - - abstract fun recordChanged(app.cash.redwood.widget/Widget<#A>) // app.cash.redwood.compose/RedwoodApplier.recordChanged|recordChanged(app.cash.redwood.widget.Widget<1:0>){}[0] -} - abstract interface app.cash.redwood.compose/RedwoodComposition { // app.cash.redwood.compose/RedwoodComposition|null[0] abstract fun cancel() // app.cash.redwood.compose/RedwoodComposition.cancel|cancel(){}[0] abstract fun setContent(kotlin/Function2) // app.cash.redwood.compose/RedwoodComposition.setContent|setContent(kotlin.Function2){}[0] } -sealed interface <#A: kotlin/Any> app.cash.redwood.compose/Node // app.cash.redwood.compose/Node|null[0] - -final class <#A: out app.cash.redwood.widget/Widget<#B>, #B: kotlin/Any> app.cash.redwood.compose/WidgetNode : app.cash.redwood.compose/Node<#B> { // app.cash.redwood.compose/WidgetNode|null[0] - constructor (app.cash.redwood.compose/RedwoodApplier<#B>, #A) // app.cash.redwood.compose/WidgetNode.|(app.cash.redwood.compose.RedwoodApplier<1:1>;1:0){}[0] - - final val widget // app.cash.redwood.compose/WidgetNode.widget|{}widget[0] - final fun (): #A // app.cash.redwood.compose/WidgetNode.widget.|(){}[0] - - final var container // app.cash.redwood.compose/WidgetNode.container|{}container[0] - final fun (): app.cash.redwood.widget/Widget.Children<#B>? // app.cash.redwood.compose/WidgetNode.container.|(){}[0] - final fun (app.cash.redwood.widget/Widget.Children<#B>?) // app.cash.redwood.compose/WidgetNode.container.|(app.cash.redwood.widget.Widget.Children<1:1>?){}[0] - final var index // app.cash.redwood.compose/WidgetNode.index|{}index[0] - final fun (): kotlin/Int // app.cash.redwood.compose/WidgetNode.index.|(){}[0] - final fun (kotlin/Int) // app.cash.redwood.compose/WidgetNode.index.|(kotlin.Int){}[0] - - final fun recordChanged() // app.cash.redwood.compose/WidgetNode.recordChanged|recordChanged(){}[0] - - final object Companion { // app.cash.redwood.compose/WidgetNode.Companion|null[0] - final val SetModifiers // app.cash.redwood.compose/WidgetNode.Companion.SetModifiers|{}SetModifiers[0] - final fun (): kotlin/Function2, kotlin/Any>, app.cash.redwood/Modifier, kotlin/Unit> // app.cash.redwood.compose/WidgetNode.Companion.SetModifiers.|(){}[0] - } -} - -final class <#A: out app.cash.redwood.widget/Widget<*>> app.cash.redwood.compose/RedwoodComposeContent { // app.cash.redwood.compose/RedwoodComposeContent|null[0] - constructor () // app.cash.redwood.compose/RedwoodComposeContent.|(){}[0] - - final fun Children(kotlin/Function1<#A, app.cash.redwood.widget/Widget.Children<*>>, kotlin/Function2, androidx.compose.runtime/Composer?, kotlin/Int) // app.cash.redwood.compose/RedwoodComposeContent.Children|Children(kotlin.Function1<1:0,app.cash.redwood.widget.Widget.Children<*>>;kotlin.Function2;androidx.compose.runtime.Composer?;kotlin.Int){}[0] - - final object Companion { // app.cash.redwood.compose/RedwoodComposeContent.Companion|null[0] - final val Instance // app.cash.redwood.compose/RedwoodComposeContent.Companion.Instance|{}Instance[0] - final fun (): app.cash.redwood.compose/RedwoodComposeContent // app.cash.redwood.compose/RedwoodComposeContent.Companion.Instance.|(){}[0] - } -} - final val app.cash.redwood.compose/LocalOnBackPressedDispatcher // app.cash.redwood.compose/LocalOnBackPressedDispatcher|{}LocalOnBackPressedDispatcher[0] final fun (): androidx.compose.runtime/ProvidableCompositionLocal // app.cash.redwood.compose/LocalOnBackPressedDispatcher.|(){}[0] final val app.cash.redwood.compose/LocalUiConfiguration // app.cash.redwood.compose/LocalUiConfiguration|{}LocalUiConfiguration[0] @@ -77,7 +36,6 @@ final fun app.cash.redwood.compose/app_cash_redwood_compose_ChildrenNode$stablep final fun app.cash.redwood.compose/app_cash_redwood_compose_NodeApplier$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_NodeApplier$stableprop_getter|app_cash_redwood_compose_NodeApplier$stableprop_getter(){}[0] final fun app.cash.redwood.compose/app_cash_redwood_compose_RedwoodComposeContent$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_RedwoodComposeContent$stableprop_getter|app_cash_redwood_compose_RedwoodComposeContent$stableprop_getter(){}[0] final fun app.cash.redwood.compose/app_cash_redwood_compose_WidgetNode$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_WidgetNode$stableprop_getter|app_cash_redwood_compose_WidgetNode$stableprop_getter(){}[0] -final inline fun <#A: app.cash.redwood.widget/WidgetFactoryOwner<#C>, #B: app.cash.redwood.widget/Widget<#C>, #C: kotlin/Any> app.cash.redwood.compose/RedwoodComposeNode(crossinline kotlin/Function1<#A, #B>, kotlin/Function1>, kotlin/Unit>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int) // app.cash.redwood.compose/RedwoodComposeNode|RedwoodComposeNode(kotlin.Function1<0:0,0:1>;kotlin.Function1>,kotlin.Unit>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int){0§>;1§>;2§}[0] // Targets: [ios] final object app.cash.redwood.compose/DisplayLinkClock : androidx.compose.runtime/MonotonicFrameClock { // app.cash.redwood.compose/DisplayLinkClock|null[0] diff --git a/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt b/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt index bb9dd91542..546af5a625 100644 --- a/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt +++ b/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/RedwoodComposition.kt @@ -18,9 +18,11 @@ package app.cash.redwood.compose import androidx.compose.runtime.Applier import androidx.compose.runtime.Composable import androidx.compose.runtime.ComposeNode +import androidx.compose.runtime.Composer import androidx.compose.runtime.Composition import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisallowComposableCalls +import androidx.compose.runtime.ExplicitGroupsComposable import androidx.compose.runtime.MonotonicFrameClock import androidx.compose.runtime.Recomposer import androidx.compose.runtime.Updater @@ -179,6 +181,14 @@ public interface RedwoodApplier { public fun recordChanged(widget: Widget) } +@PublishedApi +@RedwoodCodegenApi +internal expect inline fun Composer.redwoodApplier(): RedwoodApplier + +@PublishedApi +@RedwoodCodegenApi +internal expect inline fun , V : Any> RedwoodApplier.widgetSystem(): O + /** * A version of [ComposeNode] which exposes the applier to the [factory] function. Through this * we expose the owner type [O] to our factory function so the correct widget can be created. @@ -187,6 +197,7 @@ public interface RedwoodApplier { */ @Composable @RedwoodCodegenApi +@ExplicitGroupsComposable public inline fun , W : Widget, V : Any> RedwoodComposeNode( crossinline factory: (O) -> W, update: @DisallowComposableCalls Updater>.() -> Unit, @@ -197,19 +208,9 @@ public inline fun , W : Widget, V : Any> RedwoodCom currentComposer.startNode() if (currentComposer.inserting) { - // Perform an explicit !! on the return value to avoid the Kotlin compiler inserting a huge - // string into the output as an error message for an otherwise implicit null check. - @Suppress( - // Safe so long as you use generated composition function. - "UNCHECKED_CAST", - "UNNECESSARY_NOT_NULL_ASSERTION", - ) - val applier = currentComposer.applier!! as RedwoodApplier - + val applier = currentComposer.redwoodApplier() currentComposer.createNode { - // Safe so long as you use generated composition function. - @Suppress("UNCHECKED_CAST") - WidgetNode(applier, factory(applier.widgetSystem as O)) + WidgetNode(applier, factory(applier.widgetSystem())) } } else { currentComposer.useNode() @@ -241,6 +242,7 @@ public class RedwoodComposeContent> { ) } + @RedwoodCodegenApi // https://github.com/Kotlin/binary-compatibility-validator/issues/91 public companion object { public val Instance: RedwoodComposeContent = RedwoodComposeContent() } diff --git a/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/WidgetApplier.kt b/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/WidgetApplier.kt index e11eed7286..d8c4685009 100644 --- a/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/WidgetApplier.kt +++ b/redwood-compose/src/commonMain/kotlin/app/cash/redwood/compose/WidgetApplier.kt @@ -151,6 +151,7 @@ public class WidgetNode, V : Any>( /** The index of [widget] within its parent [container] when attached. */ public var index: Int = -1 + @RedwoodCodegenApi // https://github.com/Kotlin/binary-compatibility-validator/issues/91 public companion object { public val SetModifiers: WidgetNode, Any>.(Modifier) -> Unit = { recordChanged() diff --git a/redwood-compose/src/jsMain/kotlin/app/cash/redwood/compose/actuals.kt b/redwood-compose/src/jsMain/kotlin/app/cash/redwood/compose/actuals.kt new file mode 100644 index 0000000000..a3ff071ec2 --- /dev/null +++ b/redwood-compose/src/jsMain/kotlin/app/cash/redwood/compose/actuals.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.compose + +import androidx.compose.runtime.Composer +import app.cash.redwood.RedwoodCodegenApi +import app.cash.redwood.widget.WidgetFactoryOwner + +@PublishedApi +@RedwoodCodegenApi +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun Composer.redwoodApplier(): RedwoodApplier { + return applier.unsafeCast>() +} + +@PublishedApi +@RedwoodCodegenApi +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun , V : Any> RedwoodApplier.widgetSystem(): O { + return widgetSystem.unsafeCast() +} diff --git a/redwood-compose/src/nonJsMain/kotlin/app/cash/redwood/compose/actuals.kt b/redwood-compose/src/nonJsMain/kotlin/app/cash/redwood/compose/actuals.kt new file mode 100644 index 0000000000..388907da73 --- /dev/null +++ b/redwood-compose/src/nonJsMain/kotlin/app/cash/redwood/compose/actuals.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.compose + +import androidx.compose.runtime.Composer +import app.cash.redwood.RedwoodCodegenApi +import app.cash.redwood.widget.WidgetFactoryOwner + +@PublishedApi +@RedwoodCodegenApi +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun Composer.redwoodApplier(): RedwoodApplier { + // Perform an explicit !! on the return value to avoid the Kotlin compiler inserting a huge + // string into the output as an error message for an otherwise implicit null check. + @Suppress( + // Safe so long as you use generated composition function. + "UNCHECKED_CAST", + "UNNECESSARY_NOT_NULL_ASSERTION", + ) + return applier!! as RedwoodApplier +} + +@PublishedApi +@RedwoodCodegenApi +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun , V : Any> RedwoodApplier.widgetSystem(): O { + @Suppress("UNCHECKED_CAST") + return widgetSystem as O +} diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png new file mode 100644 index 0000000000..7a719c4667 --- /dev/null +++ b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ff7c5e4f4623567070a4a4cb33c9475f50efab24adfc10288fee4f97b2490c9 +size 8997 diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png new file mode 100644 index 0000000000..0107a6e0a0 --- /dev/null +++ b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff0b3d6f02e88356c68e6ff2130aa5403f8102e2458cfe8b4cf868ddc6361a8c +size 8834 diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png similarity index 100% rename from redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png rename to redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png similarity index 100% rename from redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png rename to redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png similarity index 100% rename from redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png rename to redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png similarity index 100% rename from redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png rename to redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png new file mode 100644 index 0000000000..37c0d42df9 --- /dev/null +++ b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:356178c270e6f52f9403e4b8ac2d5808f0b4d9b3b265b50ca21527168cd4d955 +size 17925 diff --git a/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png new file mode 100644 index 0000000000..4f381dc512 --- /dev/null +++ b/redwood-layout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ea0176bc8ec076dbcac714b241dd2292819a6b2c7ce662e7b23f9985c9b18ce +size 17927 diff --git a/redwood-layout-shared-test/build.gradle b/redwood-layout-shared-test/build.gradle index 837f4b6dc7..a1afd1f4a0 100644 --- a/redwood-layout-shared-test/build.gradle +++ b/redwood-layout-shared-test/build.gradle @@ -2,6 +2,7 @@ import static app.cash.redwood.buildsupport.TargetGroup.ToolkitAllWithoutAndroid redwoodBuild { targets(ToolkitAllWithoutAndroid) + sharedSnapshotTests() } kotlin { diff --git a/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt b/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt index 59bd8cb5b1..1ac4a93e76 100644 --- a/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt +++ b/redwood-layout-shared-test/src/commonMain/kotlin/app/cash/redwood/layout/AbstractFlexContainerTest.kt @@ -40,13 +40,6 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue abstract class AbstractFlexContainerTest { - /** - * Returns true if the FlexContainer implementation implements incremental layouts. This is - * currently opt-in, but will soon be the only supported mode. - */ - open val incremental: Boolean - get() = true - abstract val widgetFactory: TestWidgetFactory abstract fun flexContainer( @@ -245,7 +238,7 @@ abstract class AbstractFlexContainerTest { snapshotter(container.value).snapshot() } - @Test fun columnWithUpdatedCrossAxisAlignment() { + @Test fun testColumnWithUpdatedCrossAxisAlignment() { val container = flexContainer(FlexDirection.Column) val snapshotter = snapshotter(container.value) container.width(Constraint.Fill) @@ -325,6 +318,27 @@ abstract class AbstractFlexContainerTest { snapshotter(container.value).snapshot() } + @Test fun testRowWithFixedWidthHasChildWithFixedHeight() { + val container = flexContainer(FlexDirection.Row).apply { + crossAxisAlignment(CrossAxisAlignment.Start) + modifier = WidthImpl(200.dp) + width(Constraint.Fill) + height(Constraint.Fill) + } + + widgetFactory.text("A ".repeat(10)).apply { + modifier = HeightImpl(50.dp) + container.children.insert(0, this) + } + + widgetFactory.text("B ".repeat(100)).apply { + container.children.insert(1, this) + } + + container.onEndChanges() + snapshotter(container.value).snapshot() + } + @Test fun testChildWithUpdatedProperty() { val container = flexContainer(FlexDirection.Column) val snapshotter = snapshotter(container.value) @@ -662,23 +676,21 @@ abstract class AbstractFlexContainerTest { val aMeasureCountV2 = a.measureCount val bMeasureCountV2 = b.measureCount val cMeasureCountV2 = c.measureCount - if (incremental) { - // Only 'b' is measured again. - assertEquals(aMeasureCountV1, aMeasureCountV2) - assertTrue(bMeasureCountV1 <= bMeasureCountV2) - assertEquals(cMeasureCountV1, cMeasureCountV2) - } + + // Only 'b' is measured again. + assertEquals(aMeasureCountV1, aMeasureCountV2) + assertTrue(bMeasureCountV1 <= bMeasureCountV2) + assertEquals(cMeasureCountV1, cMeasureCountV2) snapshotter.snapshot("v3") val aMeasureCountV3 = a.measureCount val bMeasureCountV3 = b.measureCount val cMeasureCountV3 = c.measureCount - if (incremental) { - // Nothing is measured again. - assertEquals(aMeasureCountV2, aMeasureCountV3) - assertEquals(bMeasureCountV2, bMeasureCountV3) - assertEquals(cMeasureCountV2, cMeasureCountV3) - } + + // Nothing is measured again. + assertEquals(aMeasureCountV2, aMeasureCountV3) + assertEquals(bMeasureCountV2, bMeasureCountV3) + assertEquals(cMeasureCountV2, cMeasureCountV3) } @Test fun testRecursiveLayoutIsIncremental() { @@ -726,23 +738,21 @@ abstract class AbstractFlexContainerTest { val aMeasureCountV2 = a.measureCount val bMeasureCountV2 = b.measureCount val cMeasureCountV2 = c.measureCount - if (incremental) { - // Only 'b' is measured again. - assertEquals(aMeasureCountV1, aMeasureCountV2) - assertTrue(bMeasureCountV1 <= bMeasureCountV2) - assertEquals(cMeasureCountV1, cMeasureCountV2) - } + + // Only 'b' is measured again. + assertEquals(aMeasureCountV1, aMeasureCountV2) + assertTrue(bMeasureCountV1 <= bMeasureCountV2) + assertEquals(cMeasureCountV1, cMeasureCountV2) snapshotter.snapshot("v3") val aMeasureCountV3 = a.measureCount val bMeasureCountV3 = b.measureCount val cMeasureCountV3 = c.measureCount - if (incremental) { - // Nothing is measured again. - assertEquals(aMeasureCountV2, aMeasureCountV3) - assertEquals(bMeasureCountV2, bMeasureCountV3) - assertEquals(cMeasureCountV2, cMeasureCountV3) - } + + // Nothing is measured again. + assertEquals(aMeasureCountV2, aMeasureCountV3) + assertEquals(bMeasureCountV2, bMeasureCountV3) + assertEquals(cMeasureCountV2, cMeasureCountV3) } /** Confirm that child element size changes propagate up the view hierarchy. */ @@ -782,6 +792,43 @@ abstract class AbstractFlexContainerTest { rowA2.text("A-TWO ".repeat(5)) snapshotter.snapshot("v2") } + + /** + * When a child widget's intrinsic size won't fit in the available space, what happens? We can + * either let it have its requested size anyway (and overrun the available space) or we confine it + * to the space available. + */ + @Test fun testChildIsConstrainedToParentWidth() { + // Wrap in a parent column to let us configure an exact width for our subject flex container. + // Otherwise we're relying on the platform-specific snapshot library's unspecified frame width. + val fullWidthParent = column().apply { + width(Constraint.Fill) + height(Constraint.Fill) + } + + val alignStart = HorizontalAlignmentImpl(CrossAxisAlignment.Start) + flexContainer(FlexDirection.Column) + .apply { + width(Constraint.Fill) + modifier = WidthImpl(25.dp) + add(widgetFactory.text("ok", alignStart)) // This is under 25.dp in width. + add(widgetFactory.text("1 2 3 4", alignStart)) // Each character is under 25.dp in width. + onEndChanges() + } + .also { fullWidthParent.children.insert(0, it) } + + flexContainer(FlexDirection.Column) + .apply { + width(Constraint.Fill) + modifier = WidthImpl(25.dp) + add(widgetFactory.text("overflows parent", alignStart)) // This is over 25.dp in width. + add(widgetFactory.text("1 2 3 4", alignStart)) // Each character is under 25.dp in width. + onEndChanges() + } + .also { fullWidthParent.children.insert(1, it) } + + snapshotter(fullWidthParent.value).snapshot() + } } interface TestFlexContainer : diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png new file mode 100644 index 0000000000..917dabee44 --- /dev/null +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a79c0d38522567fa6530cde363d3c0902f02b8a3dce2c7417183e745065a404 +size 82103 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnThenRow.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnThenRow.1.png index 3d53e193d1..ab96e0965f 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnThenRow.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnThenRow.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef515d673afc46ae662ca0dc227efd17a8903434ede8fa7567f22bec6930db8c -size 98527 +oid sha256:a6a7f711ad8a208ce6b6a0c7dac78d2dce3b55866fd062beaa4ec5a66f99d66e +size 98586 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.Center.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.Center.png new file mode 100644 index 0000000000..6269ae3af4 --- /dev/null +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.Center.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a5dededde32ac1a794d57b98de9a347d35ad6c3d341d0cdc9118d26cd824d36 +size 124765 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.FlexEnd.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.FlexEnd.png new file mode 100644 index 0000000000..6269ae3af4 --- /dev/null +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.FlexEnd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a5dededde32ac1a794d57b98de9a347d35ad6c3d341d0cdc9118d26cd824d36 +size 124765 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png index f3397b1794..2b544c6c20 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b9a756eddfdc540f4b3780c965f0b5d92126a41d87aaf28cbe99f41e1ff8d79 -size 71109 +oid sha256:d51ef5847b8e55c48c99d466a7cc178c7ebf973cc9f81f55f7baa0f433dcde0c +size 71989 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png index 680502f40d..cf5da950e5 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testDynamicContainerSize.single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d0b0322fa313b5a2e71c2a23ff1af419ca31aa0739230063ae77af0cbfdfd3e -size 67288 +oid sha256:d709a8220f4dbc0b8633ac1977208adb9180660d61d53130c67278eeba7b854d +size 70632 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png index c14e753ba5..beec71a0e5 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Fill.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:82bdbd1d36d60e5a11a9cde1c1404b23452abed121af41a84e7fa158a6b127df +size 70543 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png index c14e753ba5..beec71a0e5 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Column_Wrap_Wrap.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:82bdbd1d36d60e5a11a9cde1c1404b23452abed121af41a84e7fa158a6b127df +size 70543 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png index c14e753ba5..9c36376b3b 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Fill_Wrap.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:10a58848d00aaac1a29644157079a888d3ab8e24b2b7a27deaa19b79ef6b7d21 +size 83542 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png index c14e753ba5..9c36376b3b 100644 --- a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testLayoutWithConstraints_Row_Wrap_Wrap.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128b3634533162b43d8a49f9402d803b90312d76156ebb1c6d502d8c2679a308 -size 72856 +oid sha256:10a58848d00aaac1a29644157079a888d3ab8e24b2b7a27deaa19b79ef6b7d21 +size 83542 diff --git a/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testRowWithFixedWidthHasChildWithFixedHeight.1.png b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testRowWithFixedWidthHasChildWithFixedHeight.1.png new file mode 100644 index 0000000000..e8fa563d75 --- /dev/null +++ b/redwood-layout-uiview/RedwoodLayoutUIViewTests/__Snapshots__/UIViewFlexContainerTestHost/testRowWithFixedWidthHasChildWithFixedHeight.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77f024b820bb35e1f50ec994c2680d51b75b9c536cec88491e451d4bd0091b48 +size 74442 diff --git a/redwood-layout-uiview/api/redwood-layout-uiview.klib.api b/redwood-layout-uiview/api/redwood-layout-uiview.klib.api index 638aa2f3e9..0eb3f24604 100644 --- a/redwood-layout-uiview/api/redwood-layout-uiview.klib.api +++ b/redwood-layout-uiview/api/redwood-layout-uiview.klib.api @@ -8,7 +8,6 @@ // Library unique name: final class app.cash.redwood.layout.uiview/UIViewRedwoodLayoutWidgetFactory : app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactory { // app.cash.redwood.layout.uiview/UIViewRedwoodLayoutWidgetFactory|null[0] constructor () // app.cash.redwood.layout.uiview/UIViewRedwoodLayoutWidgetFactory.|(){}[0] - constructor (kotlin/Boolean) // app.cash.redwood.layout.uiview/UIViewRedwoodLayoutWidgetFactory.|(kotlin.Boolean){}[0] final fun Box(): app.cash.redwood.layout.widget/Box // app.cash.redwood.layout.uiview/UIViewRedwoodLayoutWidgetFactory.Box|Box(){}[0] final fun Column(): app.cash.redwood.layout.widget/Column // app.cash.redwood.layout.uiview/UIViewRedwoodLayoutWidgetFactory.Column|Column(){}[0] diff --git a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewBox.kt b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewBox.kt index d9d3996c6e..437d721152 100644 --- a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewBox.kt +++ b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewBox.kt @@ -97,7 +97,7 @@ internal class UIViewBox : val children = UIViewChildren( container = this, - insert = { widget, view, _, index -> + insert = { index, widget -> if (widget is ResizableWidget<*>) { widget.sizeListener = object : ResizableWidget.SizeListener { override fun invalidateSize() { @@ -105,13 +105,12 @@ internal class UIViewBox : } } } - insertSubview(view, index.convert()) + insertSubview(widget.value, index.convert()) }, remove = { index, count -> - val views = Array(count) { - typedSubviews[index].also(UIView::removeFromSuperview) + for (i in index until index + count) { + typedSubviews[index].removeFromSuperview() } - return@UIViewChildren views }, invalidateSize = ::invalidateSize, ) diff --git a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainer.kt b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainer.kt index 1a29915fbb..ce052860d7 100644 --- a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainer.kt +++ b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainer.kt @@ -33,37 +33,38 @@ import platform.darwin.NSInteger internal class UIViewFlexContainer( direction: FlexDirection, - private val incremental: Boolean, ) : YogaFlexContainer, ResizableWidget, ChangeListener { - private val yogaView: YogaUIView = YogaUIView( - applyModifier = { node, _ -> - node.applyModifier(node.context as Modifier, Density.Default) - }, - incremental = incremental, - ) + private val yogaView: YogaUIView = YogaUIView() override val rootNode: Node get() = yogaView.rootNode override val density: Density get() = Density.Default override val value: UIView get() = yogaView override val children: UIViewChildren = UIViewChildren( container = value, - insert = { widget, view, modifier, index -> - val node = view.asNode(context = modifier) + insert = { index, widget -> + val view = widget.value + val node = view.asNode() if (widget is ResizableWidget<*>) { widget.sizeListener = NodeSizeListener(node, view, this@UIViewFlexContainer) } yogaView.rootNode.children.add(index, node) + + // Always apply changes *after* adding a node to its parent. + node.applyModifier(widget.modifier, density) + value.insertSubview(view, index.convert()) }, remove = { index, count -> yogaView.rootNode.children.remove(index, count) - Array(count) { - value.typedSubviews[index].also(UIView::removeFromSuperview) + for (i in index until index + count) { + value.typedSubviews[index].removeFromSuperview() } }, - updateModifier = { modifier, index -> - yogaView.rootNode.children[index].context = modifier + onModifierUpdated = { index, widget -> + val node = yogaView.rootNode.children[index] + val nodeBecameDirty = node.applyModifier(widget.modifier, density) + invalidateSize(nodeBecameDirty = nodeBecameDirty) }, invalidateSize = ::invalidateSize, ) @@ -76,11 +77,11 @@ internal class UIViewFlexContainer( } override fun width(width: Constraint) { - yogaView.width = width + yogaView.widthConstraint = width } override fun height(height: Constraint) { - yogaView.height = height + yogaView.heightConstraint = height } override fun overflow(overflow: Overflow) { @@ -95,30 +96,24 @@ internal class UIViewFlexContainer( invalidateSize() } - internal fun invalidateSize() { - if (incremental) { - if (rootNode.markDirty()) { - // The node was newly-dirty. Propagate that up the tree. - val sizeListener = this.sizeListener - if (sizeListener != null) { - value.setNeedsLayout() - sizeListener.invalidateSize() - } else { - value.invalidateIntrinsicContentSize() // Tell the enclosing view that our size changed. - value.setNeedsLayout() // Update layout of subviews. - } + internal fun invalidateSize(nodeBecameDirty: Boolean = false) { + if (rootNode.markDirty() || nodeBecameDirty) { + // The node was newly-dirty. Propagate that up the tree. + val sizeListener = this.sizeListener + if (sizeListener != null) { + value.setNeedsLayout() + sizeListener.invalidateSize() + } else { + value.invalidateIntrinsicContentSize() // Tell the enclosing view that our size changed. + value.setNeedsLayout() // Update layout of subviews. } - } else { - value.invalidateIntrinsicContentSize() // Tell the enclosing view that our size changed. - value.setNeedsLayout() // Update layout of subviews. } } } -private fun UIView.asNode(context: Any?): Node { +private fun UIView.asNode(): Node { val childNode = Node() childNode.measureCallback = UIViewMeasureCallback(this) - childNode.context = context return childNode } diff --git a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt index 5fe4ea6a31..d3f310ad06 100644 --- a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt +++ b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewMeasureCallback.kt @@ -24,6 +24,7 @@ import kotlinx.cinterop.useContents import platform.CoreGraphics.CGSize import platform.CoreGraphics.CGSizeMake import platform.UIKit.UIView +import platform.UIKit.UIViewNoIntrinsicMetric internal class UIViewMeasureCallback(val view: UIView) : MeasureCallback { override fun measure( @@ -34,11 +35,11 @@ internal class UIViewMeasureCallback(val view: UIView) : MeasureCallback { heightMode: MeasureMode, ): Size { val constrainedWidth = when (widthMode) { - MeasureMode.Undefined -> 0.0 + MeasureMode.Undefined -> UIViewNoIntrinsicMetric else -> width.toDouble() } val constrainedHeight = when (heightMode) { - MeasureMode.Undefined -> 0.0 + MeasureMode.Undefined -> UIViewNoIntrinsicMetric else -> height.toDouble() } @@ -54,21 +55,10 @@ internal class UIViewMeasureCallback(val view: UIView) : MeasureCallback { } return Size( - width = sanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), - height = sanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), + width = sizeThatFits.width, + height = sizeThatFits.height, ) } - - private fun sanitizeMeasurement( - constrainedSize: Double, - measuredSize: Float, - measureMode: MeasureMode, - ): Float = when (measureMode) { - MeasureMode.Exactly -> constrainedSize.toFloat() - MeasureMode.AtMost -> measuredSize - MeasureMode.Undefined -> measuredSize - else -> throw AssertionError() - } } private fun CValue.toSize() = useContents { diff --git a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewRedwoodLayoutWidgetFactory.kt b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewRedwoodLayoutWidgetFactory.kt index a3e5dda960..1cbb7651ff 100644 --- a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewRedwoodLayoutWidgetFactory.kt +++ b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/UIViewRedwoodLayoutWidgetFactory.kt @@ -24,13 +24,9 @@ import app.cash.redwood.yoga.FlexDirection import platform.UIKit.UIView @ObjCName("UIViewRedwoodLayoutWidgetFactory", exact = true) -public class UIViewRedwoodLayoutWidgetFactory( - private val incremental: Boolean, -) : RedwoodLayoutWidgetFactory { - public constructor() : this(false) - +public class UIViewRedwoodLayoutWidgetFactory : RedwoodLayoutWidgetFactory { override fun Box(): Box = UIViewBox() - override fun Column(): Column = UIViewFlexContainer(FlexDirection.Column, incremental) - override fun Row(): Row = UIViewFlexContainer(FlexDirection.Row, incremental) + override fun Column(): Column = UIViewFlexContainer(FlexDirection.Column) + override fun Row(): Row = UIViewFlexContainer(FlexDirection.Row) override fun Spacer(): Spacer = UIViewSpacer() } diff --git a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt index 781cf24486..cfe7eb7359 100644 --- a/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt +++ b/redwood-layout-uiview/src/commonMain/kotlin/app/cash/redwood/layout/uiview/YogaUIView.kt @@ -14,6 +14,7 @@ import app.cash.redwood.yoga.Size import kotlinx.cinterop.CValue import kotlinx.cinterop.cValue import kotlinx.cinterop.useContents +import platform.CoreGraphics.CGFloat import platform.CoreGraphics.CGRectMake import platform.CoreGraphics.CGRectZero import platform.CoreGraphics.CGSize @@ -24,14 +25,11 @@ import platform.UIKit.UIScrollViewDelegateProtocol import platform.UIKit.UIView import platform.UIKit.UIViewNoIntrinsicMetric -internal class YogaUIView( - private val applyModifier: (Node, Int) -> Unit, - private val incremental: Boolean, -) : UIScrollView(cValue { CGRectZero }), UIScrollViewDelegateProtocol { +internal class YogaUIView : UIScrollView(cValue { CGRectZero }), UIScrollViewDelegateProtocol { val rootNode = Node() - var width = Constraint.Wrap - var height = Constraint.Wrap + var widthConstraint = Constraint.Wrap + var heightConstraint = Constraint.Wrap var onScroll: ((Px) -> Unit)? = null @@ -43,13 +41,15 @@ internal class YogaUIView( contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever } - override fun intrinsicContentSize(): CValue { - return calculateLayoutWithSize(CGSizeMake(Size.UNDEFINED.toDouble(), Size.UNDEFINED.toDouble())) - } + override fun intrinsicContentSize(): CValue = calculateLayout() override fun sizeThatFits(size: CValue): CValue { - val constrainedSize = size.useContents { sizeForConstraints(this) } - return calculateLayoutWithSize(constrainedSize) + return size.useContents> { + calculateLayout( + width = width.toYogaWithWidthConstraint(), + height = height.toYogaWithHeightConstraint(), + ) + } } override fun layoutSubviews() { @@ -57,31 +57,28 @@ internal class YogaUIView( // Based on the constraints of Fill or Wrap, we // calculate a size that the container should fit in. - val bounds = bounds.useContents { - sizeForConstraints(size) + val boundsSize = bounds.useContents { + CGSizeMake(size.width, size.height) } - if (scrollEnabled) { + val contentSize = when { + // If we're not scrolling, the contentSize should equal the size of the view. + !scrollEnabled -> boundsSize + // When scrolling is enabled, we want to calculate and apply the contentSize // separately and have it grow a much as needed in the flexDirection. // This duplicates the calculation we're doing above, and should be // combined into one call. - val scrollSize = bounds.useContents { - if (isColumn()) { - CGSizeMake(width, Size.UNDEFINED.toDouble()) - } else { - CGSizeMake(Size.UNDEFINED.toDouble(), height) - } - } - val contentSize = calculateLayoutWithSize(scrollSize) - setContentSize(contentSize) - calculateLayoutWithSize(bounds) - } else { - // If we're not scrolling, the contentSize should equal the size of the view. - val containerSize = calculateLayoutWithSize(bounds) - setContentSize(containerSize) + isColumn() -> calculateLayout(width = boundsSize.useContents { width.toYoga() }) + else -> calculateLayout(height = boundsSize.useContents { height.toYoga() }) } + setContentSize(contentSize) + calculateLayout( + width = contentSize.useContents { width.toYoga() }, + height = contentSize.useContents { height.toYoga() }, + ) + // Layout the nodes based on the calculatedLayouts above. for (childNode in rootNode.children) { childNode.view.setFrame( @@ -95,32 +92,33 @@ internal class YogaUIView( } } - private fun calculateLayoutWithSize(size: CValue): CValue { - for ((index, node) in rootNode.children.withIndex()) { - applyModifier(node, index) - } - - if (!incremental) { - rootNode.markEverythingDirty() - } + private fun calculateLayout( + width: Float = Size.UNDEFINED, + height: Float = Size.UNDEFINED, + ): CValue { + rootNode.requestedWidth = width + rootNode.requestedHeight = height - size.useContents { rootNode.measureOnly(width.toFloat(), height.toFloat()) } + rootNode.measureOnly(Size.UNDEFINED, Size.UNDEFINED) return CGSizeMake(rootNode.width.toDouble(), rootNode.height.toDouble()) } - private fun sizeForConstraints(size: CGSize): CValue { - return CGSizeMake( - width = sizeForConstraintsDimension(width, size.width), - height = sizeForConstraintsDimension(height, size.height), - ) + private fun CGFloat.toYogaWithWidthConstraint() = when (widthConstraint) { + Constraint.Wrap -> Size.UNDEFINED + else -> toYoga() + } + + private fun CGFloat.toYogaWithHeightConstraint() = when (heightConstraint) { + Constraint.Wrap -> Size.UNDEFINED + else -> toYoga() } - private fun sizeForConstraintsDimension(constraint: Constraint, dimension: Double): Double { - if (constraint == Constraint.Wrap || dimension == UIViewNoIntrinsicMetric) { - return Size.UNDEFINED.toDouble() - } else { - return dimension + /** Convert a UIView dimension (a Double) to a Yoga dimension (a Float). */ + private fun CGFloat.toYoga(): Float { + return when (this) { + UIViewNoIntrinsicMetric -> Size.UNDEFINED + else -> this.toFloat() } } diff --git a/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt b/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt index 6317796700..53f51ec323 100644 --- a/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt +++ b/redwood-layout-uiview/src/commonTest/kotlin/app/cash/redwood/layout/uiview/UIViewFlexContainerTest.kt @@ -52,7 +52,7 @@ class UIViewFlexContainerTest( direction: FlexDirection, backgroundColor: Int, ): UIViewTestFlexContainer { - return UIViewTestFlexContainer(UIViewFlexContainer(direction, incremental)).apply { + return UIViewTestFlexContainer(UIViewFlexContainer(direction)).apply { value.backgroundColor = backgroundColor.toUIColor() // Install a default SizeListener that doesn't do anything. Otherwise the test subject diff --git a/redwood-layout-view/api/redwood-layout-view.api b/redwood-layout-view/api/redwood-layout-view.api index 3c7fdc97d0..3ffac14cdf 100644 --- a/redwood-layout-view/api/redwood-layout-view.api +++ b/redwood-layout-view/api/redwood-layout-view.api @@ -1,6 +1,5 @@ public final class app/cash/redwood/layout/view/ViewRedwoodLayoutWidgetFactory : app/cash/redwood/layout/widget/RedwoodLayoutWidgetFactory { - public fun (Landroid/content/Context;Z)V - public synthetic fun (Landroid/content/Context;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Landroid/content/Context;)V public fun Box ()Lapp/cash/redwood/layout/widget/Box; public fun Column ()Lapp/cash/redwood/layout/widget/Column; public fun Row ()Lapp/cash/redwood/layout/widget/Row; diff --git a/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewFlexContainer.kt b/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewFlexContainer.kt index a8bcfd3c4b..933ed8303d 100644 --- a/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewFlexContainer.kt +++ b/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewFlexContainer.kt @@ -43,16 +43,9 @@ import app.cash.redwood.yoga.isHorizontal internal class ViewFlexContainer( private val context: Context, private val direction: FlexDirection, - incremental: Boolean = false, ) : YogaFlexContainer, ChangeListener { - private val yogaLayout: YogaLayout = YogaLayout( - context, - applyModifier = { node, index -> - node.applyModifier(children.widgets[index].modifier, density) - }, - incremental = incremental, - ) + private val yogaLayout: YogaLayout = YogaLayout(context) override val rootNode: Node get() = yogaLayout.rootNode override val density = Density(context.resources) @@ -61,14 +54,26 @@ internal class ViewFlexContainer( override val children = ViewGroupChildren( yogaLayout, - insert = { index, view -> - yogaLayout.rootNode.children.add(index, view.asNode()) + insert = { index, widget -> + val view = widget.value + + val node = view.asNode() + yogaLayout.rootNode.children.add(index, node) + + // Always apply changes *after* adding a node to its parent. + node.applyModifier(widget.modifier, density) + yogaLayout.addView(view, index) }, remove = { index, count -> yogaLayout.rootNode.children.remove(index, count) yogaLayout.removeViews(index, count) }, + onModifierUpdated = { index, widget -> + val node = yogaLayout.rootNode.children[index] + node.applyModifier(widget.modifier, density) + yogaLayout.requestLayout() + }, ) private var onScroll: ((Px) -> Unit)? = null diff --git a/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewRedwoodLayoutWidgetFactory.kt b/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewRedwoodLayoutWidgetFactory.kt index 2425ce4e73..6f79e11866 100644 --- a/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewRedwoodLayoutWidgetFactory.kt +++ b/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/ViewRedwoodLayoutWidgetFactory.kt @@ -26,13 +26,12 @@ import app.cash.redwood.yoga.FlexDirection public class ViewRedwoodLayoutWidgetFactory( private val context: Context, - private val incremental: Boolean = true, ) : RedwoodLayoutWidgetFactory { override fun Box(): Box = ViewBox(context) - override fun Column(): Column = - ViewFlexContainer(context, FlexDirection.Column, incremental) + override fun Column(): Column = ViewFlexContainer(context, FlexDirection.Column) + + override fun Row(): Row = ViewFlexContainer(context, FlexDirection.Row) - override fun Row(): Row = ViewFlexContainer(context, FlexDirection.Row, incremental) override fun Spacer(): Spacer = ViewSpacer(context) } diff --git a/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/YogaLayout.kt b/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/YogaLayout.kt index 2505bb2c71..52a8c54a48 100644 --- a/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/YogaLayout.kt +++ b/redwood-layout-view/src/main/kotlin/app/cash/redwood/layout/view/YogaLayout.kt @@ -15,11 +15,7 @@ import app.cash.redwood.yoga.Size import kotlin.math.roundToInt @SuppressLint("ViewConstructor") -internal class YogaLayout( - context: Context, - private val applyModifier: (Node, Int) -> Unit, - private val incremental: Boolean, -) : ViewGroup(context) { +internal class YogaLayout(context: Context) : ViewGroup(context) { val rootNode = Node() private fun applyLayout(node: Node, xOffset: Float, yOffset: Float) { @@ -86,20 +82,11 @@ internal class YogaLayout( MeasureSpec.UNSPECIFIED -> {} } - // This must be applied immediately before measure. - for ((index, node) in rootNode.children.withIndex()) { - applyModifier(node, index) - } - // Sync widget layout requests to the Yoga node tree. - if (incremental) { - for (node in rootNode.children) { - if (node.view?.isLayoutRequested == true) { - node.markDirty() - } + for (node in rootNode.children) { + if (node.view?.isLayoutRequested == true) { + node.markDirty() } - } else { - rootNode.markEverythingDirty() } rootNode.measureOnly(Size.UNDEFINED, Size.UNDEFINED) diff --git a/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt b/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt index 199309cc98..050caa17e5 100644 --- a/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt +++ b/redwood-layout-view/src/test/kotlin/app/cash/redwood/layout/view/ViewFlexContainerTest.kt @@ -53,7 +53,7 @@ class ViewFlexContainerTest( direction: FlexDirection, backgroundColor: Int, ): ViewTestFlexContainer { - val delegate = ViewFlexContainer(paparazzi.context, direction, incremental).apply { + val delegate = ViewFlexContainer(paparazzi.context, direction).apply { value.setBackgroundColor(backgroundColor) } return ViewTestFlexContainer(delegate) diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png new file mode 100644 index 0000000000..bd5e721338 --- /dev/null +++ b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a9605a301e6b96a8d41b8a3f7fb237106a443cf06f3139aa55fd675dc01fe21 +size 8853 diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png new file mode 100644 index 0000000000..6e74f501f9 --- /dev/null +++ b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1fe8b4c8923cf667d62c544c05b39665b906c3b70a253964edbc8a365842da5 +size 8844 diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png similarity index 100% rename from redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png rename to redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png similarity index 100% rename from redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png rename to redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png similarity index 100% rename from redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png rename to redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png similarity index 100% rename from redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png rename to redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png new file mode 100644 index 0000000000..42a1446e19 --- /dev/null +++ b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a6a57e429033e263e253f137a030aca90ef3246f70e79216430ea1872692140 +size 17817 diff --git a/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png new file mode 100644 index 0000000000..15fd1ec9e7 --- /dev/null +++ b/redwood-layout-view/src/test/snapshots/images/app.cash.redwood.layout.view_ViewFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:344a198b6025c63dbb092803c73105a8903889f8c00ff60114f3d12cd64a4d05 +size 17893 diff --git a/redwood-layout-widget/api/redwood-layout-widget.api b/redwood-layout-widget/api/redwood-layout-widget.api index 1ec657ecef..4d3193dc81 100644 --- a/redwood-layout-widget/api/redwood-layout-widget.api +++ b/redwood-layout-widget/api/redwood-layout-widget.api @@ -34,15 +34,6 @@ public abstract interface class app/cash/redwood/layout/widget/RedwoodLayoutWidg public abstract fun Spacer ()Lapp/cash/redwood/layout/widget/Spacer; } -public abstract interface class app/cash/redwood/layout/widget/RedwoodLayoutWidgetFactoryOwner : app/cash/redwood/widget/WidgetFactoryOwner { - public static final field Companion Lapp/cash/redwood/layout/widget/RedwoodLayoutWidgetFactoryOwner$Companion; - public abstract fun getRedwoodLayout ()Lapp/cash/redwood/layout/widget/RedwoodLayoutWidgetFactory; -} - -public final class app/cash/redwood/layout/widget/RedwoodLayoutWidgetFactoryOwner$Companion { - public final fun apply (Lapp/cash/redwood/layout/widget/RedwoodLayoutWidgetFactory;Ljava/lang/Object;Lapp/cash/redwood/Modifier$UnscopedElement;)V -} - public final class app/cash/redwood/layout/widget/RedwoodLayoutWidgetSystem : app/cash/redwood/layout/widget/RedwoodLayoutWidgetFactoryOwner, app/cash/redwood/widget/WidgetSystem { public static final field Companion Lapp/cash/redwood/layout/widget/RedwoodLayoutWidgetSystem$Companion; public fun (Lapp/cash/redwood/layout/widget/RedwoodLayoutWidgetFactory;)V diff --git a/redwood-layout-widget/api/redwood-layout-widget.klib.api b/redwood-layout-widget/api/redwood-layout-widget.klib.api index a4bb9dd083..2cc99c2ac5 100644 --- a/redwood-layout-widget/api/redwood-layout-widget.klib.api +++ b/redwood-layout-widget/api/redwood-layout-widget.klib.api @@ -46,15 +46,6 @@ abstract interface <#A: kotlin/Any> app.cash.redwood.layout.widget/RedwoodLayout abstract fun Spacer(): app.cash.redwood.layout.widget/Spacer<#A> // app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactory.Spacer|Spacer(){}[0] } -abstract interface <#A: kotlin/Any> app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactoryOwner : app.cash.redwood.widget/WidgetFactoryOwner<#A> { // app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactoryOwner|null[0] - abstract val RedwoodLayout // app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactoryOwner.RedwoodLayout|{}RedwoodLayout[0] - abstract fun (): app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactory<#A> // app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactoryOwner.RedwoodLayout.|(){}[0] - - final object Companion { // app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactoryOwner.Companion|null[0] - final fun <#A2: kotlin/Any> apply(app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactory<#A2>, #A2, app.cash.redwood/Modifier.UnscopedElement) // app.cash.redwood.layout.widget/RedwoodLayoutWidgetFactoryOwner.Companion.apply|apply(app.cash.redwood.layout.widget.RedwoodLayoutWidgetFactory<0:0>;0:0;app.cash.redwood.Modifier.UnscopedElement){0§}[0] - } -} - abstract interface <#A: kotlin/Any> app.cash.redwood.layout.widget/Row : app.cash.redwood.widget/Widget<#A> { // app.cash.redwood.layout.widget/Row|null[0] abstract val children // app.cash.redwood.layout.widget/Row.children|{}children[0] abstract fun (): app.cash.redwood.widget/Widget.Children<#A> // app.cash.redwood.layout.widget/Row.children.|(){}[0] diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testChildIsConstrainedToParentWidth[LTR].png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testChildIsConstrainedToParentWidth[LTR].png new file mode 100644 index 0000000000..4073a34479 --- /dev/null +++ b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testChildIsConstrainedToParentWidth[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad0191c6e03eafe37ff8b80a6fc85087f88862741da713c5b7f5d1e79fa758be +size 8995 diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testChildIsConstrainedToParentWidth[RTL].png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testChildIsConstrainedToParentWidth[RTL].png new file mode 100644 index 0000000000..5bc0e7002f --- /dev/null +++ b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testChildIsConstrainedToParentWidth[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81b35a1e916dadda6045b4471e6f15385e03184fff94341cc3b2f2e24cea1f16 +size 8830 diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png similarity index 100% rename from redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png rename to redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png similarity index 100% rename from redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png rename to redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png similarity index 100% rename from redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png rename to redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png similarity index 100% rename from redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png rename to redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png new file mode 100644 index 0000000000..bc9da29136 --- /dev/null +++ b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fb06f6053b382ce2c67a0be3030e7c108dd95208ac7b96160151fc7b30be215 +size 9382 diff --git a/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png new file mode 100644 index 0000000000..2942a83145 --- /dev/null +++ b/redwood-lazylayout-composeui/src/test/snapshots/images/app.cash.redwood.layout.composeui_ComposeUiLazyListTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fcad431314a7c24c44287d461ee39dbdf686870f5b9b00b82e944106020067b +size 9380 diff --git a/redwood-lazylayout-shared-test/build.gradle b/redwood-lazylayout-shared-test/build.gradle index 08566161bc..3e75798bd4 100644 --- a/redwood-lazylayout-shared-test/build.gradle +++ b/redwood-lazylayout-shared-test/build.gradle @@ -2,6 +2,7 @@ import static app.cash.redwood.buildsupport.TargetGroup.ToolkitAllWithoutAndroid redwoodBuild { targets(ToolkitAllWithoutAndroid) + sharedSnapshotTests() } kotlin { diff --git a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png new file mode 100644 index 0000000000..699a318449 --- /dev/null +++ b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testChildIsConstrainedToParentWidth.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dd2fe148f072751261845d54f593753da2e373144c96782bec46bfb88b3be7d +size 68295 diff --git a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.Center.png b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.Center.png new file mode 100644 index 0000000000..734cd0dcb9 --- /dev/null +++ b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.Center.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bae6428104ea24854584c00dde35ea6c5e15dd47995890430e71176d670cfd50 +size 114831 diff --git a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.FlexEnd.png b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.FlexEnd.png new file mode 100644 index 0000000000..734cd0dcb9 --- /dev/null +++ b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testColumnWithUpdatedCrossAxisAlignment.FlexEnd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bae6428104ea24854584c00dde35ea6c5e15dd47995890430e71176d670cfd50 +size 114831 diff --git a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png index 1bcb4cd16a..96a67b0a8f 100644 --- a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png +++ b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRecursiveLayoutHandlesResizes.v2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c6776c1446efaaf445ec36fb55bbcd9e08c5d4d1bae86602cdfe6ec4746b4f7 -size 77390 +oid sha256:106ecd41ecc0fef54ef4d276bd711ca9303c3b25c1d49f9f318bd898bd6f23ae +size 74954 diff --git a/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRowWithFixedWidthHasChildWithFixedHeight.1.png b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRowWithFixedWidthHasChildWithFixedHeight.1.png new file mode 100644 index 0000000000..7c865c778b --- /dev/null +++ b/redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests/__Snapshots__/UIViewLazyListAsFlexContainerTestHost/testRowWithFixedWidthHasChildWithFixedHeight.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7abfc284becf9c9be90c3c524bf1d145c865a470fb59e6081bc53962b3927f25 +size 75639 diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png new file mode 100644 index 0000000000..b845bcba31 --- /dev/null +++ b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testChildIsConstrainedToParentWidth[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1de287706d5558908aa28cdacb115d20a8aec05dff7981095406b619a6f3166c +size 8851 diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png new file mode 100644 index 0000000000..4928982350 --- /dev/null +++ b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testChildIsConstrainedToParentWidth[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1184906d0c1edae1f05a06c4c6e3007c727be9c0ce13d8afb6a7a249cae6669 +size 8848 diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png similarity index 100% rename from redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_center.png rename to redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_center.png diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png similarity index 100% rename from redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[LTR]_flexend.png rename to redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[LTR]_flexend.png diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png similarity index 100% rename from redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_center.png rename to redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_center.png diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png similarity index 100% rename from redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_columnWithUpdatedCrossAxisAlignment[RTL]_flexend.png rename to redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testColumnWithUpdatedCrossAxisAlignment[RTL]_flexend.png diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png new file mode 100644 index 0000000000..782020d596 --- /dev/null +++ b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc440224b1ca2b16951775ff0a18a37a67cc7f8d8845c5018f07b7366afb49b3 +size 9171 diff --git a/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png new file mode 100644 index 0000000000..0d5280e6c7 --- /dev/null +++ b/redwood-lazylayout-view/src/test/snapshots/images/app.cash.redwood.lazylayout.view_ViewLazyListAsFlexContainerTest_testRowWithFixedWidthHasChildWithFixedHeight[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19efec1555c96a989cc6300e67c62df5f1c63e0bdb21efaba8129d51ebd11054 +size 9160 diff --git a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api index c82f3c7466..b9166df258 100644 --- a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api +++ b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api @@ -56,15 +56,6 @@ public abstract interface class app/cash/redwood/lazylayout/widget/RedwoodLazyLa public abstract fun RefreshableLazyList ()Lapp/cash/redwood/lazylayout/widget/RefreshableLazyList; } -public abstract interface class app/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetFactoryOwner : app/cash/redwood/widget/WidgetFactoryOwner { - public static final field Companion Lapp/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetFactoryOwner$Companion; - public abstract fun getRedwoodLazyLayout ()Lapp/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetFactory; -} - -public final class app/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetFactoryOwner$Companion { - public final fun apply (Lapp/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetFactory;Ljava/lang/Object;Lapp/cash/redwood/Modifier$UnscopedElement;)V -} - public final class app/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetSystem : app/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetFactoryOwner, app/cash/redwood/widget/WidgetSystem { public static final field Companion Lapp/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetSystem$Companion; public fun (Lapp/cash/redwood/lazylayout/widget/RedwoodLazyLayoutWidgetFactory;)V diff --git a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api index b851c4017a..1d58dd039c 100644 --- a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api +++ b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api @@ -28,15 +28,6 @@ abstract interface <#A: kotlin/Any> app.cash.redwood.lazylayout.widget/RedwoodLa abstract fun RefreshableLazyList(): app.cash.redwood.lazylayout.widget/RefreshableLazyList<#A> // app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactory.RefreshableLazyList|RefreshableLazyList(){}[0] } -abstract interface <#A: kotlin/Any> app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactoryOwner : app.cash.redwood.widget/WidgetFactoryOwner<#A> { // app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactoryOwner|null[0] - abstract val RedwoodLazyLayout // app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactoryOwner.RedwoodLazyLayout|{}RedwoodLazyLayout[0] - abstract fun (): app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactory<#A> // app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactoryOwner.RedwoodLazyLayout.|(){}[0] - - final object Companion { // app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactoryOwner.Companion|null[0] - final fun <#A2: kotlin/Any> apply(app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactory<#A2>, #A2, app.cash.redwood/Modifier.UnscopedElement) // app.cash.redwood.lazylayout.widget/RedwoodLazyLayoutWidgetFactoryOwner.Companion.apply|apply(app.cash.redwood.lazylayout.widget.RedwoodLazyLayoutWidgetFactory<0:0>;0:0;app.cash.redwood.Modifier.UnscopedElement){0§}[0] - } -} - abstract interface <#A: kotlin/Any> app.cash.redwood.lazylayout.widget/RefreshableLazyList : app.cash.redwood.widget/Widget<#A> { // app.cash.redwood.lazylayout.widget/RefreshableLazyList|null[0] abstract val items // app.cash.redwood.lazylayout.widget/RefreshableLazyList.items|{}items[0] abstract fun (): app.cash.redwood.widget/Widget.Children<#A> // app.cash.redwood.lazylayout.widget/RefreshableLazyList.items.|(){}[0] diff --git a/redwood-protocol-guest/api/redwood-protocol-guest.api b/redwood-protocol-guest/api/redwood-protocol-guest.api index 4161cf6673..444ef3539b 100644 --- a/redwood-protocol-guest/api/redwood-protocol-guest.api +++ b/redwood-protocol-guest/api/redwood-protocol-guest.api @@ -6,9 +6,9 @@ public final class app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter : public fun appendCreate-kyz2zXs (II)V public fun appendModifierChange-z3jyS0k (ILapp/cash/redwood/Modifier;)V public fun appendMove-HpxY78w (IIIII)V - public fun appendPropertyChange-DxQz5cw (IILkotlinx/serialization/KSerializer;Ljava/lang/Object;)V - public fun appendPropertyChange-M7EZMwg (III)V - public fun appendPropertyChange-e3iP1vo (IIZ)V + public fun appendPropertyChange-13Ob0Yo (IIILkotlinx/serialization/KSerializer;Ljava/lang/Object;)V + public fun appendPropertyChange-ITsWdOQ (IIIZ)V + public fun appendPropertyChange-hzhmVHk (IIII)V public fun appendRemove-HpxY78w (IIIILjava/util/List;)V public fun emitChanges ()V public fun getJson ()Lkotlinx/serialization/json/Json; @@ -24,23 +24,10 @@ public final class app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter : public abstract class app/cash/redwood/protocol/guest/GuestProtocolAdapter : app/cash/redwood/protocol/EventSink { public static final field $stable I public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public abstract fun appendAdd-ARs5Qwk (IIILapp/cash/redwood/protocol/guest/ProtocolWidget;)V - public abstract fun appendCreate-kyz2zXs (II)V - public abstract fun appendModifierChange-z3jyS0k (ILapp/cash/redwood/Modifier;)V - public abstract fun appendMove-HpxY78w (IIIII)V - public abstract fun appendPropertyChange-DxQz5cw (IILkotlinx/serialization/KSerializer;Ljava/lang/Object;)V - public abstract fun appendPropertyChange-M7EZMwg (III)V - public abstract fun appendPropertyChange-e3iP1vo (IIZ)V - public abstract fun appendRemove-HpxY78w (IIIILjava/util/List;)V public abstract fun emitChanges ()V - public final fun getChildrenVisitor ()Lapp/cash/redwood/protocol/guest/ProtocolWidget$ChildrenVisitor; - public abstract fun getJson ()Lkotlinx/serialization/json/Json; public abstract fun getRoot ()Lapp/cash/redwood/widget/Widget$Children; - public final fun getSynthesizeSubtreeRemoval ()Z public abstract fun getWidgetSystem ()Lapp/cash/redwood/widget/WidgetSystem; public abstract fun initChangesSink (Lapp/cash/redwood/protocol/ChangesSink;)V - public abstract fun nextId-0HhLjSo ()I - public abstract fun removeWidget-ou3jOuA (I)V } public abstract interface class app/cash/redwood/protocol/guest/ProtocolMismatchHandler { @@ -58,35 +45,9 @@ public final class app/cash/redwood/protocol/guest/ProtocolRedwoodCompositionKt public static synthetic fun ProtocolRedwoodComposition-C-DY9sE$default (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/redwood/protocol/guest/GuestProtocolAdapter;ILapp/cash/redwood/ui/OnBackPressedDispatcher;Landroidx/compose/runtime/saveable/SaveableStateRegistry;Lkotlinx/coroutines/flow/StateFlow;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lapp/cash/redwood/compose/RedwoodComposition; } -public abstract interface class app/cash/redwood/protocol/guest/ProtocolWidget : app/cash/redwood/widget/Widget { - public abstract fun depthFirstWalk (Lapp/cash/redwood/protocol/guest/ProtocolWidget$ChildrenVisitor;)V - public abstract fun getId-0HhLjSo ()I - public abstract fun getTag-BlhN7y0 ()I - public synthetic fun getValue ()Ljava/lang/Object; - public fun getValue ()Lkotlin/Unit; - public abstract fun sendEvent (Lapp/cash/redwood/protocol/Event;)V -} - -public abstract interface class app/cash/redwood/protocol/guest/ProtocolWidget$ChildrenVisitor { - public abstract fun visit-KUkifdM (Lapp/cash/redwood/protocol/guest/ProtocolWidget;ILapp/cash/redwood/protocol/guest/ProtocolWidgetChildren;)V -} - -public final class app/cash/redwood/protocol/guest/ProtocolWidgetChildren : app/cash/redwood/widget/Widget$Children { - public static final field $stable I - public synthetic fun (IILapp/cash/redwood/protocol/guest/GuestProtocolAdapter;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun depthFirstWalk (Lapp/cash/redwood/protocol/guest/ProtocolWidget;Lapp/cash/redwood/protocol/guest/ProtocolWidget$ChildrenVisitor;)V - public fun detach ()V - public fun getWidgets ()Ljava/util/List; - public fun insert (ILapp/cash/redwood/widget/Widget;)V - public fun move (III)V - public fun onModifierUpdated (ILapp/cash/redwood/widget/Widget;)V - public fun remove (II)V -} - public abstract interface class app/cash/redwood/protocol/guest/ProtocolWidgetSystemFactory { public abstract fun create (Lapp/cash/redwood/protocol/guest/GuestProtocolAdapter;Lapp/cash/redwood/protocol/guest/ProtocolMismatchHandler;)Lapp/cash/redwood/widget/WidgetSystem; public static synthetic fun create$default (Lapp/cash/redwood/protocol/guest/ProtocolWidgetSystemFactory;Lapp/cash/redwood/protocol/guest/GuestProtocolAdapter;Lapp/cash/redwood/protocol/guest/ProtocolMismatchHandler;ILjava/lang/Object;)Lapp/cash/redwood/widget/WidgetSystem; - public abstract fun modifierTagAndSerializationStrategy (Lapp/cash/redwood/Modifier$Element;)Lkotlin/Pair; } public final class app/cash/redwood/protocol/guest/VersionKt { diff --git a/redwood-protocol-guest/api/redwood-protocol-guest.klib.api b/redwood-protocol-guest/api/redwood-protocol-guest.klib.api index 98cbb26011..9db367a6a1 100644 --- a/redwood-protocol-guest/api/redwood-protocol-guest.klib.api +++ b/redwood-protocol-guest/api/redwood-protocol-guest.klib.api @@ -16,53 +16,20 @@ abstract interface app.cash.redwood.protocol.guest/ProtocolMismatchHandler { // } } -abstract interface app.cash.redwood.protocol.guest/ProtocolWidget : app.cash.redwood.widget/Widget { // app.cash.redwood.protocol.guest/ProtocolWidget|null[0] - abstract val id // app.cash.redwood.protocol.guest/ProtocolWidget.id|{}id[0] - abstract fun (): app.cash.redwood.protocol/Id // app.cash.redwood.protocol.guest/ProtocolWidget.id.|(){}[0] - abstract val tag // app.cash.redwood.protocol.guest/ProtocolWidget.tag|{}tag[0] - abstract fun (): app.cash.redwood.protocol/WidgetTag // app.cash.redwood.protocol.guest/ProtocolWidget.tag.|(){}[0] - open val value // app.cash.redwood.protocol.guest/ProtocolWidget.value|{}value[0] - open fun () // app.cash.redwood.protocol.guest/ProtocolWidget.value.|(){}[0] - - abstract fun depthFirstWalk(app.cash.redwood.protocol.guest/ProtocolWidget.ChildrenVisitor) // app.cash.redwood.protocol.guest/ProtocolWidget.depthFirstWalk|depthFirstWalk(app.cash.redwood.protocol.guest.ProtocolWidget.ChildrenVisitor){}[0] - abstract fun sendEvent(app.cash.redwood.protocol/Event) // app.cash.redwood.protocol.guest/ProtocolWidget.sendEvent|sendEvent(app.cash.redwood.protocol.Event){}[0] - - abstract fun interface ChildrenVisitor { // app.cash.redwood.protocol.guest/ProtocolWidget.ChildrenVisitor|null[0] - abstract fun visit(app.cash.redwood.protocol.guest/ProtocolWidget, app.cash.redwood.protocol/ChildrenTag, app.cash.redwood.protocol.guest/ProtocolWidgetChildren) // app.cash.redwood.protocol.guest/ProtocolWidget.ChildrenVisitor.visit|visit(app.cash.redwood.protocol.guest.ProtocolWidget;app.cash.redwood.protocol.ChildrenTag;app.cash.redwood.protocol.guest.ProtocolWidgetChildren){}[0] - } -} - abstract interface app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory { // app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory|null[0] - abstract fun <#A1: app.cash.redwood/Modifier.Element> modifierTagAndSerializationStrategy(#A1): kotlin/Pair?> // app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory.modifierTagAndSerializationStrategy|modifierTagAndSerializationStrategy(0:0){0§}[0] abstract fun create(app.cash.redwood.protocol.guest/GuestProtocolAdapter, app.cash.redwood.protocol.guest/ProtocolMismatchHandler = ...): app.cash.redwood.widget/WidgetSystem // app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory.create|create(app.cash.redwood.protocol.guest.GuestProtocolAdapter;app.cash.redwood.protocol.guest.ProtocolMismatchHandler){}[0] } abstract class app.cash.redwood.protocol.guest/GuestProtocolAdapter : app.cash.redwood.protocol/EventSink { // app.cash.redwood.protocol.guest/GuestProtocolAdapter|null[0] constructor (app.cash.redwood.protocol/RedwoodVersion) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.|(app.cash.redwood.protocol.RedwoodVersion){}[0] - abstract val json // app.cash.redwood.protocol.guest/GuestProtocolAdapter.json|{}json[0] - abstract fun (): kotlinx.serialization.json/Json // app.cash.redwood.protocol.guest/GuestProtocolAdapter.json.|(){}[0] abstract val root // app.cash.redwood.protocol.guest/GuestProtocolAdapter.root|{}root[0] abstract fun (): app.cash.redwood.widget/Widget.Children // app.cash.redwood.protocol.guest/GuestProtocolAdapter.root.|(){}[0] abstract val widgetSystem // app.cash.redwood.protocol.guest/GuestProtocolAdapter.widgetSystem|{}widgetSystem[0] abstract fun (): app.cash.redwood.widget/WidgetSystem // app.cash.redwood.protocol.guest/GuestProtocolAdapter.widgetSystem.|(){}[0] - final val childrenVisitor // app.cash.redwood.protocol.guest/GuestProtocolAdapter.childrenVisitor|{}childrenVisitor[0] - final fun (): app.cash.redwood.protocol.guest/ProtocolWidget.ChildrenVisitor // app.cash.redwood.protocol.guest/GuestProtocolAdapter.childrenVisitor.|(){}[0] - final val synthesizeSubtreeRemoval // app.cash.redwood.protocol.guest/GuestProtocolAdapter.synthesizeSubtreeRemoval|{}synthesizeSubtreeRemoval[0] - final fun (): kotlin/Boolean // app.cash.redwood.protocol.guest/GuestProtocolAdapter.synthesizeSubtreeRemoval.|(){}[0] - abstract fun <#A1: kotlin/Any?> appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlinx.serialization/KSerializer<#A1>, #A1) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlinx.serialization.KSerializer<0:0>;0:0){0§}[0] - abstract fun appendAdd(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, app.cash.redwood.protocol.guest/ProtocolWidget) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendAdd|appendAdd(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;app.cash.redwood.protocol.guest.ProtocolWidget){}[0] - abstract fun appendCreate(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendCreate|appendCreate(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag){}[0] - abstract fun appendModifierChange(app.cash.redwood.protocol/Id, app.cash.redwood/Modifier) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendModifierChange|appendModifierChange(app.cash.redwood.protocol.Id;app.cash.redwood.Modifier){}[0] - abstract fun appendMove(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, kotlin/Int, kotlin/Int) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendMove|appendMove(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;kotlin.Int;kotlin.Int){}[0] - abstract fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/Boolean) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.Boolean){}[0] - abstract fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/UInt) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.UInt){}[0] - abstract fun appendRemove(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, kotlin/Int, kotlin.collections/List) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendRemove|appendRemove(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;kotlin.Int;kotlin.collections.List){}[0] abstract fun emitChanges() // app.cash.redwood.protocol.guest/GuestProtocolAdapter.emitChanges|emitChanges(){}[0] abstract fun initChangesSink(app.cash.redwood.protocol/ChangesSink) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.initChangesSink|initChangesSink(app.cash.redwood.protocol.ChangesSink){}[0] - abstract fun nextId(): app.cash.redwood.protocol/Id // app.cash.redwood.protocol.guest/GuestProtocolAdapter.nextId|nextId(){}[0] - abstract fun removeWidget(app.cash.redwood.protocol/Id) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.removeWidget|removeWidget(app.cash.redwood.protocol.Id){}[0] } final class app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter : app.cash.redwood.protocol.guest/GuestProtocolAdapter { // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter|null[0] @@ -75,13 +42,13 @@ final class app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter : app.ca final val widgetSystem // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.widgetSystem|{}widgetSystem[0] final fun (): app.cash.redwood.widget/WidgetSystem // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.widgetSystem.|(){}[0] - final fun <#A1: kotlin/Any?> appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlinx.serialization/KSerializer<#A1>, #A1) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlinx.serialization.KSerializer<0:0>;0:0){0§}[0] + final fun <#A1: kotlin/Any?> appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag, app.cash.redwood.protocol/PropertyTag, kotlinx.serialization/KSerializer<#A1>, #A1) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag;app.cash.redwood.protocol.PropertyTag;kotlinx.serialization.KSerializer<0:0>;0:0){0§}[0] final fun appendAdd(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, app.cash.redwood.protocol.guest/ProtocolWidget) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendAdd|appendAdd(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;app.cash.redwood.protocol.guest.ProtocolWidget){}[0] final fun appendCreate(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendCreate|appendCreate(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag){}[0] final fun appendModifierChange(app.cash.redwood.protocol/Id, app.cash.redwood/Modifier) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendModifierChange|appendModifierChange(app.cash.redwood.protocol.Id;app.cash.redwood.Modifier){}[0] final fun appendMove(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, kotlin/Int, kotlin/Int) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendMove|appendMove(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;kotlin.Int;kotlin.Int){}[0] - final fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/Boolean) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.Boolean){}[0] - final fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/UInt) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.UInt){}[0] + final fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag, app.cash.redwood.protocol/PropertyTag, kotlin/Boolean) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag;app.cash.redwood.protocol.PropertyTag;kotlin.Boolean){}[0] + final fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag, app.cash.redwood.protocol/PropertyTag, kotlin/UInt) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag;app.cash.redwood.protocol.PropertyTag;kotlin.UInt){}[0] final fun appendRemove(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, kotlin/Int, kotlin.collections/List) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendRemove|appendRemove(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;kotlin.Int;kotlin.collections.List){}[0] final fun emitChanges() // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.emitChanges|emitChanges(){}[0] final fun initChangesSink(app.cash.redwood.protocol/ChangesSink) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.initChangesSink|initChangesSink(app.cash.redwood.protocol.ChangesSink){}[0] @@ -91,20 +58,6 @@ final class app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter : app.ca final fun takeChanges(): kotlin.collections/List // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.takeChanges|takeChanges(){}[0] } -final class app.cash.redwood.protocol.guest/ProtocolWidgetChildren : app.cash.redwood.widget/Widget.Children { // app.cash.redwood.protocol.guest/ProtocolWidgetChildren|null[0] - constructor (app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, app.cash.redwood.protocol.guest/GuestProtocolAdapter) // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.|(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;app.cash.redwood.protocol.guest.GuestProtocolAdapter){}[0] - - final val widgets // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.widgets|{}widgets[0] - final fun (): kotlin.collections/List // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.widgets.|(){}[0] - - final fun depthFirstWalk(app.cash.redwood.protocol.guest/ProtocolWidget, app.cash.redwood.protocol.guest/ProtocolWidget.ChildrenVisitor) // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.depthFirstWalk|depthFirstWalk(app.cash.redwood.protocol.guest.ProtocolWidget;app.cash.redwood.protocol.guest.ProtocolWidget.ChildrenVisitor){}[0] - final fun detach() // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.detach|detach(){}[0] - final fun insert(kotlin/Int, app.cash.redwood.widget/Widget) // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.insert|insert(kotlin.Int;app.cash.redwood.widget.Widget){}[0] - final fun move(kotlin/Int, kotlin/Int, kotlin/Int) // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.move|move(kotlin.Int;kotlin.Int;kotlin.Int){}[0] - final fun onModifierUpdated(kotlin/Int, app.cash.redwood.widget/Widget) // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.onModifierUpdated|onModifierUpdated(kotlin.Int;app.cash.redwood.widget.Widget){}[0] - final fun remove(kotlin/Int, kotlin/Int) // app.cash.redwood.protocol.guest/ProtocolWidgetChildren.remove|remove(kotlin.Int;kotlin.Int){}[0] -} - final val app.cash.redwood.protocol.guest/app_cash_redwood_protocol_guest_DefaultGuestProtocolAdapter$stableprop // app.cash.redwood.protocol.guest/app_cash_redwood_protocol_guest_DefaultGuestProtocolAdapter$stableprop|#static{}app_cash_redwood_protocol_guest_DefaultGuestProtocolAdapter$stableprop[0] final val app.cash.redwood.protocol.guest/app_cash_redwood_protocol_guest_GuestProtocolAdapter$stableprop // app.cash.redwood.protocol.guest/app_cash_redwood_protocol_guest_GuestProtocolAdapter$stableprop|#static{}app_cash_redwood_protocol_guest_GuestProtocolAdapter$stableprop[0] final val app.cash.redwood.protocol.guest/app_cash_redwood_protocol_guest_ProtocolWidgetChildren$stableprop // app.cash.redwood.protocol.guest/app_cash_redwood_protocol_guest_ProtocolWidgetChildren$stableprop|#static{}app_cash_redwood_protocol_guest_ProtocolWidgetChildren$stableprop[0] diff --git a/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter.kt b/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter.kt index 022e28d004..1c1f9b6e24 100644 --- a/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter.kt +++ b/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter.kt @@ -86,28 +86,31 @@ public class DefaultGuestProtocolAdapter( public override fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, serializer: KSerializer, value: T, ) { - changes.add(PropertyChange(id, tag, json.encodeToJsonElement(serializer, value))) + changes.add(PropertyChange(id, widgetTag, propertyTag, json.encodeToJsonElement(serializer, value))) } public override fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, value: Boolean, ) { - changes.add(PropertyChange(id, tag, JsonPrimitive(value))) + changes.add(PropertyChange(id, widgetTag, propertyTag, JsonPrimitive(value))) } @OptIn(ExperimentalSerializationApi::class) override fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, value: UInt, ) { - changes.add(PropertyChange(id, tag, JsonPrimitive(value))) + changes.add(PropertyChange(id, widgetTag, propertyTag, JsonPrimitive(value))) } override fun appendModifierChange(id: Id, value: Modifier) { diff --git a/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapter.kt b/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapter.kt index 7f76c8f3fb..45afe6e27f 100644 --- a/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapter.kt +++ b/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapter.kt @@ -80,7 +80,8 @@ public abstract class GuestProtocolAdapter( @RedwoodCodegenApi public abstract fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, serializer: KSerializer, value: T, ) @@ -88,7 +89,8 @@ public abstract class GuestProtocolAdapter( @RedwoodCodegenApi public abstract fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, value: Boolean, ) @@ -102,7 +104,8 @@ public abstract class GuestProtocolAdapter( @RedwoodCodegenApi public abstract fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, value: UInt, ) diff --git a/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/ProtocolWidget.kt b/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/ProtocolWidget.kt index e3c51ab756..675fd6a403 100644 --- a/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/ProtocolWidget.kt +++ b/redwood-protocol-guest/src/commonMain/kotlin/app/cash/redwood/protocol/guest/ProtocolWidget.kt @@ -65,6 +65,7 @@ public interface ProtocolWidget : Widget { */ public fun depthFirstWalk(visitor: ChildrenVisitor) + @RedwoodCodegenApi // https://github.com/Kotlin/binary-compatibility-validator/issues/91 public fun interface ChildrenVisitor { public fun visit( parent: ProtocolWidget, diff --git a/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapterTest.kt b/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapterTest.kt index 1f367743d3..b57f863393 100644 --- a/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapterTest.kt +++ b/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/GuestProtocolAdapterTest.kt @@ -63,7 +63,7 @@ class GuestProtocolAdapterTest { val expected = listOf( Create(Id(1), WidgetTag(5)), - PropertyChange(Id(1), PropertyTag(2), JsonPrimitive("PT10S")), + PropertyChange(Id(1), WidgetTag(5), PropertyTag(2), JsonPrimitive("PT10S")), ) assertThat(guestAdapter.takeChanges()).isEqualTo(expected) } diff --git a/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/ProtocolTest.kt b/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/ProtocolTest.kt index 0e48ac4c15..48e517da70 100644 --- a/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/ProtocolTest.kt +++ b/redwood-protocol-guest/src/commonTest/kotlin/app/cash/redwood/protocol/guest/ProtocolTest.kt @@ -120,7 +120,7 @@ class ProtocolTest { // Text Create(Id(2), WidgetTag(3)), // text - PropertyChange(Id(2), PropertyTag(1), JsonPrimitive("hey")), + PropertyChange(Id(2), WidgetTag(3), PropertyTag(1), JsonPrimitive("hey")), ModifierChange(Id(2)), ChildrenChange.Add(Id(1), ChildrenTag(1), Id(2), 0), // Row @@ -129,7 +129,7 @@ class ProtocolTest { // Text Create(Id(4), WidgetTag(3)), // text - PropertyChange(Id(4), PropertyTag(1), JsonPrimitive("hello")), + PropertyChange(Id(4), WidgetTag(3), PropertyTag(1), JsonPrimitive("hello")), ModifierChange(Id(4)), ChildrenChange.Add(Id(3), ChildrenTag(1), Id(4), 0), ChildrenChange.Add(Id(1), ChildrenTag(1), Id(3), 1), @@ -151,29 +151,29 @@ class ProtocolTest { // Button Create(Id(1), WidgetTag(4)), // text - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("hi")), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(1), JsonPrimitive("hi")), // onClick - PropertyChange(Id(1), PropertyTag(2), JsonPrimitive(false)), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(2), JsonPrimitive(false)), // color - PropertyChange(Id(1), PropertyTag(3), JsonPrimitive(0u)), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(3), JsonPrimitive(0u)), ModifierChange(Id(1)), ChildrenChange.Add(Id.Root, ChildrenTag.Root, Id(1), 0), // Button Create(Id(2), WidgetTag(4)), // text - PropertyChange(Id(2), PropertyTag(1), JsonPrimitive("hi")), + PropertyChange(Id(2), WidgetTag(4), PropertyTag(1), JsonPrimitive("hi")), // onClick - PropertyChange(Id(2), PropertyTag(2), JsonPrimitive(true)), + PropertyChange(Id(2), WidgetTag(4), PropertyTag(2), JsonPrimitive(true)), // color - PropertyChange(Id(2), PropertyTag(3), JsonPrimitive(0u)), + PropertyChange(Id(2), WidgetTag(4), PropertyTag(3), JsonPrimitive(0u)), ModifierChange(Id(2)), ChildrenChange.Add(Id.Root, ChildrenTag.Root, Id(2), 1), // Button2 Create(Id(3), WidgetTag(7)), // text - PropertyChange(Id(3), PropertyTag(1), JsonPrimitive("hi")), + PropertyChange(Id(3), WidgetTag(7), PropertyTag(1), JsonPrimitive("hi")), // onClick - PropertyChange(Id(3), PropertyTag(2), JsonPrimitive(true)), + PropertyChange(Id(3), WidgetTag(7), PropertyTag(2), JsonPrimitive(true)), ModifierChange(Id(3)), ChildrenChange.Add(Id.Root, ChildrenTag.Root, Id(3), 2), ), @@ -214,11 +214,11 @@ class ProtocolTest { // Button Create(Id(1), WidgetTag(4)), // text - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("state: 0")), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(1), JsonPrimitive("state: 0")), // onClick - PropertyChange(Id(1), PropertyTag(2), JsonPrimitive(true)), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(2), JsonPrimitive(true)), // color - PropertyChange(Id(1), PropertyTag(3), JsonPrimitive(0u)), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(3), JsonPrimitive(0u)), ModifierChange(Id(1)), ChildrenChange.Add(Id.Root, ChildrenTag.Root, Id(1), 0), ), @@ -230,7 +230,7 @@ class ProtocolTest { assertThat(composition.awaitSnapshot()).isEqualTo( listOf( // text - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("state: 1")), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(1), JsonPrimitive("state: 1")), ), ) @@ -240,9 +240,9 @@ class ProtocolTest { assertThat(composition.awaitSnapshot()).isEqualTo( listOf( // text - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("state: 2")), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(1), JsonPrimitive("state: 2")), // text - PropertyChange(Id(1), PropertyTag(2), JsonPrimitive(false)), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(2), JsonPrimitive(false)), ), ) @@ -252,7 +252,7 @@ class ProtocolTest { assertThat(composition.awaitSnapshot()).isEqualTo( listOf( // text - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("state: 3")), + PropertyChange(Id(1), WidgetTag(4), PropertyTag(1), JsonPrimitive("state: 3")), ), ) } @@ -283,9 +283,9 @@ class ProtocolTest { // Button2 Create(Id(1), WidgetTag(7)), // text - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("state: 0")), + PropertyChange(Id(1), WidgetTag(7), PropertyTag(1), JsonPrimitive("state: 0")), // onClick - PropertyChange(Id(1), PropertyTag(2), JsonPrimitive(true)), + PropertyChange(Id(1), WidgetTag(7), PropertyTag(2), JsonPrimitive(true)), ModifierChange(Id(1)), ChildrenChange.Add(Id.Root, ChildrenTag.Root, Id(1), 0), ), @@ -297,7 +297,7 @@ class ProtocolTest { assertThat(composition.awaitSnapshot()).isEqualTo( listOf( // text - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("state: 1")), + PropertyChange(Id(1), WidgetTag(7), PropertyTag(1), JsonPrimitive("state: 1")), ), ) } diff --git a/redwood-protocol-host/api/redwood-protocol-host.api b/redwood-protocol-host/api/redwood-protocol-host.api index 7ed924dade..c336697315 100644 --- a/redwood-protocol-host/api/redwood-protocol-host.api +++ b/redwood-protocol-host/api/redwood-protocol-host.api @@ -1,26 +1,9 @@ -public abstract interface class app/cash/redwood/protocol/host/GeneratedProtocolFactory : app/cash/redwood/protocol/host/ProtocolFactory { - public abstract fun createModifier (Lapp/cash/redwood/protocol/ModifierElement;)Lapp/cash/redwood/Modifier; - public abstract fun createNode-kyz2zXs (II)Lapp/cash/redwood/protocol/host/ProtocolNode; - public abstract fun widgetChildren-WCEpcRY (I)[I -} - public final class app/cash/redwood/protocol/host/HostProtocolAdapter : app/cash/redwood/protocol/ChangesSink { public synthetic fun (Ljava/lang/String;Lapp/cash/redwood/widget/Widget$Children;Lapp/cash/redwood/protocol/host/ProtocolFactory;Lapp/cash/redwood/protocol/host/UiEventSink;Lapp/cash/redwood/leaks/LeakDetector;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun close ()V public fun sendChanges (Ljava/util/List;)V } -public abstract interface class app/cash/redwood/protocol/host/IdVisitor { - public abstract fun visit-ou3jOuA (I)V -} - -public final class app/cash/redwood/protocol/host/ProtocolChildren { - public fun (Lapp/cash/redwood/widget/Widget$Children;)V - public final fun detach ()V - public final fun getChildren ()Lapp/cash/redwood/widget/Widget$Children; - public final fun visitIds (Lapp/cash/redwood/protocol/host/IdVisitor;)V -} - public abstract interface class app/cash/redwood/protocol/host/ProtocolFactory { public abstract fun getWidgetSystem ()Lapp/cash/redwood/widget/WidgetSystem; } @@ -37,20 +20,6 @@ public abstract interface class app/cash/redwood/protocol/host/ProtocolMismatchH public final class app/cash/redwood/protocol/host/ProtocolMismatchHandler$Companion { } -public abstract class app/cash/redwood/protocol/host/ProtocolNode { - public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public abstract fun apply (Lapp/cash/redwood/protocol/PropertyChange;Lapp/cash/redwood/protocol/host/UiEventSink;)V - public abstract fun children-dBpC-2Y (I)Lapp/cash/redwood/protocol/host/ProtocolChildren; - public abstract fun detach ()V - public final fun getId-0HhLjSo ()I - public abstract fun getWidget ()Lapp/cash/redwood/widget/Widget; - public abstract fun getWidgetTag-BlhN7y0 ()I - public final fun setId-ou3jOuA (I)V - public abstract fun toString ()Ljava/lang/String; - public final fun updateModifier (Lapp/cash/redwood/Modifier;)V - public fun visitIds (Lapp/cash/redwood/protocol/host/IdVisitor;)V -} - public final class app/cash/redwood/protocol/host/UiEvent { public synthetic fun (II[Ljava/lang/Object;[Lkotlinx/serialization/SerializationStrategy;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getArgs ()[Ljava/lang/Object; diff --git a/redwood-protocol-host/api/redwood-protocol-host.klib.api b/redwood-protocol-host/api/redwood-protocol-host.klib.api index d0cbc89516..ab4c28544b 100644 --- a/redwood-protocol-host/api/redwood-protocol-host.klib.api +++ b/redwood-protocol-host/api/redwood-protocol-host.klib.api @@ -6,20 +6,10 @@ // - Show declarations: true // Library unique name: -abstract fun interface app.cash.redwood.protocol.host/IdVisitor { // app.cash.redwood.protocol.host/IdVisitor|null[0] - abstract fun visit(app.cash.redwood.protocol/Id) // app.cash.redwood.protocol.host/IdVisitor.visit|visit(app.cash.redwood.protocol.Id){}[0] -} - abstract fun interface app.cash.redwood.protocol.host/UiEventSink { // app.cash.redwood.protocol.host/UiEventSink|null[0] abstract fun sendEvent(app.cash.redwood.protocol.host/UiEvent) // app.cash.redwood.protocol.host/UiEventSink.sendEvent|sendEvent(app.cash.redwood.protocol.host.UiEvent){}[0] } -abstract interface <#A: kotlin/Any> app.cash.redwood.protocol.host/GeneratedProtocolFactory : app.cash.redwood.protocol.host/ProtocolFactory<#A> { // app.cash.redwood.protocol.host/GeneratedProtocolFactory|null[0] - abstract fun createModifier(app.cash.redwood.protocol/ModifierElement): app.cash.redwood/Modifier // app.cash.redwood.protocol.host/GeneratedProtocolFactory.createModifier|createModifier(app.cash.redwood.protocol.ModifierElement){}[0] - abstract fun createNode(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag): app.cash.redwood.protocol.host/ProtocolNode<#A>? // app.cash.redwood.protocol.host/GeneratedProtocolFactory.createNode|createNode(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag){}[0] - abstract fun widgetChildren(app.cash.redwood.protocol/WidgetTag): kotlin/IntArray? // app.cash.redwood.protocol.host/GeneratedProtocolFactory.widgetChildren|widgetChildren(app.cash.redwood.protocol.WidgetTag){}[0] -} - abstract interface app.cash.redwood.protocol.host/ProtocolMismatchHandler { // app.cash.redwood.protocol.host/ProtocolMismatchHandler|null[0] abstract fun onUnknownChildren(app.cash.redwood.protocol/WidgetTag, app.cash.redwood.protocol/ChildrenTag) // app.cash.redwood.protocol.host/ProtocolMismatchHandler.onUnknownChildren|onUnknownChildren(app.cash.redwood.protocol.WidgetTag;app.cash.redwood.protocol.ChildrenTag){}[0] abstract fun onUnknownModifier(app.cash.redwood.protocol/ModifierTag) // app.cash.redwood.protocol.host/ProtocolMismatchHandler.onUnknownModifier|onUnknownModifier(app.cash.redwood.protocol.ModifierTag){}[0] @@ -37,26 +27,6 @@ sealed interface <#A: kotlin/Any> app.cash.redwood.protocol.host/ProtocolFactory abstract fun (): app.cash.redwood.widget/WidgetSystem<#A> // app.cash.redwood.protocol.host/ProtocolFactory.widgetSystem.|(){}[0] } -abstract class <#A: kotlin/Any> app.cash.redwood.protocol.host/ProtocolNode { // app.cash.redwood.protocol.host/ProtocolNode|null[0] - constructor (app.cash.redwood.protocol/Id) // app.cash.redwood.protocol.host/ProtocolNode.|(app.cash.redwood.protocol.Id){}[0] - - abstract val widget // app.cash.redwood.protocol.host/ProtocolNode.widget|{}widget[0] - abstract fun (): app.cash.redwood.widget/Widget<#A> // app.cash.redwood.protocol.host/ProtocolNode.widget.|(){}[0] - abstract val widgetTag // app.cash.redwood.protocol.host/ProtocolNode.widgetTag|{}widgetTag[0] - abstract fun (): app.cash.redwood.protocol/WidgetTag // app.cash.redwood.protocol.host/ProtocolNode.widgetTag.|(){}[0] - - final var id // app.cash.redwood.protocol.host/ProtocolNode.id|{}id[0] - final fun (): app.cash.redwood.protocol/Id // app.cash.redwood.protocol.host/ProtocolNode.id.|(){}[0] - final fun (app.cash.redwood.protocol/Id) // app.cash.redwood.protocol.host/ProtocolNode.id.|(app.cash.redwood.protocol.Id){}[0] - - abstract fun apply(app.cash.redwood.protocol/PropertyChange, app.cash.redwood.protocol.host/UiEventSink) // app.cash.redwood.protocol.host/ProtocolNode.apply|apply(app.cash.redwood.protocol.PropertyChange;app.cash.redwood.protocol.host.UiEventSink){}[0] - abstract fun children(app.cash.redwood.protocol/ChildrenTag): app.cash.redwood.protocol.host/ProtocolChildren<#A>? // app.cash.redwood.protocol.host/ProtocolNode.children|children(app.cash.redwood.protocol.ChildrenTag){}[0] - abstract fun detach() // app.cash.redwood.protocol.host/ProtocolNode.detach|detach(){}[0] - abstract fun toString(): kotlin/String // app.cash.redwood.protocol.host/ProtocolNode.toString|toString(){}[0] - final fun updateModifier(app.cash.redwood/Modifier) // app.cash.redwood.protocol.host/ProtocolNode.updateModifier|updateModifier(app.cash.redwood.Modifier){}[0] - open fun visitIds(app.cash.redwood.protocol.host/IdVisitor) // app.cash.redwood.protocol.host/ProtocolNode.visitIds|visitIds(app.cash.redwood.protocol.host.IdVisitor){}[0] -} - final class <#A: kotlin/Any> app.cash.redwood.protocol.host/HostProtocolAdapter : app.cash.redwood.protocol/ChangesSink { // app.cash.redwood.protocol.host/HostProtocolAdapter|null[0] constructor (app.cash.redwood.protocol/RedwoodVersion, app.cash.redwood.widget/Widget.Children<#A>, app.cash.redwood.protocol.host/ProtocolFactory<#A>, app.cash.redwood.protocol.host/UiEventSink, app.cash.redwood.leaks/LeakDetector) // app.cash.redwood.protocol.host/HostProtocolAdapter.|(app.cash.redwood.protocol.RedwoodVersion;app.cash.redwood.widget.Widget.Children<1:0>;app.cash.redwood.protocol.host.ProtocolFactory<1:0>;app.cash.redwood.protocol.host.UiEventSink;app.cash.redwood.leaks.LeakDetector){}[0] @@ -64,16 +34,6 @@ final class <#A: kotlin/Any> app.cash.redwood.protocol.host/HostProtocolAdapter final fun sendChanges(kotlin.collections/List) // app.cash.redwood.protocol.host/HostProtocolAdapter.sendChanges|sendChanges(kotlin.collections.List){}[0] } -final class <#A: kotlin/Any> app.cash.redwood.protocol.host/ProtocolChildren { // app.cash.redwood.protocol.host/ProtocolChildren|null[0] - constructor (app.cash.redwood.widget/Widget.Children<#A>) // app.cash.redwood.protocol.host/ProtocolChildren.|(app.cash.redwood.widget.Widget.Children<1:0>){}[0] - - final val children // app.cash.redwood.protocol.host/ProtocolChildren.children|{}children[0] - final fun (): app.cash.redwood.widget/Widget.Children<#A> // app.cash.redwood.protocol.host/ProtocolChildren.children.|(){}[0] - - final fun detach() // app.cash.redwood.protocol.host/ProtocolChildren.detach|detach(){}[0] - final fun visitIds(app.cash.redwood.protocol.host/IdVisitor) // app.cash.redwood.protocol.host/ProtocolChildren.visitIds|visitIds(app.cash.redwood.protocol.host.IdVisitor){}[0] -} - final class app.cash.redwood.protocol.host/UiEvent { // app.cash.redwood.protocol.host/UiEvent|null[0] constructor (app.cash.redwood.protocol/Id, app.cash.redwood.protocol/EventTag, kotlin/Array?, kotlin/Array>?) // app.cash.redwood.protocol.host/UiEvent.|(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.EventTag;kotlin.Array?;kotlin.Array>?){}[0] diff --git a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt index f66d6e15e7..0509b267f5 100644 --- a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt +++ b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt @@ -57,7 +57,7 @@ public class HostProtocolAdapter( private val leakDetector: LeakDetector, ) : ChangesSink { private val factory = when (factory) { - is GeneratedProtocolFactory -> factory + is GeneratedHostProtocol -> factory } private val nodes = @@ -83,7 +83,8 @@ public class HostProtocolAdapter( val id = change.id when (change) { is Create -> { - val node = factory.createNode(id, change.tag) ?: continue + val widgetProtocol = factory.widget(change.tag) ?: continue + val node = widgetProtocol.createNode(id) val old = nodes.put(change.id.value, node) require(old == null) { "Insert attempted to replace existing widget with ID ${change.id.value}" @@ -396,6 +397,8 @@ private class RootProtocolNode( Widget { override val widgetTag: WidgetTag get() = UnknownWidgetTag + override val widgetName: String get() = "RootProtocolNode" + private val children = ProtocolChildren(children) override fun apply(change: PropertyChange, eventSink: UiEventSink) { @@ -424,8 +427,6 @@ private class RootProtocolNode( override fun detach() { children.detach() } - - override fun toString() = "RootProtocolNode" } private const val REUSE_MODIFIER_TAG = -4_543_827 diff --git a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt index 4b71cbd614..977e8c1b4a 100644 --- a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt +++ b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt @@ -31,7 +31,7 @@ import app.cash.redwood.protocol.host.HostProtocolAdapter.ReuseNode */ @OptIn(RedwoodCodegenApi::class) internal fun shapesEqual( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, a: ReuseNode<*>, b: ProtocolNode<*>, ): Boolean { @@ -39,7 +39,8 @@ internal fun shapesEqual( if (a.widgetTag == UnknownWidgetTag) return false // No 'Create' for this. if (b.widgetTag != a.widgetTag) return false // Widget types don't match. - val widgetChildren = factory.widgetChildren(a.widgetTag) + val widgetChildren = factory.widget(a.widgetTag) + ?.childrenTags ?: return true // Widget has no children. return widgetChildren.all { childrenTag -> @@ -59,7 +60,7 @@ internal fun shapesEqual( */ @OptIn(RedwoodCodegenApi::class) private fun childrenEqual( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, aChildren: List>, bChildren: List>, childrenTag: ChildrenTag, @@ -81,7 +82,7 @@ private fun childrenEqual( /** Returns a hash of this node, or 0L if this node isn't eligible for reuse. */ @OptIn(RedwoodCodegenApi::class) internal fun shapeHash( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, node: ReuseNode<*>, ): Long { if (!node.eligibleForReuse) return 0L // This node is ineligible. @@ -89,7 +90,7 @@ internal fun shapeHash( var result = node.widgetTag.value.toLong() - factory.widgetChildren(node.widgetTag)?.forEach { childrenTag -> + factory.widget(node.widgetTag)?.childrenTags?.forEach { childrenTag -> result = (result * 37L) + childrenTag var childCount = 0 for (child in node.children) { @@ -106,11 +107,11 @@ internal fun shapeHash( /** Returns the same hash as [shapeHash], but on an already-built [ProtocolNode]. */ @OptIn(RedwoodCodegenApi::class) internal fun shapeHash( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, node: ProtocolNode<*>, ): Long { var result = node.widgetTag.value.toLong() - factory.widgetChildren(node.widgetTag)?.forEach { childrenTag -> + factory.widget(node.widgetTag)?.childrenTags?.forEach { childrenTag -> result = (result * 37L) + childrenTag val children = node.children(ChildrenTag(childrenTag)) ?: return@forEach // This acts like a 'continue'. diff --git a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt index 284b4edbd7..0906c6f473 100644 --- a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt +++ b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt @@ -35,20 +35,20 @@ public sealed interface ProtocolFactory { } /** - * [ProtocolFactory] but containing codegen APIs. + * [ProtocolFactory] but containing codegen APIs for a schema. * * @suppress */ @RedwoodCodegenApi -public interface GeneratedProtocolFactory : ProtocolFactory { +public interface GeneratedHostProtocol : ProtocolFactory { /** - * Create a new protocol node with [id] of the specified [tag]. + * Look up host protocol information for a widget with the given [tag]. * * Invalid [tag] values can either produce an exception or result in `null` being returned. * If `null` is returned, the caller should make every effort to ignore this node and * continue executing. */ - public fun createNode(id: Id, tag: WidgetTag): ProtocolNode? + public fun widget(tag: WidgetTag): WidgetHostProtocol? /** * Create a new modifier from the specified [element]. @@ -57,12 +57,22 @@ public interface GeneratedProtocolFactory : ProtocolFactory { * or result in the unit [`Modifier`][Modifier.Companion] being returned. */ public fun createModifier(element: ModifierElement): Modifier +} + +/** + * Protocol APIs for a widget definition. + * + * @suppress + */ +@RedwoodCodegenApi +public interface WidgetHostProtocol { + /** Create an instance of this widget wrapped as a [ProtocolNode] with the given [id]. */ + public fun createNode(id: Id): ProtocolNode /** - * Look up known children tags for the given widget [tag]. These are stored as a bare [IntArray] - * for efficiency, but are otherwise an array of [ChildrenTag] instances. - * - * @return `null` when widget has no children + * Look up known children tags for this widget. These are stored as a bare [IntArray] + * for efficiency, but are otherwise an array of [ChildrenTag] instances. A value of + * `null` indicates no children. */ - public fun widgetChildren(tag: WidgetTag): IntArray? + public val childrenTags: IntArray? } diff --git a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolNode.kt b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolNode.kt index 05260397c9..4a54fae09e 100644 --- a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolNode.kt +++ b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolNode.kt @@ -37,6 +37,8 @@ public abstract class ProtocolNode( ) { public abstract val widgetTag: WidgetTag + public abstract val widgetName: String + public abstract val widget: Widget /** The index of [widget] within its parent [container]. */ @@ -78,7 +80,14 @@ public abstract class ProtocolNode( public abstract fun detach() /** Human-readable name of this node along with [id] and [widgetTag]. */ - public abstract override fun toString(): String + public final override fun toString(): String = buildString { + append(widgetName) + append("(id=") + append(id.value) + append(", tag=") + append(widgetTag.value) + append(")") + } } /** @suppress */ diff --git a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ChildrenNodeIndexTest.kt b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ChildrenNodeIndexTest.kt index 9dacfb3a1d..db9087458b 100644 --- a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ChildrenNodeIndexTest.kt +++ b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ChildrenNodeIndexTest.kt @@ -128,6 +128,7 @@ class ChildrenNodeIndexTest { @OptIn(RedwoodCodegenApi::class) private class WidgetNode(override val widget: StringWidget) : ProtocolNode(Id(1)) { override val widgetTag: WidgetTag get() = WidgetTag(1) + override val widgetName: String get() = "WidgetNode" override fun apply(change: PropertyChange, eventSink: UiEventSink) { throw UnsupportedOperationException() @@ -139,8 +140,6 @@ private class WidgetNode(override val widget: StringWidget) : ProtocolNode { diff --git a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapterTest.kt b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapterTest.kt index b54266f631..63c11170aa 100644 --- a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapterTest.kt +++ b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapterTest.kt @@ -126,7 +126,8 @@ class HostProtocolAdapterTest { // Set Button's required color property. PropertyChange( id = Id(1), - tag = PropertyTag(3), + widgetTag = WidgetTag(4), + propertyTag = PropertyTag(3), value = JsonPrimitive(0), ), Add( @@ -155,8 +156,9 @@ class HostProtocolAdapterTest { val updateButtonText = listOf( PropertyChange( id = Id(1), + widgetTag = WidgetTag(4), // text - tag = PropertyTag(1), + propertyTag = PropertyTag(1), value = JsonPrimitive("hello"), ), ) @@ -230,7 +232,7 @@ class HostProtocolAdapterTest { ModifierChange(Id(2)), // Text Create(Id(3), WidgetTag(3)), - PropertyChange(Id(3), PropertyTag(1), JsonPrimitive("hello")), + PropertyChange(Id(3), WidgetTag(3), PropertyTag(1), JsonPrimitive("hello")), ModifierChange(Id(3)), Add(Id(2), ChildrenTag(1), Id(3), 0), Add(Id(1), ChildrenTag(1), Id(2), 0), @@ -241,7 +243,7 @@ class HostProtocolAdapterTest { // Validate we're tracking ID=3. host.sendChanges( listOf( - PropertyChange(Id(3), PropertyTag(1), JsonPrimitive("hey")), + PropertyChange(Id(3), WidgetTag(3), PropertyTag(1), JsonPrimitive("hey")), ), ) @@ -255,7 +257,7 @@ class HostProtocolAdapterTest { assertFailure { host.sendChanges( listOf( - PropertyChange(Id(3), PropertyTag(1), JsonPrimitive("sup")), + PropertyChange(Id(3), WidgetTag(3), PropertyTag(1), JsonPrimitive("sup")), ), ) }.isInstanceOf() diff --git a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt index f49c965f18..84734a56a3 100644 --- a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt +++ b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt @@ -60,7 +60,7 @@ class ProtocolFactoryTest { ) val t = assertFailsWith { - factory.createNode(Id(1), WidgetTag(345432)) + factory.widget(WidgetTag(345432)) } assertThat(t).hasMessage("Unknown widget tag 345432") } @@ -76,8 +76,7 @@ class ProtocolFactoryTest { mismatchHandler = handler, ) - assertThat(factory.createNode(Id(1), WidgetTag(345432))).isNull() - + assertThat(factory.widget(WidgetTag(345432))).isNull() assertThat(handler.events.single()).isEqualTo("Unknown widget 345432") } @@ -216,7 +215,7 @@ class ProtocolFactoryTest { RedwoodLazyLayout = RedwoodLazyLayoutTestingWidgetFactory(), ), ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) val t = assertFailsWith { button.children(ChildrenTag(345432)) @@ -235,7 +234,7 @@ class ProtocolFactoryTest { mismatchHandler = handler, ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) assertThat(button.children(ChildrenTag(345432))).isNull() assertThat(handler.events.single()).isEqualTo("Unknown children 345432 for 4") @@ -255,10 +254,10 @@ class ProtocolFactoryTest { ), json = json, ) - val textInput = factory.createNode(Id(1), WidgetTag(5))!! + val textInput = factory.widget(WidgetTag(5))!!.createNode(Id(1)) val throwingEventSink = UiEventSink { error(it) } - textInput.apply(PropertyChange(Id(1), PropertyTag(2), JsonPrimitive("PT10S")), throwingEventSink) + textInput.apply(PropertyChange(Id(1), WidgetTag(5), PropertyTag(2), JsonPrimitive("PT10S")), throwingEventSink) assertThat((textInput.widget.value as TextInputValue).customType).isEqualTo(10.seconds) } @@ -271,9 +270,9 @@ class ProtocolFactoryTest { RedwoodLazyLayout = RedwoodLazyLayoutTestingWidgetFactory(), ), ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) - val change = PropertyChange(Id(1), PropertyTag(345432)) + val change = PropertyChange(Id(1), WidgetTag(4), PropertyTag(345432)) val eventSink = UiEventSink { throw UnsupportedOperationException() } val t = assertFailsWith { button.apply(change, eventSink) @@ -291,9 +290,9 @@ class ProtocolFactoryTest { ), mismatchHandler = handler, ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) - button.apply(PropertyChange(Id(1), PropertyTag(345432))) { throw UnsupportedOperationException() } + button.apply(PropertyChange(Id(1), WidgetTag(4), PropertyTag(345432))) { throw UnsupportedOperationException() } assertThat(handler.events.single()).isEqualTo("Unknown property 345432 for 4") } @@ -312,10 +311,10 @@ class ProtocolFactoryTest { ), json = json, ) - val textInput = factory.createNode(Id(1), WidgetTag(5))!! + val textInput = factory.widget(WidgetTag(5))!!.createNode(Id(1)) val eventSink = RecordingUiEventSink() - textInput.apply(PropertyChange(Id(1), PropertyTag(4), JsonPrimitive(true)), eventSink) + textInput.apply(PropertyChange(Id(1), WidgetTag(5), PropertyTag(4), JsonPrimitive(true)), eventSink) (textInput.widget.value as TextInputValue).onChangeCustomType!!.invoke(10.seconds) diff --git a/redwood-protocol/api/redwood-protocol.api b/redwood-protocol/api/redwood-protocol.api index 1f835c9bc7..413f025a97 100644 --- a/redwood-protocol/api/redwood-protocol.api +++ b/redwood-protocol/api/redwood-protocol.api @@ -326,11 +326,12 @@ public final class app/cash/redwood/protocol/ModifierTag$Companion { public final class app/cash/redwood/protocol/PropertyChange : app/cash/redwood/protocol/ValueChange { public static final field Companion Lapp/cash/redwood/protocol/PropertyChange$Companion; - public synthetic fun (IILkotlinx/serialization/json/JsonElement;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (IIILkotlinx/serialization/json/JsonElement;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public fun getId-0HhLjSo ()I - public final fun getTag-VGAaYLs ()I + public final fun getPropertyTag-VGAaYLs ()I public final fun getValue ()Lkotlinx/serialization/json/JsonElement; + public final fun getWidgetTag-BlhN7y0 ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -347,8 +348,8 @@ public synthetic class app/cash/redwood/protocol/PropertyChange$$serializer : ko } public final class app/cash/redwood/protocol/PropertyChange$Companion { - public final fun invoke-e3iP1vo (IILkotlinx/serialization/json/JsonElement;)Lapp/cash/redwood/protocol/PropertyChange; - public static synthetic fun invoke-e3iP1vo$default (Lapp/cash/redwood/protocol/PropertyChange$Companion;IILkotlinx/serialization/json/JsonElement;ILjava/lang/Object;)Lapp/cash/redwood/protocol/PropertyChange; + public final fun invoke-ITsWdOQ (IIILkotlinx/serialization/json/JsonElement;)Lapp/cash/redwood/protocol/PropertyChange; + public static synthetic fun invoke-ITsWdOQ$default (Lapp/cash/redwood/protocol/PropertyChange$Companion;IIILkotlinx/serialization/json/JsonElement;ILjava/lang/Object;)Lapp/cash/redwood/protocol/PropertyChange; public final fun serializer ()Lkotlinx/serialization/KSerializer; } diff --git a/redwood-protocol/api/redwood-protocol.klib.api b/redwood-protocol/api/redwood-protocol.klib.api index bb8f72c667..33bd0be5c2 100644 --- a/redwood-protocol/api/redwood-protocol.klib.api +++ b/redwood-protocol/api/redwood-protocol.klib.api @@ -226,10 +226,12 @@ final class app.cash.redwood.protocol/ModifierElement { // app.cash.redwood.prot final class app.cash.redwood.protocol/PropertyChange : app.cash.redwood.protocol/ValueChange { // app.cash.redwood.protocol/PropertyChange|null[0] final val id // app.cash.redwood.protocol/PropertyChange.id|{}id[0] final fun (): app.cash.redwood.protocol/Id // app.cash.redwood.protocol/PropertyChange.id.|(){}[0] - final val tag // app.cash.redwood.protocol/PropertyChange.tag|{}tag[0] - final fun (): app.cash.redwood.protocol/PropertyTag // app.cash.redwood.protocol/PropertyChange.tag.|(){}[0] + final val propertyTag // app.cash.redwood.protocol/PropertyChange.propertyTag|{}propertyTag[0] + final fun (): app.cash.redwood.protocol/PropertyTag // app.cash.redwood.protocol/PropertyChange.propertyTag.|(){}[0] final val value // app.cash.redwood.protocol/PropertyChange.value|{}value[0] final fun (): kotlinx.serialization.json/JsonElement // app.cash.redwood.protocol/PropertyChange.value.|(){}[0] + final val widgetTag // app.cash.redwood.protocol/PropertyChange.widgetTag|{}widgetTag[0] + final fun (): app.cash.redwood.protocol/WidgetTag // app.cash.redwood.protocol/PropertyChange.widgetTag.|(){}[0] final fun equals(kotlin/Any?): kotlin/Boolean // app.cash.redwood.protocol/PropertyChange.equals|equals(kotlin.Any?){}[0] final fun hashCode(): kotlin/Int // app.cash.redwood.protocol/PropertyChange.hashCode|hashCode(){}[0] @@ -245,7 +247,7 @@ final class app.cash.redwood.protocol/PropertyChange : app.cash.redwood.protocol } final object Companion { // app.cash.redwood.protocol/PropertyChange.Companion|null[0] - final fun invoke(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlinx.serialization.json/JsonElement = ...): app.cash.redwood.protocol/PropertyChange // app.cash.redwood.protocol/PropertyChange.Companion.invoke|invoke(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlinx.serialization.json.JsonElement){}[0] + final fun invoke(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag, app.cash.redwood.protocol/PropertyTag, kotlinx.serialization.json/JsonElement = ...): app.cash.redwood.protocol/PropertyChange // app.cash.redwood.protocol/PropertyChange.Companion.invoke|invoke(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag;app.cash.redwood.protocol.PropertyTag;kotlinx.serialization.json.JsonElement){}[0] final fun serializer(): kotlinx.serialization/KSerializer // app.cash.redwood.protocol/PropertyChange.Companion.serializer|serializer(){}[0] } } diff --git a/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt index a9ed1836de..3766e1fdf7 100644 --- a/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt +++ b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt @@ -97,22 +97,29 @@ public sealed interface ValueChange : Change public class PropertyChange private constructor( @SerialName("id") private val _id: Int, + @SerialName("widget") + private val _widgetTag: Int, @SerialName("tag") private val _tag: Int, public val value: JsonElement = JsonNull, ) : ValueChange { override val id: Id get() = Id(_id) - /** Identifies which property changed on the widget with [id]. */ - public val tag: PropertyTag get() = PropertyTag(_tag) + /** Identifies the widget on which the property changed. */ + public val widgetTag: WidgetTag get() = WidgetTag(_widgetTag) + + /** Identifies which property changed on the widget. */ + public val propertyTag: PropertyTag get() = PropertyTag(_tag) public companion object { public operator fun invoke( id: Id, - /** Identifies which property changed on the widget with [id]. */ - tag: PropertyTag, + /** Identifies the widget on which the property changed. */ + widgetTag: WidgetTag, + /** Identifies which property changed on the widget. */ + propertyTag: PropertyTag, value: JsonElement = JsonNull, - ): PropertyChange = PropertyChange(id.value, tag.value, value) + ): PropertyChange = PropertyChange(id.value, widgetTag.value, propertyTag.value, value) } } diff --git a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt index 120a9e5282..378c0aca5f 100644 --- a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt +++ b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt @@ -84,8 +84,8 @@ class ProtocolTest { ), ), ), - PropertyChange(Id(1), PropertyTag(2), JsonPrimitive("hello")), - PropertyChange(Id(1), PropertyTag(2), JsonNull), + PropertyChange(Id(1), WidgetTag(2), PropertyTag(2), JsonPrimitive("hello")), + PropertyChange(Id(1), WidgetTag(2), PropertyTag(2), JsonNull), ) val json = "" + "[" + @@ -95,8 +95,8 @@ class ProtocolTest { """["remove",{"id":4,"tag":3,"index":2,"count":1}],""" + """["remove",{"id":1,"tag":2,"index":3,"count":4,"removedIds":[5,6,7,8]}],""" + """["modifier",{"id":1,"elements":[[1,{}],[2,3],[3,[]],[4],[5]]}],""" + - """["property",{"id":1,"tag":2,"value":"hello"}],""" + - """["property",{"id":1,"tag":2}]""" + + """["property",{"id":1,"widget":2,"tag":2,"value":"hello"}],""" + + """["property",{"id":1,"widget":2,"tag":2}]""" + "]" assertJsonRoundtrip(ListSerializer(Change.serializer()), changes, json) } diff --git a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/SnapshotChangeListTest.kt b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/SnapshotChangeListTest.kt index be853ddde7..5505ed7219 100644 --- a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/SnapshotChangeListTest.kt +++ b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/SnapshotChangeListTest.kt @@ -32,7 +32,7 @@ class SnapshotChangeListTest { listOf( Create(Id(1), WidgetTag(1)), ModifierChange(Id(1), emptyList()), - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("Hello")), + PropertyChange(Id(1), WidgetTag(1), PropertyTag(1), JsonPrimitive("Hello")), Add(Id.Root, ChildrenTag.Root, Id(1), 0), ), ) @@ -43,7 +43,7 @@ class SnapshotChangeListTest { |[ |{"type":"create","id":1,"tag":1}, |{"type":"modifier","id":1}, - |{"type":"property","id":1,"tag":1,"value":"Hello"}, + |{"type":"property","id":1,"widget":1,"tag":1,"value":"Hello"}, |{"type":"add","id":0,"tag":1,"childId":1,"index":0} |] """.trimMargin().replace("\n", ""), @@ -60,7 +60,7 @@ class SnapshotChangeListTest { Create(Id(1), WidgetTag(1)), ModifierChange(Id(1), emptyList()), Move(Id.Root, ChildrenTag.Root, 1, 2, 3), - PropertyChange(Id(1), PropertyTag(1), JsonPrimitive("Hello")), + PropertyChange(Id(1), WidgetTag(1), PropertyTag(1), JsonPrimitive("Hello")), Add(Id.Root, ChildrenTag.Root, Id(1), 0), Remove(Id.Root, ChildrenTag.Root, 1, 2, listOf(Id(3), Id(4))), ), diff --git a/redwood-testing/src/commonTest/kotlin/app/cash/redwood/testing/ViewTreesTest.kt b/redwood-testing/src/commonTest/kotlin/app/cash/redwood/testing/ViewTreesTest.kt index 6a4c94b1e0..a0f4b6e76d 100644 --- a/redwood-testing/src/commonTest/kotlin/app/cash/redwood/testing/ViewTreesTest.kt +++ b/redwood-testing/src/commonTest/kotlin/app/cash/redwood/testing/ViewTreesTest.kt @@ -86,22 +86,22 @@ class ViewTreesTest { Create(Id(2), WidgetTag(1)), ModifierChange(Id(2), emptyList()), Create(Id(3), WidgetTag(3)), - PropertyChange(Id(3), PropertyTag(1), JsonPrimitive("One Fish")), + PropertyChange(Id(3), WidgetTag(3), PropertyTag(1), JsonPrimitive("One Fish")), ModifierChange(Id(3), emptyList()), Add(Id(2), ChildrenTag(1), Id(3), 0), Create(Id(4), WidgetTag(3)), - PropertyChange(Id(4), PropertyTag(1), JsonPrimitive("Two Fish")), + PropertyChange(Id(4), WidgetTag(3), PropertyTag(1), JsonPrimitive("Two Fish")), ModifierChange(Id(4), emptyList()), Add(Id(2), ChildrenTag(1), Id(4), 1), Add(Id(1), ChildrenTag(1), Id(2), 0), Create(Id(5), WidgetTag(1)), ModifierChange(Id(5), emptyList()), Create(Id(6), WidgetTag(3)), - PropertyChange(Id(6), PropertyTag(1), JsonPrimitive("Red Fish")), + PropertyChange(Id(6), WidgetTag(3), PropertyTag(1), JsonPrimitive("Red Fish")), ModifierChange(Id(6), emptyList()), Add(Id(5), ChildrenTag(1), Id(6), 0), Create(Id(7), WidgetTag(3)), - PropertyChange(Id(7), PropertyTag(1), JsonPrimitive("Blue Fish")), + PropertyChange(Id(7), WidgetTag(3), PropertyTag(1), JsonPrimitive("Blue Fish")), ModifierChange(Id(7), emptyList()), Add(Id(5), ChildrenTag(1), Id(7), 1), Add(Id(1), ChildrenTag(1), Id(5), 1), diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolCodegen.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolCodegen.kt index 90eb1eb393..b768ae71f0 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolCodegen.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolCodegen.kt @@ -38,10 +38,10 @@ internal fun ProtocolSchemaSet.generateFileSpecs(type: ProtocolCodegenType): Lis Guest -> { add(generateProtocolWidgetSystemFactory(this@generateFileSpecs)) for (dependency in all) { - add(generateProtocolWidgetFactory(dependency, host = schema)) - generateProtocolModifierSerializers(dependency, host = schema)?.let { add(it) } + add(generateProtocolWidgetFactory(schema, dependency)) + generateProtocolModifierSerializers(schema, dependency)?.let { add(it) } for (widget in dependency.widgets) { - add(generateProtocolWidget(dependency, widget, host = schema)) + add(generateProtocolWidget(schema, dependency, widget)) } } } @@ -49,9 +49,9 @@ internal fun ProtocolSchemaSet.generateFileSpecs(type: ProtocolCodegenType): Lis Host -> { add(generateProtocolFactory(this@generateFileSpecs)) for (dependency in all) { - generateProtocolModifierImpls(dependency, host = schema)?.let { add(it) } + generateProtocolModifierImpls(schema, dependency)?.let { add(it) } for (widget in dependency.widgets) { - add(generateProtocolNode(dependency, widget, host = schema)) + add(generateProtocolNode(schema, dependency, widget)) } } } diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolGuestGeneration.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolGuestGeneration.kt index 7342a6d658..59bf289cc2 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolGuestGeneration.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolGuestGeneration.kt @@ -94,7 +94,7 @@ internal fun generateProtocolWidgetSystemFactory( .apply { val arguments = buildList { for (dependency in schemaSet.all) { - add(CodeBlock.of("%N = %T(guestAdapter, mismatchHandler)", dependency.type.flatName, dependency.protocolWidgetFactoryType(schema))) + add(CodeBlock.of("%N = %T(guestAdapter, mismatchHandler)", dependency.type.flatName, schema.protocolWidgetFactoryType(dependency))) } } addStatement("return %T(\n%L)", schema.getWidgetSystemType(), arguments.joinToCode(separator = ",\n")) @@ -130,16 +130,16 @@ internal class ProtocolExampleWidgetFactory( } */ internal fun generateProtocolWidgetFactory( - schema: ProtocolSchema, - host: ProtocolSchema = schema, + generatingSchema: ProtocolSchema, + widgetSchema: ProtocolSchema, ): FileSpec { - val type = schema.protocolWidgetFactoryType(host) + val type = generatingSchema.protocolWidgetFactoryType(widgetSchema) return buildFileSpec(type) { addAnnotation(suppressDeprecations) addType( TypeSpec.classBuilder(type) .addModifiers(INTERNAL) - .addSuperinterface(schema.getWidgetFactoryType().parameterizedBy(protocolViewType)) + .addSuperinterface(widgetSchema.getWidgetFactoryType().parameterizedBy(protocolViewType)) .addAnnotation(Redwood.RedwoodCodegenApi) .primaryConstructor( FunSpec.constructorBuilder() @@ -158,26 +158,26 @@ internal fun generateProtocolWidgetFactory( .build(), ) .apply { - for (widget in schema.widgets) { + for (widget in widgetSchema.widgets) { addFunction( FunSpec.builder(widget.type.flatName) .addModifiers(OVERRIDE) - .returns(schema.widgetType(widget).parameterizedBy(protocolViewType)) + .returns(widgetSchema.widgetType(widget).parameterizedBy(protocolViewType)) .addStatement( "val widget = %T(guestAdapter, mismatchHandler)", - schema.protocolWidgetType(widget, host), + generatingSchema.protocolWidgetType(widget, widgetSchema), ) .addStatement("guestAdapter.appendCreate(widget.id, widget.tag)") .addStatement("return widget") .build(), ) } - for (modifier in schema.unscopedModifiers) { + for (modifier in widgetSchema.unscopedModifiers) { addFunction( FunSpec.builder(modifier.type.flatName) .addModifiers(OVERRIDE) .addParameter("value", protocolViewType) - .addParameter("modifier", schema.modifierType(modifier)) + .addParameter("modifier", widgetSchema.modifierType(modifier)) .build(), ) } @@ -207,13 +207,13 @@ internal class ProtocolButton( } override fun text(text: String?) { - this.guestAdapter.appendPropertyChange(this.id, PropertyTag(1), serializer_0, text) + this.guestAdapter.appendPropertyChange(this.id, WidgetTag(3), PropertyTag(1), serializer_0, text) } override fun onClick(onClick: (() -> Unit)?) { val onClickSet = onClick != null if (onClickSet != (this.onClick != null)) { - guestAdapter.appendPropertyChange(this.id, PropertyTag(3), onClickSet) + guestAdapter.appendPropertyChange(this.id, WidgetTag(3), PropertyTag(3), onClickSet) } this.onClick = onClick } @@ -230,12 +230,12 @@ internal class ProtocolButton( } */ internal fun generateProtocolWidget( - schema: ProtocolSchema, + generatingSchema: ProtocolSchema, + widgetSchema: ProtocolSchema, widget: ProtocolWidget, - host: ProtocolSchema = schema, ): FileSpec { - val type = schema.protocolWidgetType(widget, host) - val widgetName = schema.widgetType(widget) + val type = generatingSchema.protocolWidgetType(widget, widgetSchema) + val widgetName = widgetSchema.widgetType(widget) return buildFileSpec(type) { addAnnotation(suppressDeprecations) addType( @@ -290,7 +290,9 @@ internal fun generateProtocolWidget( // Work around https://github.com/Kotlin/kotlinx.serialization/issues/2713. if (traitTypeName == U_INT) { addStatement( - "this.guestAdapter.appendPropertyChange(this.id, %T(%L), %N)", + "this.guestAdapter.appendPropertyChange(this.id, %T(%L), %T(%L), %N)", + Protocol.WidgetTag, + widget.tag, Protocol.PropertyTag, trait.tag, trait.name, @@ -300,7 +302,9 @@ internal fun generateProtocolWidget( nextSerializerId++ } addStatement( - "this.guestAdapter.appendPropertyChange(this.id, %T(%L), serializer_%L, %N)", + "this.guestAdapter.appendPropertyChange(this.id, %T(%L), %T(%L), serializer_%L, %N)", + Protocol.WidgetTag, + widget.tag, Protocol.PropertyTag, trait.tag, serializerId, @@ -342,7 +346,9 @@ internal fun generateProtocolWidget( "true" } addStatement( - "this.guestAdapter.appendPropertyChange(this.id, %T(%L), %L)", + "this.guestAdapter.appendPropertyChange(this.id, %T(%L), %T(%L), %L)", + Protocol.WidgetTag, + widget.tag, Protocol.PropertyTag, trait.tag, newValue, @@ -489,18 +495,18 @@ internal val GrowTagAndSerializer: Pair } */ internal fun generateProtocolModifierSerializers( - schema: ProtocolSchema, - host: ProtocolSchema, + generatingSchema: ProtocolSchema, + modifierSchema: ProtocolSchema, ): FileSpec? { - if (schema.modifiers.isEmpty()) { + if (modifierSchema.modifiers.isEmpty()) { return null } - return buildFileSpec(schema.guestProtocolPackage(host), "modifierSerializers") { + return buildFileSpec(generatingSchema.guestProtocolPackage(modifierSchema), "modifierSerializers") { addAnnotation(suppressDeprecations) - for (modifier in schema.modifiers) { - val serializerTagAndSerializer = schema.modifierTagAndSerializer(modifier, host) - val modifierType = schema.modifierType(modifier) + for (modifier in modifierSchema.modifiers) { + val serializerTagAndSerializer = generatingSchema.modifierTagAndSerializer(modifier, modifierSchema) + val modifierType = modifierSchema.modifierType(modifier) var nextSerializerId = 0 val serializerIds = mutableMapOf() @@ -716,12 +722,12 @@ internal fun modifierTagAndSerializationStrategy( .returns(returnType) .addCode("return when·(element)·{⇥\n") .apply { - for ((localSchema, modifier) in allModifiers) { - val modifierType = localSchema.modifierType(modifier) + for ((modifierSchema, modifier) in allModifiers) { + val modifierType = modifierSchema.modifierType(modifier) addStatement( "is %T -> %M", modifierType, - localSchema.modifierTagAndSerializer(modifier, schema), + schema.modifierTagAndSerializer(modifier, modifierSchema), ) } } @@ -730,14 +736,14 @@ internal fun modifierTagAndSerializationStrategy( .build() } -private fun Schema.protocolWidgetFactoryType(host: Schema): ClassName { - return ClassName(guestProtocolPackage(host), "Protocol${type.flatName}WidgetFactory") +private fun Schema.protocolWidgetFactoryType(otherSchema: Schema): ClassName { + return ClassName(guestProtocolPackage(otherSchema), "Protocol${type.flatName}WidgetFactory") } -private fun Schema.protocolWidgetType(widget: Widget, host: Schema): ClassName { - return ClassName(guestProtocolPackage(host), "Protocol${widget.type.flatName}") +private fun Schema.protocolWidgetType(widget: Widget, widgetSchema: Schema): ClassName { + return ClassName(guestProtocolPackage(widgetSchema), "Protocol${widget.type.flatName}") } -private fun Schema.modifierTagAndSerializer(modifier: Modifier, host: Schema): MemberName { - return MemberName(guestProtocolPackage(host), modifier.type.flatName + "TagAndSerializer") +private fun Schema.modifierTagAndSerializer(modifier: Modifier, modifierSchema: Schema): MemberName { + return MemberName(guestProtocolPackage(modifierSchema), modifier.type.flatName + "TagAndSerializer") } diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt index 203030fb53..0266d3e57d 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt @@ -17,6 +17,7 @@ package app.cash.redwood.tooling.codegen import app.cash.redwood.tooling.codegen.Protocol.Id import app.cash.redwood.tooling.codegen.Protocol.WidgetTag +import app.cash.redwood.tooling.schema.ProtocolModifier import app.cash.redwood.tooling.schema.ProtocolSchema import app.cash.redwood.tooling.schema.ProtocolSchemaSet import app.cash.redwood.tooling.schema.ProtocolWidget @@ -51,27 +52,19 @@ public class ExampleProtocolFactory( override val widgetSystem: ExampleWidgetSystem, private val json: Json = Json.Default, private val mismatchHandler: ProtocolMismatchHandler = ProtocolMismatchHandler.Throwing, -) : GeneratedProtocolFactory { - private val childrenTags: IntObjectMap = MutableIntObjectMap(4).apply { - put(1, intArrayOf(1)) - put(3, intArrayOf(1)) - put(1_000_001, intArrayOf(1, 2)) - put(1_000_002, intArrayOf(1)) +) : GeneratedHostProtocol { + private val widgets: IntObjectMap = + MutableIntObjectMap(4).apply { + put(1, ButtonHostProtocol(widgetSystem, json, mismatchHandler)) + put(3, TextHostProtocol(widgetSystem, json, mismatchHandler)) + put(1_000_001, RowHostProtocol(widgetSystem, json, mismatchHandler)) + put(1_000_002, ColumnHostProtocol(widgetSystem, json, mismatchHandler)) } - override fun widgetChildren(tag: WidgetTag): IntArray? { - return childrenTags[tag.value] - } - - override fun createNode(id: Id, tag: WidgetTag): ProtocolNode? = when (tag.value) { - 1 -> TextProtocolNode(id, delegate.Sunspot.Text(), json, mismatchHandler) - 2 -> ButtonProtocolNode(id, delegate.Sunspot.Button(), json, mismatchHandler) - 1_000_001 -> RedwoodLayoutRowProtocolNode(id, delegate.RedwoodLayout.Row(), json, mismatchHandler) - 1_000_002 -> RedwoodLayoutColumnProtocolNode(id, delegate.RedwoodLayout.Column(), json, mismatchHandler) - else -> { - mismatchHandler.onUnknownWidget(tag) - null - } + override fun widget(tag: WidgetTag): WidgetHostProtocol? { + widgets[tag.value]?.let { return it } + mismatchHandler.onUnknownWidget(tag) + return null } override fun createModifier(element: ModifierElement): Modifier { @@ -97,7 +90,7 @@ internal fun generateProtocolFactory( addType( TypeSpec.classBuilder(type) .addTypeVariable(typeVariableW) - .addSuperinterface(ProtocolHost.GeneratedProtocolFactory.parameterizedBy(typeVariableW)) + .addSuperinterface(ProtocolHost.GeneratedProtocolHost.parameterizedBy(typeVariableW)) .optIn(Stdlib.ExperimentalObjCName, Redwood.RedwoodCodegenApi) .addAnnotation( AnnotationSpec.builder(Stdlib.ObjCName) @@ -137,32 +130,31 @@ internal fun generateProtocolFactory( ) .addProperty( PropertySpec.builder( - "childrenTags", - AndroidxCollection.IntObjectMap.parameterizedBy(INT_ARRAY), + "widgets", + AndroidxCollection.IntObjectMap + .parameterizedBy( + ProtocolHost.WidgetHostProtocol + .parameterizedBy(typeVariableW), + ), PRIVATE, ) .initializer( buildCodeBlock { - val allParentWidgets = schemaSet.all - .flatMap { it.widgets } - .filter { it.traits.any { it is ProtocolChildren } } - .sortedBy { it.tag } + val allWidgets = schemaSet.all + .flatMap { schema -> schema.widgets.map { it to schema } } + .sortedBy { it.first.tag } beginControlFlow( "%T<%T>(%L).apply", AndroidxCollection.MutableIntObjectMap, - INT_ARRAY, - allParentWidgets.size, + ProtocolHost.WidgetHostProtocol.parameterizedBy(typeVariableW), + allWidgets.size, ) - for (parentWidget in allParentWidgets) { + for ((widget, widgetSchema) in allWidgets) { addStatement( - "put(%L, %M(%L))", - parentWidget.tag, - MemberName("kotlin", "intArrayOf"), - parentWidget.traits - .filterIsInstance() - .sortedBy { it.tag } - .joinToCode { CodeBlock.of("%L", it.tag) }, + "put(%L, %T(widgetSystem, json, mismatchHandler))", + widget.tag, + schema.widgetHostProtocolType(widget, widgetSchema), ) } endControlFlow() @@ -171,41 +163,16 @@ internal fun generateProtocolFactory( .build(), ) .addFunction( - FunSpec.builder("widgetChildren") - .addModifiers(OVERRIDE) - .addParameter("tag", WidgetTag) - .returns(INT_ARRAY.copy(nullable = true)) - .addStatement("return childrenTags[tag.value]") - .build(), - ) - .addFunction( - FunSpec.builder("createNode") + FunSpec.builder("widget") .addModifiers(OVERRIDE) - .addParameter("id", Id) .addParameter("tag", WidgetTag) .returns( - ProtocolHost.ProtocolNode.parameterizedBy(typeVariableW) + ProtocolHost.WidgetHostProtocol.parameterizedBy(typeVariableW) .copy(nullable = true), ) - .beginControlFlow("return when (tag.value)") - .apply { - for (dependency in schemaSet.all.sortedBy { it.widgets.firstOrNull()?.tag ?: 0 }) { - for (widget in dependency.widgets.sortedBy { it.tag }) { - addStatement( - "%L -> %T(id, widgetSystem.%N.%N(), json, mismatchHandler)", - widget.tag, - dependency.protocolNodeType(widget, schema), - dependency.type.flatName, - widget.type.flatName, - ) - } - } - } - .beginControlFlow("else ->") + .addStatement("widgets[tag.value]?.let { return it }") .addStatement("mismatchHandler.onUnknownWidget(tag)") - .addStatement("null") - .endControlFlow() - .endControlFlow() + .addStatement("return null") .build(), ) .addFunction( @@ -218,8 +185,8 @@ internal fun generateProtocolFactory( if (modifiers.isNotEmpty()) { beginControlFlow("val serializer = when (element.tag.value)") val host = schemaSet.schema - for ((localSchema, modifier) in modifiers) { - val typeName = ClassName(localSchema.hostProtocolPackage(host), modifier.type.flatName + "Impl") + for ((modifierSchema, modifier) in modifiers) { + val typeName = schema.modifierImplType(modifier, modifierSchema) if (modifier.properties.isEmpty()) { addStatement("%L -> return %T", modifier.tag, typeName) } else { @@ -246,7 +213,21 @@ internal fun generateProtocolFactory( } /* -internal class ProtocolButton( +internal class ButtonHostProtocol( + private val widgetSystem: EmojiSearchWidgetSystem, + private val json: Json, + private val mismatchHandler: ProtocolMismatchHandler, +) : WidgetHostProtocol { + override val childrenTags: IntArray? + get() = null + + override fun createNode(id: Id): ProtocolNode { + val widget = widgetSystem.RedwoodLayout.Box() + return BoxProtocolNode(id, widget, json, mismatchHandler) + } +} + +private class ButtonProtocolNode( id: Id, widget: Button, private val json: Json, @@ -262,7 +243,7 @@ internal class ProtocolButton( public override fun apply(change: PropertyChange, eventSink: UiEventSink): Unit { val widget = _widget ?: error("detached") - when (change.tag.value) { + when (change.propertyTag.value) { 1 -> widget.text(json.decodeFromJsonElement(serializer_0, change.value)) 2 -> widget.enabled(json.decodeFromJsonElement(serializer_1, change.value)) 3 -> { @@ -273,7 +254,7 @@ internal class ProtocolButton( } widget.onClick(onClick) } - else -> mismatchHandler.onUnknownProperty(WidgetTag(12), change.tag) + else -> mismatchHandler.onUnknownProperty(WidgetTag(12), change.propertyTag) } } @@ -294,21 +275,84 @@ internal class ProtocolButton( } */ internal fun generateProtocolNode( - schema: ProtocolSchema, + generatingSchema: ProtocolSchema, + widgetSchema: ProtocolSchema, widget: ProtocolWidget, - host: ProtocolSchema = schema, ): FileSpec { - val type = schema.protocolNodeType(widget, host) - val widgetType = schema.widgetType(widget).parameterizedBy(typeVariableW) - val protocolType = ProtocolHost.ProtocolNode.parameterizedBy(typeVariableW) + val widgetType = widgetSchema.widgetType(widget).parameterizedBy(typeVariableW) + val widgetSystem = generatingSchema.getWidgetSystemType().parameterizedBy(typeVariableW) + val widgetProtocolType = generatingSchema.widgetHostProtocolType(widget, widgetSchema) + val widgetNodeType = widgetProtocolType.peerClass("${widget.type.flatName}ProtocolNode") + val (childrens, properties) = widget.traits.partition { it is ProtocolChildren } - return buildFileSpec(type) { + + return buildFileSpec(widgetProtocolType) { addAnnotation(suppressDeprecations) addType( - TypeSpec.classBuilder(type) + TypeSpec.classBuilder(widgetProtocolType) .addModifiers(INTERNAL) .addTypeVariable(typeVariableW) - .superclass(protocolType) + .addSuperinterface(ProtocolHost.WidgetHostProtocol.parameterizedBy(typeVariableW)) + .addAnnotation(Redwood.RedwoodCodegenApi) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("widgetSystem", widgetSystem) + .addParameter("json", KotlinxSerialization.Json) + .addParameter("mismatchHandler", ProtocolHost.ProtocolMismatchHandler) + .build(), + ) + .addProperty( + PropertySpec.builder("widgetSystem", widgetSystem, PRIVATE) + .initializer("widgetSystem") + .build(), + ) + .addProperty( + PropertySpec.builder("json", KotlinxSerialization.Json, PRIVATE) + .initializer("json") + .build(), + ) + .addProperty( + PropertySpec.builder("mismatchHandler", ProtocolHost.ProtocolMismatchHandler, PRIVATE) + .initializer("mismatchHandler") + .build(), + ) + .addProperty( + PropertySpec.builder("childrenTags", INT_ARRAY.copy(nullable = true), OVERRIDE) + .getter( + FunSpec.getterBuilder() + .apply { + val childrens = widget.traits.filterIsInstance() + if (childrens.isEmpty()) { + addStatement("return null") + } else { + addStatement( + "return %M(%L)", + MemberName("kotlin", "intArrayOf"), + childrens.joinToCode { CodeBlock.of("%L", it.tag) }, + ) + } + } + .build(), + ) + .build(), + ) + .addFunction( + FunSpec.builder("createNode") + .addModifiers(OVERRIDE) + .addParameter("id", Protocol.Id) + .returns(ProtocolHost.ProtocolNode.parameterizedBy(typeVariableW)) + .addStatement("val widget = widgetSystem.%L.%L()", widgetSchema.type.flatName, widget.type.flatName) + .addStatement("return %T(id, widget, json, mismatchHandler)", widgetNodeType) + .build(), + ) + .build(), + + ) + addType( + TypeSpec.classBuilder(widgetNodeType) + .addModifiers(PRIVATE) + .addTypeVariable(typeVariableW) + .superclass(ProtocolHost.ProtocolNode.parameterizedBy(typeVariableW)) .addAnnotation(Redwood.RedwoodCodegenApi) .primaryConstructor( FunSpec.constructorBuilder() @@ -328,6 +372,15 @@ internal fun generateProtocolNode( ) .build(), ) + .addProperty( + PropertySpec.builder("widgetName", STRING, OVERRIDE) + .getter( + FunSpec.getterBuilder() + .addStatement("return %S", widget.type.flatName) + .build(), + ) + .build(), + ) .addProperty( PropertySpec.builder("_widget", widgetType.copy(nullable = true), PRIVATE) .mutable(true) @@ -372,7 +425,7 @@ internal fun generateProtocolNode( if (properties.isNotEmpty()) { addStatement("val widget = _widget ?: error(%S)", "detached") } - beginControlFlow("when (change.tag.value)") + beginControlFlow("when (change.propertyTag.value)") for (trait in properties) { when (trait) { is ProtocolProperty -> { @@ -447,7 +500,7 @@ internal fun generateProtocolNode( } } .addStatement( - "else -> mismatchHandler.onUnknownProperty(%T(%L), change.tag)", + "else -> mismatchHandler.onUnknownProperty(%T(%L), change.propertyTag)", Protocol.WidgetTag, widget.tag, ) @@ -526,22 +579,6 @@ internal fun generateProtocolNode( .addStatement("_widget = null") .build(), ) - .addFunction( - FunSpec.builder("toString") - .addModifiers(OVERRIDE) - .returns(STRING) - // This explicit string builder usage allows sharing of strings in dex. - // See https://jakewharton.com/the-economics-of-generated-code/#string-duplication. - .beginControlFlow("return buildString") - .addStatement("append(%S)", type.simpleName) - .addStatement("""append("(id=")""") - .addStatement("append(id.value)") - .addStatement("""append(", tag=")""") - .addStatement("append(%L)", widget.tag) - .addStatement("append(')')") - .endControlFlow() - .build(), - ) .build(), ) } @@ -665,17 +702,18 @@ private fun addConstructorParameterAndProperty( } internal fun generateProtocolModifierImpls( - schema: ProtocolSchema, - host: ProtocolSchema = schema, + generatingSchema: ProtocolSchema, + modifierSchema: ProtocolSchema, ): FileSpec? { - if (schema.modifiers.isEmpty()) { + if (modifierSchema.modifiers.isEmpty()) { return null } - return buildFileSpec(schema.hostProtocolPackage(host), "modifierImpls") { + val targetPackage = generatingSchema.hostProtocolPackage(modifierSchema) + return buildFileSpec(targetPackage, "modifierImpls") { addAnnotation(suppressDeprecations) - for (modifier in schema.modifiers) { - val typeName = ClassName(schema.hostProtocolPackage(host), modifier.type.flatName + "Impl") + for (modifier in modifierSchema.modifiers) { + val typeName = generatingSchema.modifierImplType(modifier, modifierSchema) val typeBuilder = if (modifier.properties.isEmpty()) { TypeSpec.objectBuilder(typeName) } else { @@ -706,8 +744,8 @@ internal fun generateProtocolModifierImpls( addType( typeBuilder .addModifiers(INTERNAL) - .addSuperinterface(schema.modifierType(modifier)) - .addFunction(modifierEquals(schema, modifier)) + .addSuperinterface(modifierSchema.modifierType(modifier)) + .addFunction(modifierEquals(modifierSchema, modifier)) .addFunction(modifierHashCode(modifier)) .addFunction(modifierToString(modifier)) .build(), @@ -716,6 +754,10 @@ internal fun generateProtocolModifierImpls( } } -private fun Schema.protocolNodeType(widget: Widget, host: Schema): ClassName { - return ClassName(hostProtocolPackage(host), "Protocol${widget.type.flatName}") +private fun Schema.widgetHostProtocolType(widget: Widget, widgetSchema: ProtocolSchema): ClassName { + return ClassName(hostProtocolPackage(widgetSchema), "${widget.type.flatName}HostProtocol") +} + +private fun Schema.modifierImplType(modifier: ProtocolModifier, modifierSchema: ProtocolSchema): ClassName { + return ClassName(hostProtocolPackage(modifierSchema), "${modifier.type.flatName}Impl") } diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/sharedHelpers.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/sharedHelpers.kt index 0c1965a92a..2206855be2 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/sharedHelpers.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/sharedHelpers.kt @@ -72,11 +72,11 @@ internal fun Schema.modifierPackage() = type.names[0] + ".modifier" internal fun Schema.testingPackage() = type.names[0] + ".testing" internal fun Schema.widgetPackage() = type.names[0] + ".widget" -internal fun Schema.guestProtocolPackage(host: Schema? = null) = protocolPackage("guest", host) -internal fun Schema.hostProtocolPackage(host: Schema? = null) = protocolPackage("host", host) -private fun Schema.protocolPackage(name: String, host: Schema?): String { - val base = (host ?: this).type.names[0] + ".protocol." + name - return if (host != null) "$base.${type.flatName.lowercase()}" else base +internal fun Schema.guestProtocolPackage(otherSchema: Schema? = null) = protocolPackage("guest", otherSchema) +internal fun Schema.hostProtocolPackage(otherSchema: Schema? = null) = protocolPackage("host", otherSchema) +private fun Schema.protocolPackage(name: String, otherSchema: Schema?): String { + val base = type.names[0] + ".protocol." + name + return if (otherSchema != null) "$base.${otherSchema.type.flatName.lowercase()}" else base } internal fun Schema.widgetType(widget: Widget): ClassName { diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/testingGeneration.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/testingGeneration.kt index 446924c431..a601b7afab 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/testingGeneration.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/testingGeneration.kt @@ -122,7 +122,6 @@ internal fun generateTester(schemaSet: SchemaSet): FileSpec { } /* -@RedwoodCodegenApi public class EmojiSearchTestingWidgetFactory : EmojiSearchWidgetFactory { public override fun Text(): Text = MutableText() public override fun Button(): Button = MutableButton() @@ -135,7 +134,6 @@ internal fun generateMutableWidgetFactory(schema: Schema): FileSpec { addType( TypeSpec.classBuilder(mutableWidgetFactoryType) .addSuperinterface(schema.getWidgetFactoryType().parameterizedBy(RedwoodTesting.WidgetValue)) - .addAnnotation(Redwood.RedwoodCodegenApi) .apply { for (widget in schema.widgets) { addFunction( diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt index b85ff8a231..f80a52342f 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt @@ -54,9 +54,10 @@ internal object ProtocolHost { ClassName("app.cash.redwood.protocol.host", "ProtocolMismatchHandler") val ProtocolNode = ClassName("app.cash.redwood.protocol.host", "ProtocolNode") val ProtocolChildren = ClassName("app.cash.redwood.protocol.host", "ProtocolChildren") - val GeneratedProtocolFactory = ClassName("app.cash.redwood.protocol.host", "GeneratedProtocolFactory") + val GeneratedProtocolHost = ClassName("app.cash.redwood.protocol.host", "GeneratedHostProtocol") val UiEvent = ClassName("app.cash.redwood.protocol.host", "UiEvent") val UiEventSink = ClassName("app.cash.redwood.protocol.host", "UiEventSink") + val WidgetHostProtocol = ClassName("app.cash.redwood.protocol.host", "WidgetHostProtocol") } internal object Redwood { diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/widgetGeneration.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/widgetGeneration.kt index 0d6ca80bf7..3091d31b22 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/widgetGeneration.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/widgetGeneration.kt @@ -170,6 +170,8 @@ internal fun generateWidgetSystem(schemaSet: SchemaSet): FileSpec { .addProperty(schema.type.flatName, schema.getWidgetFactoryType().parameterizedBy(typeVariableW)) .addType( TypeSpec.companionObjectBuilder() + // Needed because https://github.com/Kotlin/binary-compatibility-validator/issues/91 + .addAnnotation(Redwood.RedwoodCodegenApi) .addFunction( FunSpec.builder("apply") .addTypeVariable(typeVariableW) diff --git a/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/ProtocolGuestGenerationTest.kt b/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/ProtocolGuestGenerationTest.kt index a5b6e9ff04..e3fcc8b5bb 100644 --- a/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/ProtocolGuestGenerationTest.kt +++ b/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/ProtocolGuestGenerationTest.kt @@ -40,7 +40,7 @@ class ProtocolGuestGenerationTest { @Test fun `id property does not collide`() { val schema = parseTestSchema(IdPropertyNameCollisionSchema::class).schema - val fileSpec = generateProtocolWidget(schema, schema.widgets.single()) + val fileSpec = generateProtocolWidget(schema, schema, schema.widgets.single()) assertThat(fileSpec.toString()).contains( """ | override fun id(id: String) { diff --git a/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/WidgetProtocolGenerationTest.kt b/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/WidgetProtocolGenerationTest.kt index 0f3b5decdd..ec7d5afdad 100644 --- a/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/WidgetProtocolGenerationTest.kt +++ b/redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/WidgetProtocolGenerationTest.kt @@ -15,50 +15,14 @@ */ package app.cash.redwood.tooling.codegen -import app.cash.redwood.schema.Property -import app.cash.redwood.schema.Schema -import app.cash.redwood.schema.Widget import app.cash.redwood.tooling.schema.ProtocolSchemaSet -import app.cash.redwood.tooling.schema.parseTestSchema import assertk.all import assertk.assertThat import assertk.assertions.contains -import assertk.assertions.containsMatch import com.example.redwood.testapp.TestSchema -import kotlin.text.RegexOption.MULTILINE import org.junit.Test class WidgetProtocolGenerationTest { - @Schema( - [ - Node12::class, - Node1::class, - Node3::class, - Node2::class, - ], - ) - interface SortedByTagSchema - - @Widget(1) - data class Node1(@Property(1) val text: String) - - @Widget(2) - data class Node2(@Property(1) val text: String) - - @Widget(3) - data class Node3(@Property(1) val text: String) - - @Widget(12) - data class Node12(@Property(1) val text: String) - - @Test fun `names are sorted by their node tags`() { - val schema = parseTestSchema(SortedByTagSchema::class) - - val fileSpec = generateProtocolFactory(schema) - assertThat(fileSpec.toString()) - .containsMatch(Regex("1 ->[^2]+2 ->[^3]+3 ->[^1]+12 ->", MULTILINE)) - } - @Test fun `dependency layout modifier are included in serialization`() { val schema = ProtocolSchemaSet.load(TestSchema::class) diff --git a/redwood-treehouse-guest/src/jsMain/kotlin/app/cash/redwood/treehouse/ProtocolBridgeJs.kt b/redwood-treehouse-guest/src/jsMain/kotlin/app/cash/redwood/treehouse/ProtocolBridgeJs.kt index e7f96d5399..f329d3ff81 100644 --- a/redwood-treehouse-guest/src/jsMain/kotlin/app/cash/redwood/treehouse/ProtocolBridgeJs.kt +++ b/redwood-treehouse-guest/src/jsMain/kotlin/app/cash/redwood/treehouse/ProtocolBridgeJs.kt @@ -108,32 +108,42 @@ internal class FastGuestProtocolAdapter( override fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, serializer: KSerializer, value: T, ) { val id = id - val tag = tag + val widget = widgetTag + val tag = propertyTag val encodedValue = value?.let { json.encodeToDynamic(serializer, it) } - changes.push(js("""["property",{"id":id,"tag":tag,"value":encodedValue}]""")) + changes.push(js("""["property",{"id":id,"widget":widget,"tag":tag,"value":encodedValue}]""")) } override fun appendPropertyChange( id: Id, - tag: PropertyTag, + widgetTag: WidgetTag, + propertyTag: PropertyTag, value: Boolean, ) { val id = id - val tag = tag + val widget = widgetTag + val tag = propertyTag val value = value - changes.push(js("""["property",{"id":id,"tag":tag,"value":value}]""")) + changes.push(js("""["property",{"id":id,"widget":widget,"tag":tag,"value":value}]""")) } - override fun appendPropertyChange(id: Id, tag: PropertyTag, value: UInt) { + override fun appendPropertyChange( + id: Id, + widgetTag: WidgetTag, + propertyTag: PropertyTag, + value: UInt, + ) { val id = id - val tag = tag + val widget = widgetTag + val tag = propertyTag val value = value.toDouble() - changes.push(js("""["property",{"id":id,"tag":tag,"value":value}]""")) + changes.push(js("""["property",{"id":id,"widget":widget,"tag":tag,"value":value}]""")) } override fun appendModifierChange(id: Id, value: Modifier) { @@ -200,8 +210,13 @@ internal class FastGuestProtocolAdapter( val tag = tag val index = index val count = count - val removedIds = Json.encodeToDynamic(removedIds) - changes.push(js("""["remove",{"id":id,"tag":tag,"index":index,"count":count,"removedIds":removedIds}]""")) + + val removedIdsArray = js("[]") + for (i in removedIds.indices) { + removedIdsArray.push(removedIds[i].value) + } + + changes.push(js("""["remove",{"id":id,"tag":tag,"index":index,"count":count,"removedIds":removedIdsArray}]""")) } override fun emitChanges() { diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNodeFactory.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeHostProtocol.kt similarity index 66% rename from redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNodeFactory.kt rename to redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeHostProtocol.kt index 2ff5792bf4..913cacd90d 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNodeFactory.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeHostProtocol.kt @@ -20,14 +20,22 @@ import app.cash.redwood.RedwoodCodegenApi import app.cash.redwood.protocol.Id import app.cash.redwood.protocol.ModifierElement import app.cash.redwood.protocol.WidgetTag -import app.cash.redwood.protocol.host.GeneratedProtocolFactory +import app.cash.redwood.protocol.host.GeneratedHostProtocol import app.cash.redwood.protocol.host.ProtocolNode +import app.cash.redwood.protocol.host.WidgetHostProtocol import app.cash.redwood.widget.WidgetSystem @OptIn(RedwoodCodegenApi::class) -internal class FakeProtocolNodeFactory : GeneratedProtocolFactory { +internal class FakeHostProtocol : GeneratedHostProtocol { override val widgetSystem: WidgetSystem = FakeWidgetSystem() - override fun createNode(id: Id, tag: WidgetTag): ProtocolNode = FakeProtocolNode(id, tag) + override fun widget(tag: WidgetTag): WidgetHostProtocol? = FakeWidgetHostProtocol(tag) override fun createModifier(element: ModifierElement): Modifier = Modifier - override fun widgetChildren(tag: WidgetTag): IntArray? = null +} + +@OptIn(RedwoodCodegenApi::class) +private class FakeWidgetHostProtocol( + private val tag: WidgetTag, +) : WidgetHostProtocol { + override fun createNode(id: Id): ProtocolNode = FakeProtocolNode(id, tag) + override val childrenTags: IntArray? get() = null } diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt index ac89c94601..c5b69d0581 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt @@ -35,6 +35,8 @@ internal class FakeProtocolNode( id: Id, override val widgetTag: WidgetTag, ) : ProtocolNode(id) { + override val widgetName: String get() = "FakeProtocolNode" + override val widget = FakeWidget() override fun apply(change: PropertyChange, eventSink: UiEventSink) { @@ -50,6 +52,4 @@ internal class FakeProtocolNode( override fun detach() { } - - override fun toString() = "FakeProtocolNode(id=${id.value}, tag=${widgetTag.value})" } diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt index e68b3351bb..4b663d73d7 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt @@ -22,5 +22,5 @@ internal class FakeTreehouseWidgetSystem : TreehouseView.WidgetSystem (Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V - public synthetic fun (Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun detach ()V public fun getWidgets ()Ljava/util/List; public fun insert (ILapp/cash/redwood/widget/Widget;)V @@ -100,9 +100,6 @@ public abstract interface class app/cash/redwood/widget/Widget$Children { public abstract fun remove (II)V } -public abstract interface class app/cash/redwood/widget/WidgetFactoryOwner { -} - public abstract interface class app/cash/redwood/widget/WidgetSystem { public abstract fun apply (Ljava/lang/Object;Lapp/cash/redwood/Modifier$UnscopedElement;)V } diff --git a/redwood-widget/api/jvm/redwood-widget.api b/redwood-widget/api/jvm/redwood-widget.api index d16906742e..d80f32cd6f 100644 --- a/redwood-widget/api/jvm/redwood-widget.api +++ b/redwood-widget/api/jvm/redwood-widget.api @@ -78,9 +78,6 @@ public abstract interface class app/cash/redwood/widget/Widget$Children { public abstract fun remove (II)V } -public abstract interface class app/cash/redwood/widget/WidgetFactoryOwner { -} - public abstract interface class app/cash/redwood/widget/WidgetSystem { public abstract fun apply (Ljava/lang/Object;Lapp/cash/redwood/Modifier$UnscopedElement;)V } diff --git a/redwood-widget/api/redwood-widget.klib.api b/redwood-widget/api/redwood-widget.klib.api index 3c6403497a..da76ff1e28 100644 --- a/redwood-widget/api/redwood-widget.klib.api +++ b/redwood-widget/api/redwood-widget.klib.api @@ -40,8 +40,6 @@ abstract interface <#A: kotlin/Any> app.cash.redwood.widget/Widget { // app.cash } } -abstract interface <#A: kotlin/Any> app.cash.redwood.widget/WidgetFactoryOwner // app.cash.redwood.widget/WidgetFactoryOwner|null[0] - abstract interface <#A: kotlin/Any> app.cash.redwood.widget/WidgetSystem { // app.cash.redwood.widget/WidgetSystem|null[0] abstract fun apply(#A, app.cash.redwood/Modifier.UnscopedElement) // app.cash.redwood.widget/WidgetSystem.apply|apply(1:0;app.cash.redwood.Modifier.UnscopedElement){}[0] } @@ -111,7 +109,7 @@ abstract interface <#A: kotlin/Any> app.cash.redwood.widget/ResizableWidget : ap // Targets: [ios] final class app.cash.redwood.widget/UIViewChildren : app.cash.redwood.widget/Widget.Children { // app.cash.redwood.widget/UIViewChildren|null[0] - constructor (platform.UIKit/UIView, kotlin/Function4, platform.UIKit/UIView, app.cash.redwood/Modifier, kotlin/Int, kotlin/Unit> = ..., kotlin/Function2> = ..., kotlin/Function2 = ..., kotlin/Function0 = ...) // app.cash.redwood.widget/UIViewChildren.|(platform.UIKit.UIView;kotlin.Function4,platform.UIKit.UIView,app.cash.redwood.Modifier,kotlin.Int,kotlin.Unit>;kotlin.Function2>;kotlin.Function2;kotlin.Function0){}[0] + constructor (platform.UIKit/UIView, kotlin/Function2, kotlin/Unit> = ..., kotlin/Function2 = ..., kotlin/Function0 = ..., kotlin/Function2, kotlin/Unit> = ...) // app.cash.redwood.widget/UIViewChildren.|(platform.UIKit.UIView;kotlin.Function2,kotlin.Unit>;kotlin.Function2;kotlin.Function0;kotlin.Function2,kotlin.Unit>){}[0] final val widgets // app.cash.redwood.widget/UIViewChildren.widgets|{}widgets[0] final fun (): kotlin.collections/List> // app.cash.redwood.widget/UIViewChildren.widgets.|(){}[0] diff --git a/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewGroupChildren.kt b/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewGroupChildren.kt index 7f5a14e60b..1ffc9fb0ea 100644 --- a/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewGroupChildren.kt +++ b/redwood-widget/src/androidMain/kotlin/app/cash/redwood/widget/ViewGroupChildren.kt @@ -20,27 +20,27 @@ import android.view.ViewGroup public class ViewGroupChildren( private val container: ViewGroup, - private val insert: (index: Int, view: View) -> Unit = { index, view -> - container.addView(view, index) + private val insert: (index: Int, widget: Widget) -> Unit = { index, widget -> + container.addView(widget.value, index) }, private val remove: (index: Int, count: Int) -> Unit = { index, count -> container.removeViews(index, count) }, + private val onModifierUpdated: (index: Int, widget: Widget) -> Unit = { _, _ -> + container.requestLayout() + }, ) : Widget.Children { private val _widgets = ArrayList>() override val widgets: List> get() = _widgets override fun insert(index: Int, widget: Widget) { _widgets.add(index, widget) - insert(index, widget.value) + insert.invoke(index, widget) } override fun move(fromIndex: Int, toIndex: Int, count: Int) { _widgets.move(fromIndex, toIndex, count) - val views = Array(count) { offset -> - container.getChildAt(fromIndex + offset) - } remove.invoke(fromIndex, count) val newIndex = if (toIndex > fromIndex) { @@ -48,8 +48,8 @@ public class ViewGroupChildren( } else { toIndex } - views.forEachIndexed { offset, view -> - insert(newIndex + offset, view) + for (i in newIndex until newIndex + count) { + insert.invoke(i, _widgets[i]) } } @@ -59,7 +59,7 @@ public class ViewGroupChildren( } override fun onModifierUpdated(index: Int, widget: Widget) { - container.requestLayout() + onModifierUpdated.invoke(index, widget) } override fun detach() { diff --git a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt index 963dd84a32..6ca4869ab2 100644 --- a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt +++ b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt @@ -15,7 +15,6 @@ */ package app.cash.redwood.widget -import app.cash.redwood.Modifier import kotlinx.cinterop.convert import platform.UIKit.UIStackView import platform.UIKit.UIView @@ -24,40 +23,48 @@ import platform.darwin.NSInteger @ObjCName("UIViewChildren", exact = true) public class UIViewChildren( private val container: UIView, - private val insert: (Widget, UIView, Modifier, Int) -> Unit = when (container) { - is UIStackView -> { _, view, _, index -> container.insertArrangedSubview(view, index.convert()) } - else -> { _, view, _, index -> container.insertSubview(view, index.convert()) } + private val insert: (index: Int, widget: Widget) -> Unit = when (container) { + is UIStackView -> { index, widget -> + container.insertArrangedSubview(widget.value, index.convert()) + } + else -> { index, widget -> + container.insertSubview(widget.value, index.convert()) + } }, - private val remove: (index: Int, count: Int) -> Array = when (container) { - is UIStackView -> { index, count -> container.typedArrangedSubviews.remove(index, count) } - else -> { index, count -> container.typedSubviews.remove(index, count) } + private val remove: (index: Int, count: Int) -> Unit = when (container) { + is UIStackView -> { index, count -> + container.typedArrangedSubviews.removeFromSuperview(index, count) + } + else -> { index, count -> + container.typedSubviews.removeFromSuperview(index, count) + } }, - private val updateModifier: (Modifier, Int) -> Unit = { _, _ -> }, private val invalidateSize: () -> Unit = { (container.superview ?: container).setNeedsLayout() }, + private val onModifierUpdated: (index: Int, widget: Widget) -> Unit = { _, _ -> + invalidateSize() + }, ) : Widget.Children { private val _widgets = ArrayList>() override val widgets: List> get() = _widgets override fun insert(index: Int, widget: Widget) { _widgets.add(index, widget) - insert(widget, widget.value, widget.modifier, index) + insert.invoke(index, widget) invalidateSize() } override fun move(fromIndex: Int, toIndex: Int, count: Int) { _widgets.move(fromIndex, toIndex, count) - val subviews = remove.invoke(fromIndex, count) + remove.invoke(fromIndex, count) val newIndex = if (toIndex > fromIndex) { toIndex - count } else { toIndex } - subviews.forEachIndexed { offset, view -> - val subviewIndex = newIndex + offset - val widget = widgets[subviewIndex] - insert(widget, view, widget.modifier, subviewIndex) + for (i in newIndex until newIndex + count) { + insert.invoke(i, widgets[i]) } invalidateSize() } @@ -76,8 +83,7 @@ public class UIViewChildren( } override fun onModifierUpdated(index: Int, widget: Widget) { - updateModifier(widget.modifier, index) - invalidateSize() + onModifierUpdated.invoke(index, widget) } override fun detach() { @@ -92,8 +98,8 @@ public class UIViewChildren( } } -private fun List.remove(index: Int, count: Int): Array { - return Array(count) { offset -> - this[index + offset].also(UIView::removeFromSuperview) +private fun List.removeFromSuperview(index: Int, count: Int) { + for (i in index until index + count) { + this[i].removeFromSuperview() } } diff --git a/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/Node.kt b/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/Node.kt index 8c08ac3e32..6ca7673abf 100644 --- a/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/Node.kt +++ b/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/Node.kt @@ -114,6 +114,8 @@ public class Node internal constructor( return true } + public fun isDirty(): Boolean = native.isDirty() + public fun markEverythingDirty() { native.markDirtyAndPropogateDownwards() } diff --git a/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/internal/Yoga.kt b/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/internal/Yoga.kt index 530f8832c0..99707d3483 100644 --- a/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/internal/Yoga.kt +++ b/redwood-yoga/src/commonMain/kotlin/app/cash/redwood/yoga/internal/Yoga.kt @@ -1745,15 +1745,17 @@ internal object Yoga { mode = childWidthMeasureMode, size = childWidthRef, ) + childWidth = childWidthRef.argValue + val childHeightRef = RefObject(childHeight) YGConstrainMaxSizeForMode( node = child, axis = YGFlexDirection.YGFlexDirectionColumn, ownerAxisSize = ownerHeight, ownerWidth = ownerWidth, mode = childHeightMeasureMode, - size = childWidthRef, + size = childHeightRef, ) - childWidth = childWidthRef.argValue + childHeight = childHeightRef.argValue YGLayoutNodeInternal( node = child, availableWidth = childWidth, diff --git a/settings.gradle b/settings.gradle index 05d666ffc1..e1a5edf165 100644 --- a/settings.gradle +++ b/settings.gradle @@ -43,6 +43,7 @@ enableFeaturePreview('TYPESAFE_PROJECT_ACCESSORS') rootProject.name = 'redwood' +include ':build-support-ksp-processor' include ':redwood-bom' include ':redwood-compose' include ':redwood-composeui'