diff --git a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt index ff3056dcf7..c9bf3fee69 100644 --- a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt +++ b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiFlexContainer.kt @@ -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 @@ -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) @@ -76,11 +110,11 @@ 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 } @@ -88,11 +122,11 @@ internal class ComposeUiFlexContainer( 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 } @@ -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. diff --git a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt index 7df88755bd..ccae48fe5f 100644 --- a/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt +++ b/redwood-layout-composeui/src/commonMain/kotlin/app/cash/redwood/layout/composeui/ComposeUiRedwoodLayoutWidgetFactory.kt @@ -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() } diff --git a/redwood-layout-dom/src/commonMain/kotlin/app/cash/redwood/layout/dom/HTMLElementRedwoodLayoutWidgetFactory.kt b/redwood-layout-dom/src/commonMain/kotlin/app/cash/redwood/layout/dom/HTMLElementRedwoodLayoutWidgetFactory.kt index 0b3250d29e..f8d908bfcc 100644 --- a/redwood-layout-dom/src/commonMain/kotlin/app/cash/redwood/layout/dom/HTMLElementRedwoodLayoutWidgetFactory.kt +++ b/redwood-layout-dom/src/commonMain/kotlin/app/cash/redwood/layout/dom/HTMLElementRedwoodLayoutWidgetFactory.kt @@ -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 { - override fun Box(): Box { + override fun Box(): Box = TODO("Not yet implemented") - } override fun Column(): Column = - HTMLFlexContainer( - value = document.createElement("div") as HTMLDivElement, - direction = "column", - overflowSetter = { style.overflowY = it }, - ) + HTMLColumn(document.createElement("div") as HTMLDivElement) override fun Row(): Row = - HTMLFlexContainer( - value = document.createElement("div") as HTMLDivElement, - direction = "row", - overflowSetter = { style.overflowX = it }, - ) + HTMLRow(document.createElement("div") as HTMLDivElement) override fun Spacer(): Spacer = - HTMLSpacer( - value = document.createElement("div") as HTMLDivElement, - ) + HTMLSpacer(document.createElement("div") as HTMLDivElement) +} + +private class HTMLColumn( + value: HTMLDivElement, +) : Column { + 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 { + 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, - Row { + private val overflowSetter: CSSStyleDeclaration.(String) -> Unit, +) { init { value.style.display = "flex" value.style.flexDirection = direction } - override val children: Widget.Children = HTMLFlexElementChildren(value) + val children: Widget.Children = 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() @@ -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 @@ -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( 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 80308ccd74..59a12862b2 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 @@ -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 @@ -31,16 +36,60 @@ import kotlinx.cinterop.convert import platform.UIKit.UIView import platform.darwin.NSInteger -internal class UIViewFlexContainer( - direction: FlexDirection, -) : YogaFlexContainer, +internal class UIViewColumn : + Column, + ResizableWidget, + 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, ResizableWidget, 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 { + 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 @@ -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() } 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 1cbb7651ff..7a35e87a59 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 @@ -20,13 +20,12 @@ 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 import platform.UIKit.UIView @ObjCName("UIViewRedwoodLayoutWidgetFactory", exact = true) public class UIViewRedwoodLayoutWidgetFactory : RedwoodLayoutWidgetFactory { override fun Box(): Box = UIViewBox() - override fun Column(): Column = UIViewFlexContainer(FlexDirection.Column) - override fun Row(): Row = UIViewFlexContainer(FlexDirection.Row) + override fun Column(): Column = UIViewColumn() + override fun Row(): Row = UIViewRow() override fun Spacer(): Spacer = UIViewSpacer() } 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 c40e62a51f..6f2d1f1057 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 @@ -27,8 +27,13 @@ import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView.OnScrollChangeListener as OnScrollChangeListenerCompat 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.Density +import app.cash.redwood.ui.Margin import app.cash.redwood.ui.Px import app.cash.redwood.widget.ChangeListener import app.cash.redwood.widget.ViewGroupChildren @@ -37,19 +42,54 @@ import app.cash.redwood.yoga.FlexDirection import app.cash.redwood.yoga.Node import app.cash.redwood.yoga.isHorizontal -internal class ViewFlexContainer( +internal class ViewColumn(context: Context) : Column, ChangeListener { + private val delegate = ViewFlexContainer(context, FlexDirection.Column) + + override val value get() = delegate.value + override var modifier by delegate::modifier + + override val children get() = delegate.children + + override fun width(width: Constraint) = delegate.width(width) + override fun height(height: Constraint) = delegate.height(height) + override fun margin(margin: Margin) = delegate.margin(margin) + override fun overflow(overflow: Overflow) = delegate.overflow(overflow) + override fun horizontalAlignment(horizontalAlignment: CrossAxisAlignment) = delegate.crossAxisAlignment(horizontalAlignment) + override fun verticalAlignment(verticalAlignment: MainAxisAlignment) = delegate.mainAxisAlignment(verticalAlignment) + override fun onScroll(onScroll: ((Px) -> Unit)?) = delegate.onScroll(onScroll) + override fun onEndChanges() = delegate.onEndChanges() +} + +internal class ViewRow(context: Context) : Row, ChangeListener { + private val delegate = ViewFlexContainer(context, FlexDirection.Row) + + override val value get() = delegate.value + override var modifier by delegate::modifier + + override val children get() = delegate.children + + override fun width(width: Constraint) = delegate.width(width) + override fun height(height: Constraint) = delegate.height(height) + override fun margin(margin: Margin) = delegate.margin(margin) + override fun overflow(overflow: Overflow) = delegate.overflow(overflow) + override fun horizontalAlignment(horizontalAlignment: MainAxisAlignment) = delegate.mainAxisAlignment(horizontalAlignment) + override fun verticalAlignment(verticalAlignment: CrossAxisAlignment) = delegate.crossAxisAlignment(verticalAlignment) + override fun onScroll(onScroll: ((Px) -> Unit)?) = delegate.onScroll(onScroll) + override fun onEndChanges() = delegate.onEndChanges() +} + +private class ViewFlexContainer( private val context: Context, private val direction: FlexDirection, -) : YogaFlexContainer, - ChangeListener { +) : YogaFlexContainer { private val yogaLayout: YogaLayout = YogaLayout(context) override val rootNode: Node get() = yogaLayout.rootNode override val density = Density(context.resources) private val hostView = HostView() - override val value: View get() = hostView + val value: View get() = hostView - override val children = ViewGroupChildren( + val children = ViewGroupChildren( yogaLayout, insert = { index, widget -> val view = widget.value @@ -75,7 +115,7 @@ internal class ViewFlexContainer( private var onScroll: ((Px) -> Unit)? = null - override var modifier: Modifier = Modifier + var modifier: Modifier = Modifier init { yogaLayout.rootNode.direction = when (hostView.resources.configuration.layoutDirection) { @@ -86,15 +126,15 @@ internal class ViewFlexContainer( yogaLayout.rootNode.flexDirection = direction } - override fun width(width: Constraint) { + fun width(width: Constraint) { yogaLayout.widthConstraint = width } - override fun height(height: Constraint) { + fun height(height: Constraint) { yogaLayout.heightConstraint = height } - override fun overflow(overflow: Overflow) { + fun overflow(overflow: Overflow) { hostView.scrollEnabled = when (overflow) { Overflow.Clip -> false Overflow.Scroll -> true @@ -102,12 +142,12 @@ internal class ViewFlexContainer( } } - override fun onScroll(onScroll: ((Px) -> Unit)?) { + fun onScroll(onScroll: ((Px) -> Unit)?) { this.onScroll = onScroll hostView.attachOrDetachScrollListeners() } - override fun onEndChanges() { + fun onEndChanges() { hostView.invalidate() hostView.requestLayout() yogaLayout.invalidate() 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 6f79e11866..909a859fe7 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 @@ -29,9 +29,9 @@ public class ViewRedwoodLayoutWidgetFactory( ) : RedwoodLayoutWidgetFactory { override fun Box(): Box = ViewBox(context) - override fun Column(): Column = ViewFlexContainer(context, FlexDirection.Column) + override fun Column(): Column = ViewColumn(context) - override fun Row(): Row = ViewFlexContainer(context, FlexDirection.Row) + override fun Row(): Row = ViewRow(context) override fun Spacer(): Spacer = ViewSpacer(context) }