diff --git a/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt b/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt index 120ec07b8f..7e34de0ee2 100644 --- a/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt +++ b/redwood-lazylayout-dom/src/commonMain/kotlin/app/cash/redwood/lazylayout/dom/HTMLLazyList.kt @@ -45,9 +45,9 @@ internal class HTMLLazyList(document: Document) : LazyList { override fun deleteRows(index: Int, count: Int) { } - override fun setContent(view: HTMLElement, content: HTMLElement?, modifier: Modifier) { - if (content != null) { - view.appendChild(content) + override fun setContent(view: HTMLElement, widget: Widget?) { + if (widget != null) { + view.appendChild(widget.value) } } } diff --git a/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt b/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt index 87b8fb6b43..b047f7901c 100644 --- a/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt +++ b/redwood-lazylayout-uiview/src/commonMain/kotlin/app/cash/redwood/lazylayout/uiview/UIViewLazyList.kt @@ -124,8 +124,6 @@ internal class UIViewLazyList : private var updateProcessor: LazyListUpdateProcessor? = object : LazyListUpdateProcessor() { override fun createPlaceholder(original: UIView) = SizeOnlyPlaceholder(original) - override fun isSizeOnlyPlaceholder(placeholder: UIView) = placeholder is SizeOnlyPlaceholder - override fun insertRows(index: Int, count: Int) { rowCount += count val tableView = this@UIViewLazyList.tableView ?: error("detached") @@ -156,8 +154,8 @@ internal class UIViewLazyList : tableView.endUpdates() } - override fun setContent(view: LazyListContainerCell, content: UIView?, modifier: Modifier) { - view.setContent(content) + override fun setContent(view: LazyListContainerCell, widget: Widget?) { + view.setContent(widget?.value) } override fun detach(view: LazyListContainerCell) { diff --git a/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt b/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt index be3f3c167e..8af4d07515 100644 --- a/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt +++ b/redwood-lazylayout-view/src/main/kotlin/app/cash/redwood/lazylayout/view/ViewLazyList.kt @@ -73,9 +73,6 @@ internal class ViewLazyList private constructor( override fun createPlaceholder(original: View) = SizeOnlyPlaceholder(original, value.context) - override fun isSizeOnlyPlaceholder(placeholder: View) = - placeholder is SizeOnlyPlaceholder - override fun insertRows(index: Int, count: Int) { adapter.notifyItemRangeInserted(index, count) } @@ -84,8 +81,8 @@ internal class ViewLazyList private constructor( adapter.notifyItemRangeRemoved(index, count) } - override fun setContent(view: ViewHolder, content: View?, modifier: Modifier) { - view.content = content + override fun setContent(view: ViewHolder, widget: Widget?) { + view.content = widget?.value } } diff --git a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api index b9166df258..a938adb1e8 100644 --- a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api +++ b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.api @@ -36,11 +36,10 @@ public abstract class app/cash/redwood/lazylayout/widget/LazyListUpdateProcessor public final fun getPlaceholder ()Lapp/cash/redwood/widget/Widget$Children; public final fun getSize ()I protected abstract fun insertRows (II)V - protected fun isSizeOnlyPlaceholder (Ljava/lang/Object;)Z public final fun itemsAfter (I)V public final fun itemsBefore (I)V public final fun onEndChanges ()V - protected abstract fun setContent (Ljava/lang/Object;Ljava/lang/Object;Lapp/cash/redwood/Modifier;)V + protected abstract fun setContent (Ljava/lang/Object;Lapp/cash/redwood/widget/Widget;)V } public final class app/cash/redwood/lazylayout/widget/LazyListUpdateProcessor$Binding { diff --git a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api index 1d58dd039c..9e9861513a 100644 --- a/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api +++ b/redwood-lazylayout-widget/api/redwood-lazylayout-widget.klib.api @@ -62,7 +62,7 @@ abstract class <#A: kotlin/Any, #B: kotlin/Any> app.cash.redwood.lazylayout.widg abstract fun deleteRows(kotlin/Int, kotlin/Int) // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.deleteRows|deleteRows(kotlin.Int;kotlin.Int){}[0] abstract fun insertRows(kotlin/Int, kotlin/Int) // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.insertRows|insertRows(kotlin.Int;kotlin.Int){}[0] - abstract fun setContent(#A, #B?, app.cash.redwood/Modifier) // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.setContent|setContent(1:0;1:1?;app.cash.redwood.Modifier){}[0] + abstract fun setContent(#A, app.cash.redwood.widget/Widget<#B>?) // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.setContent|setContent(1:0;app.cash.redwood.widget.Widget<1:1>?){}[0] final fun bind(kotlin/Int, #A): app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.Binding<#A, #B> // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.bind|bind(kotlin.Int;1:0){}[0] final fun getOrCreateView(kotlin/Int, kotlin/Function1, #A>): #A // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.getOrCreateView|getOrCreateView(kotlin.Int;kotlin.Function1,1:0>){}[0] final fun itemsAfter(kotlin/Int) // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.itemsAfter|itemsAfter(kotlin.Int){}[0] @@ -71,7 +71,6 @@ abstract class <#A: kotlin/Any, #B: kotlin/Any> app.cash.redwood.lazylayout.widg open fun createPlaceholder(#B): #B? // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.createPlaceholder|createPlaceholder(1:1){}[0] open fun detach(#A) // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.detach|detach(1:0){}[0] open fun detach() // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.detach|detach(){}[0] - open fun isSizeOnlyPlaceholder(#B): kotlin/Boolean // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.isSizeOnlyPlaceholder|isSizeOnlyPlaceholder(1:1){}[0] final class <#A1: kotlin/Any, #B1: kotlin/Any> Binding { // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.Binding|null[0] final val isBound // app.cash.redwood.lazylayout.widget/LazyListUpdateProcessor.Binding.isBound|{}isBound[0] diff --git a/redwood-lazylayout-widget/src/commonMain/kotlin/app/cash/redwood/lazylayout/widget/LazyListUpdateProcessor.kt b/redwood-lazylayout-widget/src/commonMain/kotlin/app/cash/redwood/lazylayout/widget/LazyListUpdateProcessor.kt index 86f906a14b..c1d069a7ad 100644 --- a/redwood-lazylayout-widget/src/commonMain/kotlin/app/cash/redwood/lazylayout/widget/LazyListUpdateProcessor.kt +++ b/redwood-lazylayout-widget/src/commonMain/kotlin/app/cash/redwood/lazylayout/widget/LazyListUpdateProcessor.kt @@ -42,13 +42,13 @@ public abstract class LazyListUpdateProcessor { get() = itemsBefore.nonNullElements + loadedItems + itemsAfter.nonNullElements /** Pool of placeholder widgets. */ - private val placeholdersQueue = ArrayDeque() + private val placeholdersQueue = ArrayDeque>() /** * The first placeholder ever returned. We use it to choose measured dimensions for created * placeholders if the pool ever runs out. */ - private var firstPlaceholder: W? = null + private var firstPlaceholder: Widget? = null /** Loaded items that may or may not have a view bound. */ private var loadedItems = mutableListOf>() @@ -68,8 +68,8 @@ public abstract class LazyListUpdateProcessor { override fun insert(index: Int, widget: Widget) { _widgets += widget - if (firstPlaceholder == null) firstPlaceholder = widget.value - placeholdersQueue += widget.value + if (firstPlaceholder == null) firstPlaceholder = widget + placeholdersQueue += widget } override fun move(fromIndex: Int, toIndex: Int, count: Int) { @@ -244,7 +244,7 @@ public abstract class LazyListUpdateProcessor { for (i in 0 until edit.widgets.size) { val index = itemsBefore.size + edit.index + i val binding = Binding(this).apply { - setContentAndModifier(edit.widgets[i]) + setContent(edit.widgets[i]) } loadedItems.add(edit.index + i, binding) @@ -336,7 +336,7 @@ public abstract class LazyListUpdateProcessor { // No binding for this index. Create one. if (binding == null) { return Binding(this).apply { - setContentAndModifier(loadedContent) + setContent(loadedContent) } } @@ -346,7 +346,7 @@ public abstract class LazyListUpdateProcessor { recyclePlaceholder(binding.content) } binding.isPlaceholder = false - binding.setContentAndModifier(loadedContent) + binding.setContent(loadedContent) return binding } @@ -357,9 +357,9 @@ public abstract class LazyListUpdateProcessor { if (!loaded.isBound) return null // Show the placeholder in the bound view. - val placeholderContent = loaded.processor.takePlaceholder() - if (placeholderContent != null) { - loaded.setContentAndModifier(placeholderContent, Modifier) + val placeholder = loaded.processor.takePlaceholder() + if (placeholder != null) { + loaded.setContent(placeholder) } loaded.isPlaceholder = true return loaded @@ -396,7 +396,7 @@ public abstract class LazyListUpdateProcessor { isPlaceholder = true, ).apply { val placeholder = takePlaceholder() ?: return@apply // Detached. - setContentAndModifier(placeholder, Modifier) + setContent(placeholder) } return when { @@ -406,17 +406,21 @@ public abstract class LazyListUpdateProcessor { } } - private fun takePlaceholder(): W? { + private fun takePlaceholder(): Widget? { val result = placeholdersQueue.removeFirstOrNull() if (result != null) return result val firstPlaceholder = this.firstPlaceholder ?: return null // Detached. - return createPlaceholder(firstPlaceholder) + val sizeOnlyPlaceholderValue = createPlaceholder(firstPlaceholder.value) ?: return null + return SizeOnlyPlaceholderWidget( + value = sizeOnlyPlaceholderValue, + modifier = firstPlaceholder.modifier, + ) } - private fun recyclePlaceholder(placeholder: W?) { + private fun recyclePlaceholder(placeholder: Widget?) { if (placeholder == null) return // Detached. - if (!isSizeOnlyPlaceholder(placeholder)) { + if (placeholder !is SizeOnlyPlaceholderWidget) { placeholdersQueue += placeholder } } @@ -429,13 +433,11 @@ public abstract class LazyListUpdateProcessor { */ protected open fun createPlaceholder(original: W): W? = null - protected open fun isSizeOnlyPlaceholder(placeholder: W): Boolean = false - protected abstract fun insertRows(index: Int, count: Int) protected abstract fun deleteRows(index: Int, count: Int) - protected abstract fun setContent(view: V, content: W?, modifier: Modifier) + protected abstract fun setContent(view: V, widget: Widget?) protected open fun detach(view: V) { } @@ -478,21 +480,14 @@ public abstract class LazyListUpdateProcessor { * This may also be null if it should be a placeholder but the enclosing LazyList is detached. * This could happen if the user scrolls the table after the view is detached. */ - internal var content: W? = null + internal var content: Widget? = null private set - private var modifier: Modifier = Modifier - - internal fun setContentAndModifier(widget: Widget) { - setContentAndModifier(widget.value, widget.modifier) - } - - internal fun setContentAndModifier(content: W, modifier: Modifier) { + internal fun setContent(content: Widget) { this.content = content - this.modifier = modifier val view = this.view - if (view != null) processor.setContent(view, content, modifier) + if (view != null) processor.setContent(view, content) } public fun bind(view: V) { @@ -503,14 +498,14 @@ public abstract class LazyListUpdateProcessor { } this.view = view - processor.setContent(view, content, modifier) + processor.setContent(view, content) } public fun unbind() { val view = this.view ?: return // Unbind the view. - processor.setContent(view, null, Modifier) + processor.setContent(view, null) this.view = null if (isPlaceholder) { @@ -542,4 +537,9 @@ public abstract class LazyListUpdateProcessor { class Move(val fromIndex: Int, val toIndex: Int, val count: Int) : Edit() class Remove(var index: Int, var count: Int) : Edit() } + + private class SizeOnlyPlaceholderWidget( + override val value: W, + override var modifier: Modifier, + ) : Widget } diff --git a/redwood-lazylayout-widget/src/commonTest/kotlin/app/cash/redwood/lazylayout/widget/FakeUpdateProcessor.kt b/redwood-lazylayout-widget/src/commonTest/kotlin/app/cash/redwood/lazylayout/widget/FakeUpdateProcessor.kt index 4086ffed1c..ad8e119c15 100644 --- a/redwood-lazylayout-widget/src/commonTest/kotlin/app/cash/redwood/lazylayout/widget/FakeUpdateProcessor.kt +++ b/redwood-lazylayout-widget/src/commonTest/kotlin/app/cash/redwood/lazylayout/widget/FakeUpdateProcessor.kt @@ -15,9 +15,9 @@ */ package app.cash.redwood.lazylayout.widget -import app.cash.redwood.Modifier import app.cash.redwood.lazylayout.widget.FakeUpdateProcessor.StringCell import app.cash.redwood.lazylayout.widget.FakeUpdateProcessor.StringContent +import app.cash.redwood.widget.Widget /** * This fake simulates a real scroll window, which is completely independent of the window of loaded @@ -112,7 +112,9 @@ class FakeUpdateProcessor : LazyListUpdateProcessor() } } - override fun setContent(view: StringCell, content: StringContent?, modifier: Modifier) { + override fun setContent(view: StringCell, widget: Widget?) { + val content = widget?.value + // It is an error for `content` to already have a parent cell. val previous = view.content require(content?.parentCell == null) diff --git a/redwood-snapshot-testing/src/iosMain/kotlin/app/cash/redwood/snapshot/testing/UIViewTestWidgetFactory.kt b/redwood-snapshot-testing/src/iosMain/kotlin/app/cash/redwood/snapshot/testing/UIViewTestWidgetFactory.kt index ab542c5cbb..575b7123a2 100644 --- a/redwood-snapshot-testing/src/iosMain/kotlin/app/cash/redwood/snapshot/testing/UIViewTestWidgetFactory.kt +++ b/redwood-snapshot-testing/src/iosMain/kotlin/app/cash/redwood/snapshot/testing/UIViewTestWidgetFactory.kt @@ -23,7 +23,6 @@ import app.cash.redwood.widget.ResizableWidget import app.cash.redwood.widget.ResizableWidget.SizeListener import kotlinx.cinterop.CValue import kotlinx.cinterop.readValue -import platform.CoreGraphics.CGRectMake import platform.CoreGraphics.CGRectZero import platform.CoreGraphics.CGSize import platform.CoreGraphics.CGSizeMake @@ -84,12 +83,16 @@ class UIViewText : } } -class UIViewColor : Color { +class UIViewColor : + Color, + ResizableWidget { + override var sizeListener: SizeListener? = null + override val value: UIView = object : UIView(CGRectZero.readValue()) { - override fun intrinsicContentSize(): CValue { - return CGSizeMake(width, height) - } + override fun intrinsicContentSize() = CGSizeMake(width, height) + override fun sizeThatFits(size: CValue) = CGSizeMake(width, height) } + override var modifier: Modifier = Modifier private var width = 0.0 @@ -97,22 +100,17 @@ class UIViewColor : Color { override fun width(width: Dp) { this.width = with(Density.Default) { width.toPx() } - invalidate() + sizeListener?.invalidateSize() } override fun height(height: Dp) { this.height = with(Density.Default) { height.toPx() } - invalidate() + sizeListener?.invalidateSize() } override fun color(color: Int) { value.backgroundColor = color.toUIColor() } - - private fun invalidate() { - value.setFrame(CGRectMake(0.0, 0.0, width, height)) - value.invalidateIntrinsicContentSize() - } } class UIViewSimpleColumn : SimpleColumn {