adrielcafe / bonsai

:deciduous_tree: A multiplatform tree view for Jetpack Compose
MIT License
355 stars 13 forks source link
android android-library android-ui compose desktop expandable jetpack-compose kotlin kotlin-android kotlin-dsl kotlin-multiplatform selectable tree tree-structure treeview

Maven metadata URL Android API kotlin ktlint License MIT


Bonsai

A batteries-included Tree View for Jetpack Compose

Features

Roadmap

Import to your project

Add the desired dependencies to your module's build.gradle:

implementation "cafe.adriel.bonsai:bonsai-core:${latest-version}"
implementation "cafe.adriel.bonsai:bonsai-file-system:${latest-version}"
implementation "cafe.adriel.bonsai:bonsai-json:${latest-version}"

Current version: Maven metadata URL

Usage

Bonsai comes with a handy DSL for creating high-performance, customizable trees:

  1. Start by creating a new tree with Tree<T>{}
  2. Create nodes with Leaf<T>() and Branch<T>()
  3. Call Bonsai() to render the tree

    @Composable
    fun BonsaiExample() {
    val tree = Tree {
        Branch("Mammalia") {
            Branch("Carnivora") {
                Branch("Canidae") {
                    Branch("Canis") {
                        Leaf("Wolf", customIcon = { EmojiIcon("🐺") })
                        Leaf("Dog", customIcon = { EmojiIcon("🐶") })
                    }
                }
                Branch("Felidae") {
                    Branch("Felis") {
                        Leaf("Cat", customIcon = { EmojiIcon("🐱") })
                    }
                    Branch("Panthera") {
                        Leaf("Lion", customIcon = { EmojiIcon("🦁") })
                    }
                }
            }
        }
    }
    
    Bonsai(tree)
    }

Output:

Take a look at the sample app for working examples.

File System integration

Import cafe.adriel.bonsai:bonsai-file-system module to use it.

val tree = FileSystemTree(
    // Also works with java.nio.file.Path and okio.Path
    rootPath = File(path),
    // To show or not the root directory in the tree
    selfInclude = true
)

Bonsai(
    tree = tree,
    // Custom style
    style = FileSystemBonsaiStyle()
)

Output:

JSON integration

Import cafe.adriel.bonsai:bonsai-json module to use it.

val tree = JsonTree(
    // Sample JSON from https://gateway.marvel.com/v1/public/characters
    json = responseJson
)

Bonsai(
    tree = tree,
    // Custom style
    style = JsonBonsaiStyle()
)

Output:

Expanding & Collapsing

Easily control the expanded/collapsed state of your Tree:

Selecting

Selected/Unselected state is also pretty simple to control:

Click handling

Its also possible to set custom click behaviors for your Tree. Control single, double and long clicks by using the expand and select APIs.

Bonsai(
    tree = tree,
    onClick = { node ->
        tree.clearSelection()
        tree.toggleExpansion(node)
    },
    onDoubleClick = { node -> /* ... */ },
    onLongClick = { node -> /* ... */ }
)

Styling

Change your Tree appearance as you wish. Take a look at BonsaiStyle class for all available customizations.

Bonsai(
    tree = tree,
    style = BonsaiStyle(
        toggleIconRotationDegrees = 0f,
        toggleIcon = { node ->
            rememberVectorPainter(
                if (node is BranchNode && node.isExpanded) Icons.Outlined.UnfoldLess
                else Icons.Outlined.UnfoldMore
            )
        },
        nodeIconSize = 18.dp,
        nodeShape = CutCornerShape(percent = 20),
        nodeCollapsedIcon = { rememberVectorPainter(Icons.Outlined.Circle) },
        nodeExpandedIcon = { rememberVectorPainter(Icons.Outlined.Adjust) },
        nodeNameTextStyle = MaterialTheme.typography.overline
    )
)

Output:

Custom nodes

Need a deeper customization? You can set customIcons and customNames for each Leaf<T>() and Branch<T>() nodes.

Leaf(
    content = "Wolf", 
    customIcon = { EmojiIcon("🐺") }
)

Output: