TreeState

@Stable
class TreeState @RememberInComposition constructor(initialSelectedPaths: Set<List<Int>> = emptySet(), initialExpandedPaths: Set<List<Int>> = emptySet())

A hoistable state holder for what a Tree has selected and what it has open, carrying the gesture that brings one of its nodes into view.

Each path is the chain of child indices from the root, so [] is the root, [0] its first child, and [0, 2] that child's third child.

selectedPaths and expandedPaths are two-way: assigning either applies it to the tree, and the user selecting a node or opening one writes the change back here. Both are snapshot-observable, so reading one inside a composable (or a snapshotFlow collector) subscribes to the user's later changes as well.

What this state names is the composition's own and is re-applied on every pass: a tree driven by a state stands on exactly the nodes the state holds, so a state starting on the empty expansion opens nothing - start it on setOf(emptyList()) for a tree that opens on its root. A node the structure does not have is left out of the tree while it goes on being named here - a structure that has it again shows it selected, or open.

revealPath brings one node into view when the application decides to - a search hit, a node a load has just filled in:

val state = rememberTreeState(initialExpandedPaths = setOf(emptyList()))

Button("Find", onClick = { state.revealPath(search(query)) })
ScrollPane {
Tree(root = root, children = ::childrenOf, state = state, modifier = SwingModifier.viewport())
}

rowCount, isExpanded and shownSelectedPaths answer for the tree instead of for what this state holds. Each reads the bound tree where it is called, so what it reports is what the tree stands on - which is not always what was declared, since a structure can drop a node and a closed node cannot show its descendants selected. They are not snapshot state, so reading one subscribes to nothing; a composable that has to follow the user reads selectedPaths and expandedPaths. An unbound state has no tree to answer for and reports no rows, nothing open and nothing selected.

A state drives at most one tree: passing it to a second one moves it there and leaves the first unbound.

Parameters

initialSelectedPaths

the nodes selected until the caller or the user moves the selection.

initialExpandedPaths

the nodes open until the caller or the user opens or closes one.

See also

Constructors

Link copied to clipboard
@RememberInComposition
constructor(initialSelectedPaths: Set<List<Int>> = emptySet(), initialExpandedPaths: Set<List<Int>> = emptySet())

Properties

Link copied to clipboard

The open nodes as index paths from the root; every other node is collapsed, except an ancestor of an open one, which stays open so that node is reachable.

Link copied to clipboard

How many rows the tree shows: every node whose ancestors are all open, and the root itself while it is shown. 0 while no tree is bound.

Link copied to clipboard

The selected nodes as index paths from the root.

Link copied to clipboard

The nodes the tree has selected, as index paths from the root. Empty while no tree is bound.

Functions

Link copied to clipboard
fun isExpanded(path: List<Int>): Boolean

Whether the tree shows the children of the node path names below it. false for a node no structure the tree currently shows has, for a node under a closed one, and while no tree is bound.

Link copied to clipboard
fun revealPath(path: List<Int>): Boolean

Brings the node path names into view, opening every ancestor that hides it, and returns whether it was reached.