blob: 8b6d1f10fb36b7a18b0368686c66fb6d78357d1d [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* 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 androidx.compose
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.compose.adapters.getViewAdapterIfExists
import java.util.Stack
class ViewAdapters {
private val adapters = mutableListOf<(parent: Any, child: Any) -> Any?>()
fun register(adapter: (parent: Any, child: Any) -> Any?) = adapters.add(adapter)
fun adapt(parent: Any, child: Any): Any? =
adapters.map { it(parent, child) }.filterNotNull().firstOrNull()
}
private fun invalidNode(node: Any): Nothing =
error("Unsupported node type ${node.javaClass.simpleName}")
internal class ViewApplyAdapter(private val adapters: ViewAdapters? = null) :
ApplyAdapter<Any> {
private data class PendingInsert(val index: Int, val instance: Any)
private val pendingInserts = Stack<PendingInsert>()
override fun Any.start(instance: Any) {}
override fun Any.insertAt(index: Int, instance: Any) {
pendingInserts.push(PendingInsert(index, instance))
}
override fun Any.removeAt(index: Int, count: Int) {
when (this) {
is ViewGroup -> removeViews(index, count)
is Emittable -> emitRemoveAt(index, count)
else -> invalidNode(this)
}
}
override fun Any.move(from: Int, to: Int, count: Int) {
when (this) {
is ViewGroup -> {
if (from > to) {
var currentFrom = from
var currentTo = to
repeat(count) {
val view = getChildAt(currentFrom)
removeViewAt(currentFrom)
addView(view, currentTo)
currentFrom++
currentTo++
}
} else {
repeat(count) {
val view = getChildAt(from)
removeViewAt(from)
addView(view, to - 1)
}
}
}
is Emittable -> {
emitMove(from, to, count)
}
else -> invalidNode(this)
}
}
override fun Any.end(instance: Any, parent: Any) {
val adapter = when (instance) {
is View -> instance.getViewAdapterIfExists()
else -> null
}
if (pendingInserts.isNotEmpty()) {
val pendingInsert = pendingInserts.peek()
if (pendingInsert.instance == instance) {
val index = pendingInsert.index
pendingInserts.pop()
when (parent) {
is ViewGroup ->
when (instance) {
is View -> {
adapter?.willInsert(instance, parent)
parent.addView(instance, index)
adapter?.didInsert(instance, parent)
}
is Emittable -> {
val adaptedView = adapters?.adapt(parent, instance) as? View
?: error(
"Could not convert ${
instance.javaClass.simpleName
} to a View"
)
adapter?.willInsert(adaptedView, parent)
parent.addView(adaptedView, index)
adapter?.didInsert(adaptedView, parent)
}
else -> invalidNode(instance)
}
is Emittable ->
when (instance) {
is View -> parent.emitInsertAt(
index,
adapters?.adapt(parent, instance) as? Emittable
?: error(
"Could not convert ${
instance.javaClass.name
} to an Emittable"
)
)
is Emittable -> parent.emitInsertAt(index, instance)
else -> invalidNode(instance)
}
else -> invalidNode(parent)
}
return
}
}
if (parent is ViewGroup)
adapter?.didUpdate(instance as View, parent)
}
}
class ViewComposer(
val root: Any,
val context: Context,
recomposer: Recomposer,
val adapters: ViewAdapters? = ViewAdapters()
) : Composer<Any>(
SlotTable(),
Applier(root, ViewApplyAdapter(adapters)), recomposer
) {
init {
FrameManager.ensureStarted()
}
}
@Suppress("UNCHECKED_CAST")
@EffectsDsl
/* inline */ class ViewComposition(val composer: ViewComposer) {
@Suppress("NOTHING_TO_INLINE")
inline operator fun <V> Effect<V>.unaryPlus(): V = resolve(this@ViewComposition.composer)
inline fun <T : View> emit(
key: Any,
/*crossinline*/
ctor: (context: Context) -> T,
update: ViewUpdater<T>.() -> Unit
) = with(composer) {
startNode(key)
val node = if (inserting) ctor(context).also { emitNode(it) }
else useNode() as T
ViewUpdater<T>(this, node).update()
endNode()
}
inline fun <T : ViewGroup> emit(
key: Any,
/*crossinline*/
ctor: (context: Context) -> T,
update: ViewUpdater<T>.() -> Unit,
children: () -> Unit
) = with(composer) {
startNode(key)
val node = if (inserting) ctor(context).also { emitNode(it) }
else useNode() as T
ViewUpdater<T>(this, node).update()
children()
endNode()
}
inline fun <T : Emittable> emit(
key: Any,
/*crossinline*/
ctor: () -> T,
update: ViewUpdater<T>.() -> Unit
) = with(composer) {
startNode(key)
val node = if (inserting) ctor().also { emitNode(it) }
else useNode() as T
ViewUpdater<T>(this, node).update()
endNode()
}
inline fun <T : Emittable> emit(
key: Any,
/*crossinline*/
ctor: () -> T,
update: ViewUpdater<T>.() -> Unit,
children: () -> Unit
) = with(composer) {
startNode(key)
val node = if (inserting) ctor().also { emitNode(it) }
else useNode() as T
ViewUpdater<T>(this, node).update()
children()
endNode()
}
@Suppress("NOTHING_TO_INLINE")
inline fun joinKey(left: Any, right: Any?): Any = composer.joinKey(left, right)
inline fun call(
key: Any,
/*crossinline*/
invalid: ViewValidator.() -> Boolean,
block: () -> Unit
) = with(composer) {
startGroup(key)
if (ViewValidator(composer).invalid() || inserting) {
startGroup(invocation)
block()
endGroup()
} else {
skipGroup(invocation)
}
endGroup()
}
inline fun <T> call(
key: Any,
/*crossinline*/
ctor: () -> T,
/*crossinline*/
invalid: ViewValidator.(f: T) -> Boolean,
block: (f: T) -> Unit
) = with(composer) {
startGroup(key)
val f = cache(true, ctor)
if (ViewValidator(this).invalid(f) || inserting) {
startGroup(invocation)
block(f)
endGroup()
} else {
skipGroup(invocation)
}
endGroup()
}
/*inline*/ fun observe(
key: Any,
block: (invalidate: (sync: Boolean) -> Unit) -> Unit
) {
composer.startGroup(key)
val invalidate = composer.startJoin(false, block)
block(invalidate)
composer.doneJoin(false)
}
}
/* inline */ class ViewValidator(val composer: Composer<*>) {
// TODO: Add more overloads for common primitive types like String and Float etc to avoid boxing
// and the immutable check
@Suppress("NOTHING_TO_INLINE")
inline fun changed(value: Int) = with(composer) {
if (nextSlot() != value || inserting) {
updateValue(value)
true
} else {
skipValue()
false
}
}
inline fun <reified T> changed(value: T) = with(composer) {
if (nextSlot() != value || inserting || !isEffectivelyImmutable(value)) {
updateValue(value)
true
} else {
skipValue()
false
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun updated(value: Int) = with(composer) {
inserting.let { inserting ->
if (nextSlot() != value || inserting) {
updateValue(value)
!inserting
} else {
skipValue()
false
}
}
}
inline fun <reified T> updated(value: T) = with(composer) {
inserting.let { inserting ->
if (nextSlot() != value || inserting || !isEffectivelyImmutable(value)) {
updateValue(value)
!inserting
} else {
skipValue()
false
}
}
}
inline fun set(value: Int, /*crossinline*/ block: (value: Int) -> Unit): Boolean =
changed(value).also { if (it) block(value) }
inline fun <reified T> set(value: T, /*crossinline*/ block: (value: T) -> Unit): Boolean =
changed(value).also { if (it) block(value) }
inline fun update(value: Int, /*crossinline*/ block: (value: Int) -> Unit): Boolean =
updated(value).also { if (it) block(value) }
inline fun <reified T> update(value: T, /*crossinline*/ block: (value: T) -> Unit): Boolean =
updated(value).also { if (it) block(value) }
/*inline*/ operator fun Boolean.plus(other: Boolean) = this || other
}
@Suppress("UNCHECKED_CAST")
/*inline */ class ComposerUpdater<N, T : N>(val composer: Composer<N>, val node: T) {
inline fun set(
value: Int,
/*crossinline*/
block: T.(value: Int) -> Unit
) = with(composer) {
if (inserting || nextSlot() != value) {
updateValue(value)
node.block(value)
// val appliedBlock: T.(value: Int) -> Unit = { block(it) }
// composer.apply(value, appliedBlock)
} else skipValue()
}
inline fun <reified V> set(
value: V,
/*crossinline*/
block: T.(value: V) -> Unit
) = with(composer) {
if (inserting || nextSlot() != value) {
updateValue(value)
node.block(value)
// val appliedBlock: T.(value: V) -> Unit = { block(it) }
// composer.apply(value, appliedBlock)
} else skipValue()
}
inline fun update(
value: Int,
/*crossinline*/
block: T.(value: Int) -> Unit
) = with(composer) {
if (inserting || nextSlot() != value) {
updateValue(value)
node.block(value)
// val appliedBlock: T.(value: Int) -> Unit = { block(it) }
// if (!inserting) composer.apply(value, appliedBlock)
} else skipValue()
}
inline fun <reified V> update(
value: V,
/*crossinline*/
block: T.(value: V) -> Unit
) = with(composer) {
if (inserting || nextSlot() != value) {
updateValue(value)
node.block(value)
// val appliedBlock: T.(value: V) -> Unit = { block(it) }
// if (!inserting) composer.apply(value, appliedBlock)
} else skipValue()
}
}
internal val currentComposerNonNull
get() = currentComposer ?: emptyComposition()
private fun emptyComposition(): Nothing =
error("Composition requires an active composition context")
val composer get() = ViewComposition(currentComposerNonNull as ViewComposer)
internal var currentComposer: Composer<*>? = null
private set
fun <T> Composer<*>.runWithCurrent(block: () -> T): T {
val prev = currentComposer
try {
currentComposer = this
return block()
} finally {
currentComposer = prev
}
}
fun ViewComposition.registerAdapter(
adapter: (parent: Any, child: Any) -> Any?
) = composer.adapters?.register(adapter)
typealias ViewUpdater<T> = ComposerUpdater<Any, T>
@PublishedApi
internal val invocation = Object()
@PublishedApi
internal val provider = Object()
@PublishedApi
internal val consumer = Object()
@PublishedApi
internal val reference = Object()