Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stack overflow on UITreeView #1919

Merged
merged 4 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
})
)
}
}
}
Loading