Tree

@Composable
fun <T> Tree(root: T, children: (T) -> List<T>, modifier: SwingModifier = SwingModifier, label: (T) -> @Nls String = { it.toString() }, hasChildren: (T) -> Boolean? = null, selectedPaths: Set<List<Int>>? = null, onSelectionChange: (Set<List<Int>>) -> Unit = {}, expandedPaths: Set<List<Int>>? = null, onExpansionChange: (Set<List<Int>>) -> Unit = {}, onWillExpand: (value: T, path: List<Int>) -> Boolean? = null, isEditable: Boolean = false, onNodeEdit: (value: T, path: List<Int>, newValue: Any?) -> Unit = { _, _, _ -> }, selectionMode: Int = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION, rootVisible: Boolean = true, showsRootHandles: Boolean? = null, rowHeight: Int? = null, visibleRowCount: Int = 20, toggleClickCount: Int = 2, nodeContent: @Composable TreeNodeScope.(value: T) -> Unit? = null)

A hierarchy the user opens and selects in: a JTree built from data, reporting what the user selects, opens, and edits.

The tree is described as data: root is the root value and children yields each value's child values, walked recursively to build the displayed structure; label renders each value's row text (its toString by default), and nodeContent renders a value's node as a composable of its own where a row is more than text. The structure reflects the last composition - changing the data the accessors return moves the tree on recompose. A tree follows the values its accessors answer with, so a value that recomposes the tree is what moves it: mutating in place a child list a value already handed over leaves the tree as it was until something else recomposes it. The child values a list hands over unchanged at its front and at its back keep the nodes they had, staying open if they were open and selected if they were selected while the rows around them shift; what lies between them settles by position, so there openness and selection stay with the place rather than following the value. Selection is declared with selectedPaths and expansion with expandedPaths, each path expressed as the chain of child indices from the root (so [] is the root, [0] its first child, [0, 2] that child's third child), and the user's changes to either arrive through onSelectionChange and onExpansionChange. Place it in a org.jetbrains.compose.swing.components.layout.ScrollPane to scroll.

ScrollPane {
content {
Tree(
root = fileSystem,
children = { it.entries },
label = { it.name },
selectedPaths = selection,
onSelectionChange = { selection = it },
expandedPaths = expansion,
onExpansionChange = { expansion = it },
)
}
}

onSelectionChange and onExpansionChange report the user's changes only; new data reaching the structure produces neither. A declared selection or expansion is the composition's state and is re-applied on every pass: it survives a new structure, and a user change the caller does not adopt does not stand. Undeclared, either belongs to the user alone - the library never imposes one, and what the user reached survives a new structure as well, the nodes they closed as much as the ones they opened. A node the new structure no longer has is the exception: it leaves the selection, and onSelectionChange reports what is left of it.

A selection and an expansion that cannot both stand settle the way a JTree settles them: a closed node shows none of its descendants, so it holds the selection in place of the ones it hides, and onSelectionChange reports what the tree was left with.

hasChildren decides which values are branches. A value children yields nothing for is a leaf, with no handle to click; declaring hasChildren lets such a value call itself a branch all the same, so the user can ask for children the data does not hold yet and onWillExpand - or onExpansionChange - is where fetching them starts. onWillExpand also decides whether a node opens at all: returning false leaves it closed.

Editing is a report, never a mutation. While isEditable is on the user can edit a node's text in place, and committing it hands onNodeEdit the value edited, its index path, and what was entered; the row goes on showing what the data says until a later composition supplies data that says otherwise, and the tree root and children describe is never written to.

Parameters

root

the root value of the tree

children

yields the child values of a value, in display order

modifier

the SwingModifier applied to the underlying component

label

renders a value's row text; a value's toString by default

hasChildren

whether a value is a branch, asked for a value children yields none for; a value with children is a branch either way. null - the default - makes a childless value a leaf

selectedPaths

the selected nodes as index paths from the root; null - the default - leaves the selection to the user

onSelectionChange

callback invoked when the user changes the selection

expandedPaths

the expanded nodes as index paths from the root; every other node is collapsed, except an ancestor of an expanded one, which stays expanded so that node is reachable. null - the default - leaves expansion to the tree and to the user

onExpansionChange

callback invoked when the user expands or collapses a node, receiving every node that is then expanded

onWillExpand

asked before a node opens - whether the user opened it or a declared expansion did

  • with the value and the index path of that node, and vetoes the expansion by returning false; null - the default - lets every expansion through

isEditable

whether the user can edit a node's text in place; false - the default - leaves the nodes read-only

onNodeEdit

callback invoked when an edit is committed, receiving the value edited, its index path, and the value entered; update the backing data from here so the next composition shows the edit. An edit still open on a node a later composition hands another value to - or takes out of the structure - ends there and commits nothing

selectionMode

how many nodes may be selected; DISCONTIGUOUS_TREE_SELECTION - the default - lets the user select any number of them

rootVisible

whether the root node is shown; true - the default - shows it, and hiding it leaves its children as the top-level rows

showsRootHandles

whether expand/collapse handles are shown for the top-level nodes; null leaves the choice to the installed look and feel

rowHeight

the height of every row in pixels; 0 asks each node's rendering how tall it wants to be, which is what lets a composable node size itself. null - the default - leaves the height to the installed look and feel

visibleRowCount

how many rows the tree asks a viewport to make room for; 20 is the default, and it has no effect outside a scroll pane

toggleClickCount

how many clicks on a node expand or collapse it; 2 - the default - is a double click, and 0 takes that gesture away

nodeContent

optional composable node rendered per row against a TreeNodeScope; null - the default - renders each node's label through the renderer the tree carries

See also


@Composable
fun <T> Tree(root: T, children: (T) -> List<T>, treeSelectionListener: TreeSelectionListener, modifier: SwingModifier = SwingModifier, label: (T) -> @Nls String = { it.toString() }, hasChildren: (T) -> Boolean? = null, selectedPaths: Set<List<Int>>? = null, expandedPaths: Set<List<Int>>? = null, treeExpansionListener: TreeExpansionListener? = null, treeWillExpandListener: TreeWillExpandListener? = null, isEditable: Boolean = false, onNodeEdit: (value: T, path: List<Int>, newValue: Any?) -> Unit = { _, _, _ -> }, selectionMode: Int = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION, rootVisible: Boolean = true, showsRootHandles: Boolean? = null, rowHeight: Int? = null, visibleRowCount: Int = 20, toggleClickCount: Int = 2, nodeContent: @Composable TreeNodeScope.(value: T) -> Unit? = null)

A Tree driven by raw listeners instead of the onSelectionChange/onExpansionChange/onWillExpand lambdas. A listener is notified of the user's changes only - the selection listener also of a selection a new structure took away from the user - and is removed on the same instance; pass a stable instance (e.g. remember {}) to avoid churn. The will-expand listener hears more than the user: it is announced every expansion and every collapse, the ones a declaration applies as much as the ones the user asks for, and vetoes the one it refuses by throwing an ExpandVetoException.

Parameters

root

the root value of the tree

children

yields the child values of a value, in display order

treeSelectionListener

the listener notified of the user's selection changes

modifier

the SwingModifier applied to the underlying component

label

renders a value's row text; a value's toString by default

hasChildren

whether a value is a branch, asked for a value children yields none for; a value with children is a branch either way. null - the default - makes a childless value a leaf

selectedPaths

the selected nodes as index paths from the root; null - the default - leaves the selection to the user

expandedPaths

the expanded nodes as index paths from the root; every other node is collapsed, except an ancestor of an expanded one, which stays expanded so that node is reachable. null - the default - leaves expansion to the tree and to the user

treeExpansionListener

the listener notified of the user's expansions and collapses; null installs none

treeWillExpandListener

the listener announced each expansion and collapse before it happens; null installs none

isEditable

whether the user can edit a node's text in place; false - the default - leaves the nodes read-only

onNodeEdit

callback invoked when an edit is committed, receiving the value edited, its index path, and the value entered; update the backing data from here so the next composition shows the edit. An edit still open on a node a later composition hands another value to - or takes out of the structure - ends there and commits nothing

selectionMode

how many nodes may be selected; DISCONTIGUOUS_TREE_SELECTION - the default - lets the user select any number of them

rootVisible

whether the root node is shown; true - the default - shows it, and hiding it leaves its children as the top-level rows

showsRootHandles

whether expand/collapse handles are shown for the top-level nodes; null leaves the choice to the installed look and feel

rowHeight

the height of every row in pixels; 0 asks each node's rendering how tall it wants to be, which is what lets a composable node size itself. null - the default - leaves the height to the installed look and feel

visibleRowCount

how many rows the tree asks a viewport to make room for; 20 is the default, and it has no effect outside a scroll pane

toggleClickCount

how many clicks on a node expand or collapse it; 2 - the default - is a double click, and 0 takes that gesture away

nodeContent

optional composable node rendered per row against a TreeNodeScope; null - the default - renders each node's label through the renderer the tree carries

See also


@Composable
fun Tree(model: TreeModel, modifier: SwingModifier = SwingModifier, selectedPaths: Set<List<Int>>? = null, onSelectionChange: (Set<List<Int>>) -> Unit = {}, expandedPaths: Set<List<Int>>? = null, onExpansionChange: (Set<List<Int>>) -> Unit = {}, selectionMode: Int = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION, rootVisible: Boolean = true, showsRootHandles: Boolean? = null, rowHeight: Int? = null, visibleRowCount: Int = 20, toggleClickCount: Int = 2)

A hierarchy the user opens and selects in, over a TreeModel the caller owns: a JTree reporting what the user selects and opens.

The model is displayed as-is: its own nodes and structure drive the tree, and the library never mutates it. Supplying a new model instance swaps it into the tree on recomposition. Selection is declared with selectedPaths and expansion with expandedPaths, each path expressed as the chain of child indices from the root (so [] is the root, [0] its first child, [0, 2] that child's third child); the indices are resolved through the model's own accessors, so any TreeModel works. Both survive a model swap, declared or not. Place it in a org.jetbrains.compose.swing.components.layout.ScrollPane to scroll.

ScrollPane {
content {
Tree(
model = fileSystemModel,
selectedPaths = selection,
onSelectionChange = { selection = it },
)
}
}

onSelectionChange and onExpansionChange report the user's changes only; installing a new model produces neither. A declared selection or expansion is the composition's state and is re-applied on every pass, so a user change the caller does not adopt does not stand; undeclared, either belongs to the user alone and is never imposed - the nodes the user closed stay closed across a model swap as surely as the ones they opened stay open. A node the new model does not have is the exception: it leaves the selection, and onSelectionChange reports what is left of it.

Parameters

model

the tree model to display; owned by the caller and never mutated by the library

modifier

the SwingModifier applied to the underlying component

selectedPaths

the selected nodes as index paths from the root; null - the default - leaves the selection to the user

onSelectionChange

callback invoked when the user changes the selection

expandedPaths

the expanded nodes as index paths from the root; every other node is collapsed, except an ancestor of an expanded one, which stays expanded so that node is reachable. null - the default - leaves expansion to the tree and to the user

onExpansionChange

callback invoked when the user expands or collapses a node, receiving every node that is then expanded

selectionMode

how many nodes may be selected; DISCONTIGUOUS_TREE_SELECTION - the default - lets the user select any number of them

rootVisible

whether the root node is shown; true - the default - shows it, and hiding it leaves its children as the top-level rows

showsRootHandles

whether expand/collapse handles are shown for the top-level nodes; null leaves the choice to the installed look and feel

rowHeight

the height of every row in pixels; 0 asks each node's rendering how tall it wants to be. null - the default - leaves the height to the installed look and feel

visibleRowCount

how many rows the tree asks a viewport to make room for; 20 is the default, and it has no effect outside a scroll pane

toggleClickCount

how many clicks on a node expand or collapse it; 2 - the default - is a double click, and 0 takes that gesture away

See also


@Composable
fun Tree(model: TreeModel, treeSelectionListener: TreeSelectionListener, modifier: SwingModifier = SwingModifier, selectedPaths: Set<List<Int>>? = null, expandedPaths: Set<List<Int>>? = null, treeExpansionListener: TreeExpansionListener? = null, selectionMode: Int = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION, rootVisible: Boolean = true, showsRootHandles: Boolean? = null, rowHeight: Int? = null, visibleRowCount: Int = 20, toggleClickCount: Int = 2)

A model-driven Tree driven by raw listeners instead of the onSelectionChange/onExpansionChange lambdas. A listener is notified of the user's changes only - the selection listener also of a selection a new model took away from the user - and is removed on the same instance; pass a stable instance (e.g. remember {}) to avoid churn.

The model is displayed as-is and never mutated by the library; a selection and an expansion survive a model swap, declared or not.

Parameters

model

the tree model to display; owned by the caller and never mutated by the library

treeSelectionListener

the listener notified of the user's selection changes

modifier

the SwingModifier applied to the underlying component

selectedPaths

the selected nodes as index paths from the root; null - the default - leaves the selection to the user

expandedPaths

the expanded nodes as index paths from the root; every other node is collapsed, except an ancestor of an expanded one, which stays expanded so that node is reachable. null - the default - leaves expansion to the tree and to the user

treeExpansionListener

the listener notified of the user's expansions and collapses; null installs none

selectionMode

how many nodes may be selected; DISCONTIGUOUS_TREE_SELECTION - the default - lets the user select any number of them

rootVisible

whether the root node is shown; true - the default - shows it, and hiding it leaves its children as the top-level rows

showsRootHandles

whether expand/collapse handles are shown for the top-level nodes; null leaves the choice to the installed look and feel

rowHeight

the height of every row in pixels; 0 asks each node's rendering how tall it wants to be. null - the default - leaves the height to the installed look and feel

visibleRowCount

how many rows the tree asks a viewport to make room for; 20 is the default, and it has no effect outside a scroll pane

toggleClickCount

how many clicks on a node expand or collapse it; 2 - the default - is a double click, and 0 takes that gesture away

See also


@Composable
fun <T> Tree(root: T, children: (T) -> List<T>, state: TreeState, modifier: SwingModifier = SwingModifier, label: (T) -> @Nls String = { it.toString() }, hasChildren: (T) -> Boolean? = null, onWillExpand: (value: T, path: List<Int>) -> Boolean? = null, isEditable: Boolean = false, onNodeEdit: (value: T, path: List<Int>, newValue: Any?) -> Unit = { _, _, _ -> }, selectionMode: Int = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION, rootVisible: Boolean = true, showsRootHandles: Boolean? = null, rowHeight: Int? = null, visibleRowCount: Int = 20, toggleClickCount: Int = 2, nodeContent: @Composable TreeNodeScope.(value: T) -> Unit? = null)

A Tree driven by a TreeState instead of declared selectedPaths/expandedPaths and the onSelectionChange/onExpansionChange lambdas. The state owns both facets: the nodes it holds are what the tree shows selected and open, the user's own selecting and opening is written back into it, and it is where a node is revealed from.

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

ScrollPane {
Tree(root = fileSystem, children = { it.entries }, state = state, modifier = SwingModifier.viewport())
}
Label("Selected: ${describe(state.selectedPaths)}")

Parameters

root

the root value of the tree

children

yields the child values of a value, in display order

state

the hoistable selection and expansion state the tree applies and reports into; see TreeState

modifier

the SwingModifier applied to the underlying component

label

renders a value's row text; a value's toString by default

hasChildren

whether a value is a branch, asked for a value children yields none for; a value with children is a branch either way. null - the default - makes a childless value a leaf

onWillExpand

asked before a node opens - whether the user opened it or the state's expansion did

  • with the value and the index path of that node, and vetoes the expansion by returning false; null - the default - lets every expansion through

isEditable

whether the user can edit a node's text in place; false - the default - leaves the nodes read-only

onNodeEdit

callback invoked when an edit is committed, receiving the value edited, its index path, and the value entered; update the backing data from here so the next composition shows the edit. An edit still open on a node a later composition hands another value to - or takes out of the structure - ends there and commits nothing

selectionMode

how many nodes may be selected; DISCONTIGUOUS_TREE_SELECTION - the default - lets the user select any number of them

rootVisible

whether the root node is shown; true - the default - shows it, and hiding it leaves its children as the top-level rows

showsRootHandles

whether expand/collapse handles are shown for the top-level nodes; null leaves the choice to the installed look and feel

rowHeight

the height of every row in pixels; 0 asks each node's rendering how tall it wants to be, which is what lets a composable node size itself. null - the default - leaves the height to the installed look and feel

visibleRowCount

how many rows the tree asks a viewport to make room for; 20 is the default, and it has no effect outside a scroll pane

toggleClickCount

how many clicks on a node expand or collapse it; 2 - the default - is a double click, and 0 takes that gesture away

nodeContent

optional composable node rendered per row against a TreeNodeScope; null - the default - renders each node's label through the renderer the tree carries

See also


@Composable
fun Tree(model: TreeModel, state: TreeState, modifier: SwingModifier = SwingModifier, selectionMode: Int = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION, rootVisible: Boolean = true, showsRootHandles: Boolean? = null, rowHeight: Int? = null, visibleRowCount: Int = 20, toggleClickCount: Int = 2)

A model-driven Tree driven by a TreeState instead of declared selectedPaths/expandedPaths and the onSelectionChange/onExpansionChange lambdas. The state owns both facets: the nodes it holds are what the tree shows selected and open, the user's own selecting and opening is written back into it, and it is where a node is revealed from.

The model is displayed as-is and never mutated by the library; the selection and the expansion survive a model swap.

Parameters

model

the tree model to display; owned by the caller and never mutated by the library

state

the hoistable selection and expansion state the tree applies and reports into; see TreeState

modifier

the SwingModifier applied to the underlying component

selectionMode

how many nodes may be selected; DISCONTIGUOUS_TREE_SELECTION - the default - lets the user select any number of them

rootVisible

whether the root node is shown; true - the default - shows it, and hiding it leaves its children as the top-level rows

showsRootHandles

whether expand/collapse handles are shown for the top-level nodes; null leaves the choice to the installed look and feel

rowHeight

the height of every row in pixels; 0 asks each node's rendering how tall it wants to be. null - the default - leaves the height to the installed look and feel

visibleRowCount

how many rows the tree asks a viewport to make room for; 20 is the default, and it has no effect outside a scroll pane

toggleClickCount

how many clicks on a node expand or collapse it; 2 - the default - is a double click, and 0 takes that gesture away

See also