Skip to content

Commit

Permalink
Migrate Row and Column implementations away from subtypes
Browse files Browse the repository at this point in the history
When widgets switch to abstract classes, this will no longer be allowed.
  • Loading branch information
JakeWharton committed Oct 23, 2024
1 parent 814a4db commit 1da0130
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import app.cash.redwood.layout.api.Constraint
import app.cash.redwood.layout.api.CrossAxisAlignment
import app.cash.redwood.layout.api.MainAxisAlignment
import app.cash.redwood.layout.api.Overflow
import app.cash.redwood.layout.widget.Column
import app.cash.redwood.layout.widget.Row
import app.cash.redwood.ui.Density
import app.cash.redwood.ui.Margin
import app.cash.redwood.ui.Px
Expand All @@ -56,14 +58,46 @@ import app.cash.redwood.yoga.Node
import app.cash.redwood.yoga.Size
import app.cash.redwood.yoga.isHorizontal

internal class ComposeUiColumn : Column<@Composable () -> Unit> {
private val container = ComposeUiFlexContainer(FlexDirection.Row)

override val value get() = container.value
override var modifier by container::modifier
override val children get() = container.children

override fun width(width: Constraint) = container.width(width)
override fun height(height: Constraint) = container.height(height)
override fun margin(margin: Margin) = container.margin(margin)
override fun overflow(overflow: Overflow) = container.overflow(overflow)
override fun horizontalAlignment(horizontalAlignment: CrossAxisAlignment) = container.crossAxisAlignment(horizontalAlignment)
override fun verticalAlignment(verticalAlignment: MainAxisAlignment) = container.mainAxisAlignment(verticalAlignment)
override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll)
}

internal class ComposeUiRow : Row<@Composable () -> Unit> {
private val container = ComposeUiFlexContainer(FlexDirection.Row)

override val value get() = container.value
override var modifier by container::modifier
override val children get() = container.children

override fun width(width: Constraint) = container.width(width)
override fun height(height: Constraint) = container.height(height)
override fun margin(margin: Margin) = container.margin(margin)
override fun overflow(overflow: Overflow) = container.overflow(overflow)
override fun horizontalAlignment(horizontalAlignment: MainAxisAlignment) = container.mainAxisAlignment(horizontalAlignment)
override fun verticalAlignment(verticalAlignment: CrossAxisAlignment) = container.crossAxisAlignment(verticalAlignment)
override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll)
}

internal class ComposeUiFlexContainer(
private val flexDirection: FlexDirection,
) : YogaFlexContainer<@Composable () -> Unit> {
override val rootNode = Node().apply {
flexDirection = this@ComposeUiFlexContainer.flexDirection
}
override val children = ComposeWidgetChildren()
override var modifier: RedwoodModifier = RedwoodModifier
val children = ComposeWidgetChildren()
var modifier: RedwoodModifier = RedwoodModifier

private var recomposeTick by mutableIntStateOf(0)
private var width by mutableStateOf(Constraint.Wrap)
Expand All @@ -76,23 +110,23 @@ internal class ComposeUiFlexContainer(
internal var testOnlyModifier: Modifier? = null
internal var scrollState: ScrollState? = null

override fun width(width: Constraint) {
fun width(width: Constraint) {
this.width = width
}

override fun height(height: Constraint) {
fun height(height: Constraint) {
this.height = height
}

override fun margin(margin: Margin) {
this.margin = margin
}

override fun overflow(overflow: Overflow) {
fun overflow(overflow: Overflow) {
this.overflow = overflow
}

override fun onScroll(onScroll: ((Px) -> Unit)?) {
fun onScroll(onScroll: ((Px) -> Unit)?) {
this.onScroll = onScroll
}

Expand All @@ -110,7 +144,7 @@ internal class ComposeUiFlexContainer(
recomposeTick++
}

override val value: @Composable () -> Unit = @Composable {
val value: @Composable () -> Unit = @Composable {
Layout(
content = {
// Observe this so we can manually trigger recomposition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ import app.cash.redwood.layout.widget.Column
import app.cash.redwood.layout.widget.RedwoodLayoutWidgetFactory
import app.cash.redwood.layout.widget.Row
import app.cash.redwood.layout.widget.Spacer
import app.cash.redwood.yoga.FlexDirection

public class ComposeUiRedwoodLayoutWidgetFactory : RedwoodLayoutWidgetFactory<@Composable () -> Unit> {
override fun Box(): Box<@Composable () -> Unit> = ComposeUiBox()
override fun Column(): Column<@Composable () -> Unit> = ComposeUiFlexContainer(FlexDirection.Column)
override fun Row(): Row<@Composable () -> Unit> = ComposeUiFlexContainer(FlexDirection.Row)
override fun Column(): Column<@Composable () -> Unit> = ComposeUiColumn()
override fun Row(): Row<@Composable () -> Unit> = ComposeUiRow()
override fun Spacer(): Spacer<@Composable () -> Unit> = ComposeUiSpacer()
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,60 +40,85 @@ import app.cash.redwood.widget.Widget
import org.w3c.dom.Document
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.css.CSSStyleDeclaration
import org.w3c.dom.events.Event
import org.w3c.dom.events.EventListener

public class HTMLElementRedwoodLayoutWidgetFactory(
private val document: Document,
) : RedwoodLayoutWidgetFactory<HTMLElement> {
override fun Box(): Box<HTMLElement> {
override fun Box(): Box<HTMLElement> =
TODO("Not yet implemented")
}

override fun Column(): Column<HTMLElement> =
HTMLFlexContainer(
value = document.createElement("div") as HTMLDivElement,
direction = "column",
overflowSetter = { style.overflowY = it },
)
HTMLColumn(document.createElement("div") as HTMLDivElement)

override fun Row(): Row<HTMLElement> =
HTMLFlexContainer(
value = document.createElement("div") as HTMLDivElement,
direction = "row",
overflowSetter = { style.overflowX = it },
)
HTMLRow(document.createElement("div") as HTMLDivElement)

override fun Spacer(): Spacer<HTMLElement> =
HTMLSpacer(
value = document.createElement("div") as HTMLDivElement,
)
HTMLSpacer(document.createElement("div") as HTMLDivElement)
}

private class HTMLColumn(
value: HTMLDivElement,
) : Column<HTMLElement> {
private val container = HTMLFlexContainer(value, "column") { overflowY = it }

override val value get() = container.value
override var modifier by container::modifier
override val children get() = container.children

override fun width(width: Constraint) = container.width(width)
override fun height(height: Constraint) = container.height(height)
override fun margin(margin: Margin) = container.margin(margin)
override fun overflow(overflow: Overflow) = container.overflow(overflow)
override fun horizontalAlignment(horizontalAlignment: CrossAxisAlignment) = container.crossAxisAlignment(horizontalAlignment)
override fun verticalAlignment(verticalAlignment: MainAxisAlignment) = container.mainAxisAlignment(verticalAlignment)
override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll)
}

private class HTMLRow(
value: HTMLDivElement,
) : Row<HTMLElement> {
private val container = HTMLFlexContainer(value, "row") { overflowX = it }

override val value get() = container.value
override var modifier by container::modifier
override val children get() = container.children

override fun width(width: Constraint) = container.width(width)
override fun height(height: Constraint) = container.height(height)
override fun margin(margin: Margin) = container.margin(margin)
override fun overflow(overflow: Overflow) = container.overflow(overflow)
override fun horizontalAlignment(horizontalAlignment: MainAxisAlignment) = container.mainAxisAlignment(horizontalAlignment)
override fun verticalAlignment(verticalAlignment: CrossAxisAlignment) = container.crossAxisAlignment(verticalAlignment)
override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll)
}

private class HTMLFlexContainer(
override val value: HTMLDivElement,
val value: HTMLDivElement,
direction: String,
private val overflowSetter: HTMLDivElement.(String) -> Unit,
) : Column<HTMLElement>,
Row<HTMLElement> {
private val overflowSetter: CSSStyleDeclaration.(String) -> Unit,
) {
init {
value.style.display = "flex"
value.style.flexDirection = direction
}

override val children: Widget.Children<HTMLElement> = HTMLFlexElementChildren(value)
val children: Widget.Children<HTMLElement> = HTMLFlexElementChildren(value)

private var scrollEventListener: EventListener? = null

override fun width(width: Constraint) {
fun width(width: Constraint) {
value.style.width = width.toCss()
}

override fun height(height: Constraint) {
fun height(height: Constraint) {
value.style.height = height.toCss()
}

override fun margin(margin: Margin) {
fun margin(margin: Margin) {
value.style.apply {
marginInlineStart = margin.start.toPxString()
marginInlineEnd = margin.end.toPxString()
Expand All @@ -102,11 +127,11 @@ private class HTMLFlexContainer(
}
}

override fun overflow(overflow: Overflow) {
value.overflowSetter(overflow.toCss())
fun overflow(overflow: Overflow) {
value.style.overflowSetter(overflow.toCss())
}

override fun onScroll(onScroll: ((Px) -> Unit)?) {
fun onScroll(onScroll: ((Px) -> Unit)?) {
scrollEventListener?.let { eventListener ->
value.removeEventListener("scroll", eventListener)
scrollEventListener = null
Expand All @@ -127,31 +152,15 @@ private class HTMLFlexContainer(
}
}

override fun horizontalAlignment(horizontalAlignment: MainAxisAlignment) {
mainAxisAlignment(horizontalAlignment)
}

override fun horizontalAlignment(horizontalAlignment: CrossAxisAlignment) {
crossAxisAlignment(horizontalAlignment)
}

override fun verticalAlignment(verticalAlignment: MainAxisAlignment) {
mainAxisAlignment(verticalAlignment)
}

override fun verticalAlignment(verticalAlignment: CrossAxisAlignment) {
crossAxisAlignment(verticalAlignment)
}

private fun crossAxisAlignment(crossAxisAlignment: CrossAxisAlignment) {
fun crossAxisAlignment(crossAxisAlignment: CrossAxisAlignment) {
value.style.alignItems = crossAxisAlignment.toCss()
}

private fun mainAxisAlignment(mainAxisAlignment: MainAxisAlignment) {
fun mainAxisAlignment(mainAxisAlignment: MainAxisAlignment) {
value.style.justifyContent = mainAxisAlignment.toCss()
}

override var modifier: Modifier = Modifier
var modifier: Modifier = Modifier
}

private class HTMLSpacer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ package app.cash.redwood.layout.uiview

import app.cash.redwood.Modifier
import app.cash.redwood.layout.api.Constraint
import app.cash.redwood.layout.api.CrossAxisAlignment
import app.cash.redwood.layout.api.MainAxisAlignment
import app.cash.redwood.layout.api.Overflow
import app.cash.redwood.layout.widget.Column
import app.cash.redwood.layout.widget.Row
import app.cash.redwood.ui.Default
import app.cash.redwood.ui.Density
import app.cash.redwood.ui.Margin
import app.cash.redwood.ui.Px
import app.cash.redwood.widget.ChangeListener
import app.cash.redwood.widget.ResizableWidget
Expand All @@ -31,16 +36,60 @@ import kotlinx.cinterop.convert
import platform.UIKit.UIView
import platform.darwin.NSInteger

internal class UIViewFlexContainer(
direction: FlexDirection,
) : YogaFlexContainer<UIView>,
internal class UIViewColumn :
Column<UIView>,
ResizableWidget<UIView>,
ChangeListener {
private val container = UIViewFlexContainer(FlexDirection.Column)

override val value: UIView get() = container.value
override var modifier by container::modifier
override val children get() = container.children
override var sizeListener by container::sizeListener

override fun width(width: Constraint) = container.width(width)
override fun height(height: Constraint) = container.height(height)
override fun margin(margin: Margin) = container.margin(margin)
override fun overflow(overflow: Overflow) = container.overflow(overflow)
override fun horizontalAlignment(horizontalAlignment: CrossAxisAlignment) = container.crossAxisAlignment(horizontalAlignment)
override fun verticalAlignment(verticalAlignment: MainAxisAlignment) = container.mainAxisAlignment(verticalAlignment)
override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll)
override fun onEndChanges() = container.onEndChanges()
}

internal class UIViewRow :
Row<UIView>,
ResizableWidget<UIView>,
ChangeListener {
private val yogaView: YogaUIView = YogaUIView()
private val container = UIViewFlexContainer(FlexDirection.Row)

override val value: UIView get() = container.value
override var modifier by container::modifier
override val children get() = container.children
override var sizeListener by container::sizeListener

override fun width(width: Constraint) = container.width(width)
override fun height(height: Constraint) = container.height(height)
override fun margin(margin: Margin) = container.margin(margin)
override fun overflow(overflow: Overflow) = container.overflow(overflow)
override fun horizontalAlignment(horizontalAlignment: MainAxisAlignment) = container.mainAxisAlignment(horizontalAlignment)
override fun verticalAlignment(verticalAlignment: CrossAxisAlignment) = container.crossAxisAlignment(verticalAlignment)
override fun onScroll(onScroll: ((Px) -> Unit)?) = container.onScroll(onScroll)
override fun onEndChanges() = container.onEndChanges()
}

internal class UIViewFlexContainer(
direction: FlexDirection,
) : YogaFlexContainer<UIView> {
private val yogaView: YogaUIView = YogaUIView().apply {
rootNode.flexDirection = direction
}

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(
val value: UIView get() = yogaView

val children: UIViewChildren = UIViewChildren(
container = value,
insert = { index, widget ->
val view = widget.value
Expand Down Expand Up @@ -68,31 +117,27 @@ internal class UIViewFlexContainer(
},
invalidateSize = ::invalidateSize,
)
override var modifier: Modifier = Modifier
var modifier: Modifier = Modifier

override var sizeListener: SizeListener? = null

init {
yogaView.rootNode.flexDirection = direction
}
var sizeListener: SizeListener? = null

override fun width(width: Constraint) {
fun width(width: Constraint) {
yogaView.widthConstraint = width
}

override fun height(height: Constraint) {
fun height(height: Constraint) {
yogaView.heightConstraint = height
}

override fun overflow(overflow: Overflow) {
fun overflow(overflow: Overflow) {
yogaView.scrollEnabled = overflow == Overflow.Scroll
}

override fun onScroll(onScroll: ((Px) -> Unit)?) {
fun onScroll(onScroll: ((Px) -> Unit)?) {
yogaView.onScroll = onScroll
}

override fun onEndChanges() {
fun onEndChanges() {
invalidateSize()
}

Expand Down
Loading

0 comments on commit 1da0130

Please sign in to comment.