-
-
Notifications
You must be signed in to change notification settings - Fork 14
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
Consider supporting a composable tree builder #1
Comments
Amazing! Thanks for that @zach-klippenstein. I'll learn from your code and incorporate into the library. Stay tuned! |
It works great 🎉 Now I'm trying to solve some issues related to expansion: 1. How to get the parent node?
@Composable
public fun <T> TreeScope<T>.Branch(
content: T,
name: String = content.toString(),
children: @Composable TreeScope<T>.() -> Unit = {}
) {
var node by remember { mutableStateOf<BranchNode<T>?>(null) }
ComposeNode<BranchNode<T>, TreeApplier<T>>(
factory = {
SimpleBranchNode(...)
.also { node = it }
},
update = { ... }
)
if (isExpanded) {
TreeScope(depth = depth.inc(), parent = node).children()
}
} I believe it will work, but couldn't test it yet because of the next issue. Will keep looking for a solution that doesn't depend on a state. 2. How to expand a node at X depth?
And private fun expandDown(nodes: List<Node<T>>, maxDepth: Int) {
nodes
.asSequence()
.filterIsInstance<BranchNode<T>>()
.filter { it.depth <= maxDepth }
.sortedBy { it.depth }
.forEach { it.setExpanded(true) }
} Any ideas? |
Another issue I'm trying to fix: When I try to use the following code I get @Composable
public fun <T> TreeScope<T>.Branch(...) {
ComposeNode<BranchNode<T>, TreeApplier<T>>(...)
AnimatedVisibility(visible = isExpanded) {
TreeScope(depth, parent).children()
}
} Unfortunatelly |
About the issue #2 (How to expand a node at X depth?), I'm using the @Composable
public fun <T> TreeScope.Branch(...) {
val (isExpanded, setExpanded) = rememberSaveable { mutableStateOf(isExpanded && depth <= expandMaxDepth) }
val (expandMaxDepth, setExpandMaxDepth) = rememberSaveable { mutableStateOf(expandMaxDepth) }
ComposeNode<BranchNode<T>, TreeApplier<T>>(
factory = {
BranchNode(...)
},
update = {
set(isExpanded) { this.isExpandedState = isExpanded }
set(setExpanded) {
this.onToggleExpanded = { isExpanded, maxDepth ->
setExpanded(isExpanded)
setExpandMaxDepth(maxDepth)
}
}
}
)
if (isExpanded && depth <= expandMaxDepth) {
TreeScope(
depth = depth.inc(),
isExpanded = isExpanded,
expandMaxDepth = expandMaxDepth
).children()
}
} |
Just released v1.2.0 with the custom composition. Still need to find a way to animate expanded/collapsed nodes (probably will wait for Seems Compose Multiplatform doesn't have Thanks again @zach-klippenstein, feel free to suggest more enhancements! |
Compose vs Plain Old DSL
The current DSL for building trees looks a lot like compose code:
Compose is, in fact, a tree-building library. The core runtime knows nothing about UI, it can be used to build any type of tree. For example, the
rememberVectorPainter
composable has acontent
parameter that emits not UI nodes, but vector primitives (think SVG elements).Since one of the main tasks of this library is to express a tree structure, Compose itself might be a good fit to use for the DSL. Some of the advantages over a simpler, non-compose approach are:
There's one other benefit of using Compose, which is that it makes it really easy to make better use of the
LazyColumn
that is actually used to structure the tree UI.Tree flattening
To get the full benefit of a
LazyColumn
, every expanded node in the tree (everything that has a "row" in the lazy column) should have its own lazy item so the column can effectively recycle and reuse bits of UI that aren't on the screen. To do that, you need to flatten a tree structure into a single list that can be processed by theLazyColumn
.This also happens to be one of the core jobs of Compose. It takes the call graph of Composable functions (a tree) and flattens them into a single array-like structure (the slot table). So we can take advantage of that to flatten a tree for the
LazyColumn
.Proof-of-concept
I've thrown together a quick sketch of how this could look. I didn't try too hard to make it match all the exact APIs of this library, but just wanted to show how a custom subcomposition could be used and consumed by the
LazyColumn
.The main entry point to the API in this demo is the
rememberLazyTree
function:Then, you can take the tree object returned by that function and pass it to a
LazyColumn
:This is probably something you'd want to hide from the public API of this library, but it shows how flexible and generic this approach can be (it could work in lazy rows, or even grids, I guess).
Code
Usage demo
Note the
@UiComposable
and@TreeNodeComposable
annotations that document to the reader, and tell the compiler, what type of nodes can be emitted by each composable function.Also, I omitted the kotlin DSL annotations from the
TreeBuilderScope
type, but you'd probably want to have those on there to make sure that children can't accidentally refer to the wrong scope and get the wrong depth.Implementation
The text was updated successfully, but these errors were encountered: