Skip to content

Commit

Permalink
Stack overflow on UITreeView (#1919)
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz authored Oct 5, 2023
1 parent 9025bd3 commit 0913358
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 22 deletions.
4 changes: 2 additions & 2 deletions korge-core/src/common/korlibs/image/vector/Context2d.kt
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,9 @@ open class Context2d(
inline fun skew(skewX: Angle = Angle.ZERO, skewY: Angle = Angle.ZERO, block: () -> Unit) =
keep { skew(skewX, skewY).also { block() } }

inline fun scale(sx: Float, sy: Float = sx, block: () -> Unit) = keep { scale(sx, sy).also { block() } }
inline fun scale(sx: Number, sy: Number = sx, block: () -> Unit) = keep { scale(sx.toDouble(), sy.toDouble()).also { block() } }
inline fun rotate(angle: Angle, block: () -> Unit) = keep { rotate(angle).also { block() } }
inline fun translate(tx: Float, ty: Float, block: () -> Unit) = keep { translate(tx, ty).also { block() } }
inline fun translate(tx: Number, ty: Number, block: () -> Unit) = keep { translate(tx.toDouble(), ty.toDouble()).also { block() } }

fun skew(skewX: Angle = 0.degrees, skewY: Angle = 0.degrees) {
state.transform = state.transform.preskewed(skewX, skewY)
Expand Down
27 changes: 19 additions & 8 deletions korge/src/common/korlibs/korge/ui/UIContainerLayouts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ inline fun Container.uiContainer(
) = UIContainer(size).addTo(this).apply(block)

open class UIContainer(size: Size) : UIBaseContainer(size) {
override fun relayout() {}
override fun relayoutInternal() {}
}

abstract class UIBaseContainer(size: Size) : UIView(size) {
Expand All @@ -121,7 +121,18 @@ abstract class UIBaseContainer(size: Size) : UIView(size) {
relayout()
}

abstract fun relayout()
private var doingRelayout = false
fun relayout() {
if (doingRelayout) return
doingRelayout = true
try {
relayoutInternal()
} finally {
doingRelayout = false
}
}

protected abstract fun relayoutInternal()

var deferredRendering: Boolean? = true
//var deferredRendering: Boolean? = false
Expand Down Expand Up @@ -155,7 +166,7 @@ open class UIVerticalStack(
}
}

override fun relayout() {
override fun relayoutInternal() {
var y = 0.0
var bb = BoundsBuilder()
forEachChild {
Expand Down Expand Up @@ -189,7 +200,7 @@ open class UIHorizontalStack(
}
}

override fun relayout() {
override fun relayoutInternal() {
var x = 0.0
var bb = BoundsBuilder()
forEachChild {
Expand Down Expand Up @@ -219,7 +230,7 @@ inline fun Container.uiHorizontalFill(
) = UIHorizontalFill(size).addTo(this).apply(block)

open class UIHorizontalFill(size: Size = Size(128, 20)) : UIContainer(size) {
override fun relayout() {
override fun relayoutInternal() {
var x = 0.0
val elementWidth = width / numChildren
forEachChild {
Expand All @@ -237,7 +248,7 @@ inline fun Container.uiVerticalFill(
) = UIVerticalFill(size).addTo(this).apply(block)

open class UIVerticalFill(size: Size = Size(128, 128)) : UIContainer(size) {
override fun relayout() {
override fun relayoutInternal() {
var y = 0.0
val elementHeight = height / numChildren
forEachChild {
Expand Down Expand Up @@ -273,7 +284,7 @@ open class UIGridFill(
@ViewProperty
var direction: UIDirection by UIObservable(direction) { relayout() }

override fun relayout() {
override fun relayoutInternal() {
val width = width
val height = height
val paddingH = spacing.horizontal
Expand Down Expand Up @@ -310,7 +321,7 @@ inline fun Container.uiFillLayeredContainer(
) = UIFillLayeredContainer(size).addTo(this).apply(block)

open class UIFillLayeredContainer(size: Size = Size(128, 20)) : UIContainer(size) {
override fun relayout() {
override fun relayoutInternal() {
val width = this.width
val height = this.height
forEachChild {
Expand Down
61 changes: 49 additions & 12 deletions korge/src/common/korlibs/korge/ui/UITreeView.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package korlibs.korge.ui

import korlibs.datastructure.*
import korlibs.datastructure.iterators.*
import korlibs.image.bitmap.*
import korlibs.image.color.*
import korlibs.korge.animate.*
import korlibs.korge.annotations.*
import korlibs.korge.input.*
import korlibs.korge.tween.*
import korlibs.korge.view.*
import korlibs.math.geom.*
import kotlin.time.Duration.Companion.seconds

@KorgeExperimental
class UITreeViewNode<T>(val element: T, val items: List<UITreeViewNode<T>> = emptyList()) {
Expand Down Expand Up @@ -64,7 +69,8 @@ interface UITreeViewProvider<T> {

@KorgeExperimental
private class UITreeViewVerticalListProviderAdapter<T>(val provider: UITreeViewProvider<T>) : UIVerticalList.Provider {
class Node<T>(val value: T, val localIndex: Int, val indentation: Int) {
class Node<T>(val value: T, val localIndex: Int, val indentation: Int, val parent: Node<T>?) {
val path: List<Node<T>> = (parent?.path ?: emptyList()) + listOf(this)
var opened: Boolean = false
var openCount: Int = 0
}
Expand All @@ -76,27 +82,58 @@ private class UITreeViewVerticalListProviderAdapter<T>(val provider: UITreeViewP
override val numItems: Int get() = items.size
override val fixedHeight: Double get() = provider.height

companion object {
val ICON_DOWN = NativeImageContext2d(10, 10) {
val sw = 4.0
val sh = 4.0
translate(width * 0.5, height * 0.5 + sh * 0.5) {
stroke(Colors.WHITE, 2.0) {
moveTo(-sw, +sh)
lineTo(0.0, -sh)
lineTo(+sw, +sh)
}
}
}
}

override fun getItemHeight(index: Int): Double = fixedHeight
override fun getItemView(index: Int, vlist: UIVerticalList): View {
val node = items[index]
val itemViews = vlist.extraCache("itemViewsCache") { LinkedHashMap<List<Node<T>>, View>() }
return itemViews.getOrPut(node.path) {
createItemView(index, vlist)
}
}
fun createItemView(index: Int, vlist: UIVerticalList): View {
//println("Creating new createItemView index=$index")
val node = items[index]
val childCount = provider.getNumChildren(node.value)
val container = UIFillLayeredContainer()
val background = container.solidRect(10, 10, Colors.TRANSPARENT)
val stack = container.uiHorizontalStack(padding = 2.0)
val stack = container.uiHorizontalStack(padding = 4.0)
val child = provider.getViewForNode(node.value)
stack.solidRect(10 * node.indentation, 10, Colors.TRANSPARENT)
val rect = stack.solidRect(10, 10, Colors.TRANSPARENT)
fun updateIcon() {
rect.color = when {
val imageContainer = stack.fixedSizeContainer(Size(10, 10))
val icon = imageContainer.image(ICON_DOWN).centered.xy((ICON_DOWN.size * 0.5).toDouble().toVector())
fun updateIcon(animated: Boolean) {
val isOpen: Boolean? = when {
childCount > 0 -> {
when {
node.opened -> Colors.GREEN
else -> Colors.RED
node.opened -> true
else -> false
}
}
else -> null
}

else -> Colors.TRANSPARENT
icon.visible = isOpen != null
val angle = if (isOpen == true) 90.degrees else 0.degrees
if (animated) {
icon.simpleAnimator.tween(icon::rotation[angle], time = 0.25.seconds)
} else {
icon.rotation = angle
}

background.color = when {
selectedNode == node -> Colors["#191034"]
else -> Colors.TRANSPARENT
Expand All @@ -111,13 +148,13 @@ private class UITreeViewVerticalListProviderAdapter<T>(val provider: UITreeViewP
selectedNode = node
if (node.hasChildren()) {
node.toggle()
updateIcon()
updateIcon(animated = true)
vlist.invalidateList()
} else {
vlist.invalidateList()
}
}
updateIcon()
updateIcon(animated = false)
return container
}

Expand Down Expand Up @@ -152,7 +189,7 @@ private class UITreeViewVerticalListProviderAdapter<T>(val provider: UITreeViewP
val nodeIndex = getNodeIndex(node)
val children = provider.getChildrenList(node.value)
node.openCount = children.size
items.addAll(nodeIndex + 1, children.mapIndexed { index, t -> Node(t, index, node.indentation + 1) })
items.addAll(nodeIndex + 1, children.mapIndexed { index, t -> Node(t, index, node.indentation + 1, node) })
}

fun toggleNode(node: Node<T>) {
Expand All @@ -162,7 +199,7 @@ private class UITreeViewVerticalListProviderAdapter<T>(val provider: UITreeViewP
fun init() {
items.clear()
provider.getChildrenList(null).fastForEachWithIndex { index, value ->
items.add(Node(value, index, 0))
items.add(Node(value, index, 0, null))
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions korge/test/common/korlibs/korge/ui/UITreeViewTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package korlibs.korge.ui

import korlibs.korge.annotations.*
import korlibs.korge.tests.*
import kotlin.test.*

@OptIn(KorgeExperimental::class)
class UITreeViewTest : ViewsForTesting() {
@Test
fun test() = viewsTest {
uiTooltipContainer { tooltips ->
uiTreeView(
UITreeViewList(
listOf(
UITreeViewNode("hello"),
UITreeViewNode(
"world",
UITreeViewNode("test"),
UITreeViewNode(
"demo",
UITreeViewNode("demo"),
UITreeViewNode("demo"),
UITreeViewNode(
"demo",
UITreeViewNode("demo")
),
),
),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
UITreeViewNode("hello"),
), height = 16.0, genView = {
UIText("$it").tooltip(tooltips, "Tooltip for $it")
})
)
}
}
}

0 comments on commit 0913358

Please sign in to comment.