From e2d279a8b181d5b6d174d69beef3e96f17dfafd6 Mon Sep 17 00:00:00 2001 From: Elie G Date: Tue, 10 Dec 2024 21:42:47 +0200 Subject: [PATCH 1/3] Add TreeView component and navigation screen Introduce a reusable TreeView component for hierarchical data display with expandable and collapsible nodes. Implemented the corresponding TreeViewScreen in the gallery for demonstration purposes. Includes a tree builder DSL for easy tree structure creation and ensures visual consistency with Fluent design. --- .../com/konyaco/fluent/component/Tree.kt | 314 ++++++++++++++++++ .../screen/navigation/TreeViewScreen.kt | 60 ++++ 2 files changed, 374 insertions(+) create mode 100644 fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt create mode 100644 gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt new file mode 100644 index 00000000..2bf78abf --- /dev/null +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt @@ -0,0 +1,314 @@ +package com.konyaco.fluent.component + +import androidx.compose.animation.* +import androidx.compose.animation.core.EaseInOut +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import com.konyaco.fluent.FluentTheme +import com.konyaco.fluent.animation.FluentDuration +import com.konyaco.fluent.icons.Icons +import com.konyaco.fluent.icons.regular.ChevronDown +import com.konyaco.fluent.icons.regular.ChevronRight +import com.konyaco.fluent.scheme.PentaVisualScheme +import com.konyaco.fluent.scheme.VisualState +import com.konyaco.fluent.scheme.VisualStateScheme +import com.konyaco.fluent.scheme.collectVisualState + +// ------------------------------------- +// Data structures and tree builder +// ------------------------------------- + +// Represents a tree composed of multiple root elements +data class Tree(val roots: List>) { + fun isEmpty() = roots.isEmpty() +} + +// Represents an element in the tree (either a Leaf or a Node) +sealed class TreeElement { + abstract val data: T + abstract val id: Any + abstract val children: List> + abstract val depth: Int + val isLeaf: Boolean get() = children.isEmpty() + + data class Leaf( + override val data: T, + override val id: Any, + override val depth: Int + ) : TreeElement() { + override val children: List> = emptyList() + } + + data class Node( + override val data: T, + override val id: Any, + override val depth: Int, + override val children: List> + ) : TreeElement() +} + +// Builder DSL to construct trees in a declarative manner +fun buildTree(block: TreeBuilder.() -> Unit): Tree { + val builder = TreeBuilder() + builder.block() + return Tree(builder.buildRoots()) +} + +// Tree builder that helps create roots and their descendants +class TreeBuilder { + private val elements = mutableListOf>() + + fun node(data: T, id: Any = data.toString(), children: NodeBuilder.() -> Unit) { + val childBuilder = NodeBuilder(0) + childBuilder.children() + elements.add(BuilderElement.Node(data, id, childBuilder.elements, 0)) + } + + fun leaf(data: T, id: Any = data.toString()) { + elements.add(BuilderElement.Leaf(data, id, 0)) + } + + internal fun buildRoots(): List> = elements.map { it.buildElement(depth = 0) } + + sealed class BuilderElement { + abstract fun buildElement(depth: Int): TreeElement + + data class Leaf(val data: T, val id: Any, val depthInit: Int) : BuilderElement() { + override fun buildElement(depth: Int): TreeElement { + return TreeElement.Leaf(data, id, depth) + } + } + + data class Node( + val data: T, + val id: Any, + val children: List>, + val depthInit: Int + ) : BuilderElement() { + override fun buildElement(depth: Int): TreeElement { + val builtChildren = children.map { it.buildElement(depth + 1) } + return TreeElement.Node(data, id, depth, builtChildren) + } + } + } +} + +// Builder for adding children under a node +class NodeBuilder(private val parentDepth: Int) { + internal val elements = mutableListOf>() + + fun node(data: T, id: Any = data.toString(), children: NodeBuilder.() -> Unit) { + val childBuilder = NodeBuilder(parentDepth + 1) + childBuilder.children() + elements.add(TreeBuilder.BuilderElement.Node(data, id, childBuilder.elements, parentDepth + 1)) + } + + fun leaf(data: T, id: Any = data.toString()) { + elements.add(TreeBuilder.BuilderElement.Leaf(data, id, parentDepth + 1)) + } +} + +// ------------------------------------- +// Tree Node Color Scheme +// ------------------------------------- + +@Immutable +data class TreeNodeColor( + val backgroundColor: Color, + val contentColor: Color, + val borderColor: Color, + val labelTextColor: Color +) + +typealias TreeNodeColorScheme = PentaVisualScheme + +object TreeNodeDefaults { + @Stable + @Composable + fun defaultNodeColors( + default: TreeNodeColor = TreeNodeColor( + backgroundColor = Color.Transparent, + contentColor = FluentTheme.colors.text.onAccent.primary, + borderColor = FluentTheme.colors.controlStrong.default, + labelTextColor = FluentTheme.colors.text.text.primary + ), + hovered: TreeNodeColor = TreeNodeColor( + backgroundColor = FluentTheme.colors.controlAlt.tertiary, + contentColor = FluentTheme.colors.text.onAccent.primary, + borderColor = FluentTheme.colors.controlStrong.default, + labelTextColor = FluentTheme.colors.text.text.primary + ), + pressed: TreeNodeColor = TreeNodeColor( + backgroundColor = FluentTheme.colors.controlAlt.quaternary, + contentColor = FluentTheme.colors.text.onAccent.secondary, + borderColor = FluentTheme.colors.controlStrong.default, + labelTextColor = FluentTheme.colors.text.text.primary + ), + disabled: TreeNodeColor = TreeNodeColor( + backgroundColor = FluentTheme.colors.controlAlt.disabled, + contentColor = FluentTheme.colors.text.onAccent.disabled, + borderColor = FluentTheme.colors.controlStrong.disabled, + labelTextColor = FluentTheme.colors.text.text.disabled + ) + ) = TreeNodeColorScheme( + default = default, + hovered = hovered, + pressed = pressed, + disabled = disabled + ) +} + +@Composable +fun TreeNodeColorScheme.schemeFor(state: VisualState): TreeNodeColor { + return when (state) { + VisualState.Default -> default + VisualState.Hovered -> hovered + VisualState.Pressed -> pressed + VisualState.Disabled -> disabled + VisualState.Focused -> focused + } +} + +// ------------------------------------- +// Tree composables +// ------------------------------------- + +/** + * Displays the entire tree recursively using a Column. + * This approach removes code duplication and uses a single function to handle nodes and their children. + */ +@Composable +fun TreeView( + tree: Tree, + modifier: Modifier = Modifier, + nodeColors: VisualStateScheme = TreeNodeDefaults.defaultNodeColors() +) { + Column(modifier) { + tree.roots.forEach { element -> + TreeElementView(element, nodeColors) + } + } +} + +/** + * Recursively displays a tree element (node or leaf). + * If it is a node, it can be expanded or collapsed to show its children. + */ +@Composable +fun TreeElementView( + element: TreeElement, + colors: VisualStateScheme +) { + val isNode = element is TreeElement.Node + val expandedState = if (isNode) remember { mutableStateOf(false) } else null + + // Display the current node line + TreeNodeView( + element = element, + colors = colors, + onClick = { + if (isNode) expandedState!!.value = !expandedState.value + } + ) + + // If it's a node and expanded, show its children with animation + if (isNode) { + val node = element as TreeElement.Node + AnimatedVisibility( + visible = expandedState!!.value, + enter = fadeIn() + expandVertically( + animationSpec = tween( + durationMillis = FluentDuration.QuickDuration, + easing = EaseInOut + ) + ), + exit = fadeOut() + shrinkVertically( + animationSpec = tween( + durationMillis = FluentDuration.QuickDuration, + easing = EaseInOut + ) + ) + ) { + Column { + node.children.forEach { child -> + TreeElementView(child, colors) + } + } + } + } +} + +/** + * Displays a single tree node/leaf line. + * Handles the clickable area, indentation, and icon display. + */ +@Composable +fun TreeNodeView( + element: TreeElement, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: VisualStateScheme, + onClick: (() -> Unit)? = null +) { + val interactionSource = remember { MutableInteractionSource() } + val visualState = interactionSource.collectVisualState(!enabled) + val color = colors.schemeFor(visualState) + + val indentation = (element.depth * 16).dp + val isNode = element is TreeElement.Node && element.children.isNotEmpty() + + // Local expanded state for icons only (true/false), not triggering recursion here directly + val expandedState = if (isNode) remember { mutableStateOf(false) } else null + + Row( + modifier = modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 32.dp) + .clip(RoundedCornerShape(8.dp)) + .hoverable(interactionSource) + .background(if (visualState == VisualState.Hovered) color.backgroundColor else Color.Transparent) + .clickable( + enabled = enabled && isNode, + role = Role.Button, + indication = null, + interactionSource = interactionSource + ) { + expandedState?.value = !(expandedState?.value ?: false) + onClick?.invoke() + }, + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(Modifier.padding(start = 4.dp).width(indentation)) + + val icon = when { + isNode -> if (expandedState?.value == true) Icons.Regular.ChevronDown else Icons.Regular.ChevronRight + else -> null + } + + if (icon != null) { + Box(Modifier.size(20.dp), contentAlignment = Alignment.Center) { + Icon(imageVector = icon, contentDescription = null) + } + } else { + Spacer(Modifier.size(20.dp)) + } + + Spacer(Modifier.width(8.dp)) + Text( + text = element.data.toString(), + style = FluentTheme.typography.body.copy(color = color.labelTextColor) + ) + } +} diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt new file mode 100644 index 00000000..e0e17298 --- /dev/null +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt @@ -0,0 +1,60 @@ +package com.konyaco.fluent.gallery.screen.navigation + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.konyaco.fluent.component.TreeView +import com.konyaco.fluent.component.buildTree +import com.konyaco.fluent.gallery.annotation.Component +import com.konyaco.fluent.gallery.annotation.Sample +import com.konyaco.fluent.gallery.component.ComponentPagePath +import com.konyaco.fluent.gallery.component.GalleryPage +import com.konyaco.fluent.source.generated.FluentSourceFile + + +@Component( + index = 2, + description = "A control that displays a hierarchical structure of items that can be expanded or collapsed." +) +@Composable +fun TreeViewScreen() { + GalleryPage( + title = "TreeView", + description = "A TreeView provides a hierarchical structure of items that can be expanded or collapsed.", + componentPath = FluentSourceFile.Tree, + galleryPath = ComponentPagePath.TreeViewScreen + ) { + Section( + title = "Basic TreeView", + sourceCode = sourceCodeOfTreeViewSample, + content = { TreeViewSample() } + ) + } +} + + +@Sample +@Composable +fun TreeViewSample() { + val tree = remember { + buildTree { + node("Root") { + node("Folder 1") { + leaf("File 1-1") + leaf("File 1-2") + node("Folder 1-3") { + leaf("File 1-3-1") + } + } + node("Folder 2") { + leaf("File 2-1") + } + } + } + } + TreeView( + tree = tree, + modifier = Modifier.fillMaxWidth() + ) +} From f5d6d72709aa0b6aaadc540444f85a352c09ea20 Mon Sep 17 00:00:00 2001 From: Elie G Date: Tue, 10 Dec 2024 23:07:35 +0200 Subject: [PATCH 2/3] Add `onClick` support for tree nodes and leaves Introduced an optional `onClick` callback to `TreeElement` (both nodes and leaves) to handle custom click actions. Updated the tree builder DSL and composables to utilize this functionality, enabling more interactive tree components. --- .../com/konyaco/fluent/component/Tree.kt | 53 +++++++------------ .../screen/navigation/TreeViewScreen.kt | 4 +- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt index 2bf78abf..f9817af8 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt @@ -30,23 +30,23 @@ import com.konyaco.fluent.scheme.collectVisualState // Data structures and tree builder // ------------------------------------- -// Represents a tree composed of multiple root elements data class Tree(val roots: List>) { fun isEmpty() = roots.isEmpty() } -// Represents an element in the tree (either a Leaf or a Node) sealed class TreeElement { abstract val data: T abstract val id: Any abstract val children: List> abstract val depth: Int + abstract val onClick: (() -> Unit)? val isLeaf: Boolean get() = children.isEmpty() data class Leaf( override val data: T, override val id: Any, - override val depth: Int + override val depth: Int, + override val onClick: (() -> Unit)? ) : TreeElement() { override val children: List> = emptyList() } @@ -55,29 +55,28 @@ sealed class TreeElement { override val data: T, override val id: Any, override val depth: Int, - override val children: List> + override val children: List>, + override val onClick: (() -> Unit)? ) : TreeElement() } -// Builder DSL to construct trees in a declarative manner fun buildTree(block: TreeBuilder.() -> Unit): Tree { val builder = TreeBuilder() builder.block() return Tree(builder.buildRoots()) } -// Tree builder that helps create roots and their descendants class TreeBuilder { private val elements = mutableListOf>() - fun node(data: T, id: Any = data.toString(), children: NodeBuilder.() -> Unit) { + fun node(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null, children: NodeBuilder.() -> Unit) { val childBuilder = NodeBuilder(0) childBuilder.children() - elements.add(BuilderElement.Node(data, id, childBuilder.elements, 0)) + elements.add(BuilderElement.Node(data, id, onClick, childBuilder.elements, 0)) } - fun leaf(data: T, id: Any = data.toString()) { - elements.add(BuilderElement.Leaf(data, id, 0)) + fun leaf(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null) { + elements.add(BuilderElement.Leaf(data, id, onClick, 0)) } internal fun buildRoots(): List> = elements.map { it.buildElement(depth = 0) } @@ -85,38 +84,38 @@ class TreeBuilder { sealed class BuilderElement { abstract fun buildElement(depth: Int): TreeElement - data class Leaf(val data: T, val id: Any, val depthInit: Int) : BuilderElement() { + data class Leaf(val data: T, val id: Any, val onClick: (() -> Unit)?, val depthInit: Int) : BuilderElement() { override fun buildElement(depth: Int): TreeElement { - return TreeElement.Leaf(data, id, depth) + return TreeElement.Leaf(data, id, depth, onClick) } } data class Node( val data: T, val id: Any, + val onClick: (() -> Unit)?, val children: List>, val depthInit: Int ) : BuilderElement() { override fun buildElement(depth: Int): TreeElement { val builtChildren = children.map { it.buildElement(depth + 1) } - return TreeElement.Node(data, id, depth, builtChildren) + return TreeElement.Node(data, id, depth, builtChildren, onClick) } } } } -// Builder for adding children under a node class NodeBuilder(private val parentDepth: Int) { internal val elements = mutableListOf>() - fun node(data: T, id: Any = data.toString(), children: NodeBuilder.() -> Unit) { + fun node(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null, children: NodeBuilder.() -> Unit) { val childBuilder = NodeBuilder(parentDepth + 1) childBuilder.children() - elements.add(TreeBuilder.BuilderElement.Node(data, id, childBuilder.elements, parentDepth + 1)) + elements.add(TreeBuilder.BuilderElement.Node(data, id, onClick, childBuilder.elements, parentDepth + 1)) } - fun leaf(data: T, id: Any = data.toString()) { - elements.add(TreeBuilder.BuilderElement.Leaf(data, id, parentDepth + 1)) + fun leaf(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null) { + elements.add(TreeBuilder.BuilderElement.Leaf(data, id, onClick, parentDepth + 1)) } } @@ -185,10 +184,6 @@ fun TreeNodeColorScheme.schemeFor(state: VisualState): TreeNodeColor { // Tree composables // ------------------------------------- -/** - * Displays the entire tree recursively using a Column. - * This approach removes code duplication and uses a single function to handle nodes and their children. - */ @Composable fun TreeView( tree: Tree, @@ -202,10 +197,6 @@ fun TreeView( } } -/** - * Recursively displays a tree element (node or leaf). - * If it is a node, it can be expanded or collapsed to show its children. - */ @Composable fun TreeElementView( element: TreeElement, @@ -214,16 +205,15 @@ fun TreeElementView( val isNode = element is TreeElement.Node val expandedState = if (isNode) remember { mutableStateOf(false) } else null - // Display the current node line TreeNodeView( element = element, colors = colors, onClick = { if (isNode) expandedState!!.value = !expandedState.value + element.onClick?.invoke() } ) - // If it's a node and expanded, show its children with animation if (isNode) { val node = element as TreeElement.Node AnimatedVisibility( @@ -250,10 +240,6 @@ fun TreeElementView( } } -/** - * Displays a single tree node/leaf line. - * Handles the clickable area, indentation, and icon display. - */ @Composable fun TreeNodeView( element: TreeElement, @@ -269,7 +255,6 @@ fun TreeNodeView( val indentation = (element.depth * 16).dp val isNode = element is TreeElement.Node && element.children.isNotEmpty() - // Local expanded state for icons only (true/false), not triggering recursion here directly val expandedState = if (isNode) remember { mutableStateOf(false) } else null Row( @@ -280,7 +265,7 @@ fun TreeNodeView( .hoverable(interactionSource) .background(if (visualState == VisualState.Hovered) color.backgroundColor else Color.Transparent) .clickable( - enabled = enabled && isNode, + enabled = enabled, role = Role.Button, indication = null, interactionSource = interactionSource diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt index e0e17298..e16ee229 100644 --- a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt @@ -39,9 +39,9 @@ fun TreeViewScreen() { fun TreeViewSample() { val tree = remember { buildTree { - node("Root") { + node(data = "Root", onClick = { println("Root clicked") }) { node("Folder 1") { - leaf("File 1-1") + leaf(data = "File 1-1", onClick = { println("File 1-1 clicked") }) leaf("File 1-2") node("Folder 1-3") { leaf("File 1-3-1") From e47371fca108756393ad177fdcb6970889c08050 Mon Sep 17 00:00:00 2001 From: Elie G Date: Wed, 11 Dec 2024 00:09:15 +0200 Subject: [PATCH 3/3] Update Tree component to support expanded state in onClick Modified the Tree component and TreeBuilder to include the expanded state as a parameter in the onClick callbacks. This allows nodes to differentiate actions based on their expanded or collapsed state, improving interactivity in the TreeView. Updated sample usage in TreeViewScreen to reflect these changes. --- .../com/konyaco/fluent/component/Tree.kt | 20 +++++++++---------- .../screen/navigation/TreeViewScreen.kt | 19 +++++++++++++----- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt index f9817af8..5b439b87 100644 --- a/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt +++ b/fluent/src/commonMain/kotlin/com/konyaco/fluent/component/Tree.kt @@ -39,14 +39,14 @@ sealed class TreeElement { abstract val id: Any abstract val children: List> abstract val depth: Int - abstract val onClick: (() -> Unit)? + abstract val onClick: ((Boolean) -> Unit)? val isLeaf: Boolean get() = children.isEmpty() data class Leaf( override val data: T, override val id: Any, override val depth: Int, - override val onClick: (() -> Unit)? + override val onClick: ((Boolean) -> Unit)? ) : TreeElement() { override val children: List> = emptyList() } @@ -56,7 +56,7 @@ sealed class TreeElement { override val id: Any, override val depth: Int, override val children: List>, - override val onClick: (() -> Unit)? + override val onClick: ((Boolean) -> Unit)? ) : TreeElement() } @@ -69,13 +69,13 @@ fun buildTree(block: TreeBuilder.() -> Unit): Tree { class TreeBuilder { private val elements = mutableListOf>() - fun node(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null, children: NodeBuilder.() -> Unit) { + fun node(data: T, id: Any = data.toString(), onClick: ((Boolean) -> Unit)? = null, children: NodeBuilder.() -> Unit) { val childBuilder = NodeBuilder(0) childBuilder.children() elements.add(BuilderElement.Node(data, id, onClick, childBuilder.elements, 0)) } - fun leaf(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null) { + fun leaf(data: T, id: Any = data.toString(), onClick: ((Boolean) -> Unit)? = null) { elements.add(BuilderElement.Leaf(data, id, onClick, 0)) } @@ -84,7 +84,7 @@ class TreeBuilder { sealed class BuilderElement { abstract fun buildElement(depth: Int): TreeElement - data class Leaf(val data: T, val id: Any, val onClick: (() -> Unit)?, val depthInit: Int) : BuilderElement() { + data class Leaf(val data: T, val id: Any, val onClick: ((Boolean) -> Unit)?, val depthInit: Int) : BuilderElement() { override fun buildElement(depth: Int): TreeElement { return TreeElement.Leaf(data, id, depth, onClick) } @@ -93,7 +93,7 @@ class TreeBuilder { data class Node( val data: T, val id: Any, - val onClick: (() -> Unit)?, + val onClick: ((Boolean) -> Unit)?, val children: List>, val depthInit: Int ) : BuilderElement() { @@ -108,13 +108,13 @@ class TreeBuilder { class NodeBuilder(private val parentDepth: Int) { internal val elements = mutableListOf>() - fun node(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null, children: NodeBuilder.() -> Unit) { + fun node(data: T, id: Any = data.toString(), onClick: ((Boolean) -> Unit)? = null, children: NodeBuilder.() -> Unit) { val childBuilder = NodeBuilder(parentDepth + 1) childBuilder.children() elements.add(TreeBuilder.BuilderElement.Node(data, id, onClick, childBuilder.elements, parentDepth + 1)) } - fun leaf(data: T, id: Any = data.toString(), onClick: (() -> Unit)? = null) { + fun leaf(data: T, id: Any = data.toString(), onClick: ((Boolean) -> Unit)? = null) { elements.add(TreeBuilder.BuilderElement.Leaf(data, id, onClick, parentDepth + 1)) } } @@ -210,7 +210,7 @@ fun TreeElementView( colors = colors, onClick = { if (isNode) expandedState!!.value = !expandedState.value - element.onClick?.invoke() + element.onClick?.invoke(expandedState?.value ?: false) } ) diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt index e16ee229..2f3b8508 100644 --- a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/navigation/TreeViewScreen.kt @@ -39,15 +39,23 @@ fun TreeViewScreen() { fun TreeViewSample() { val tree = remember { buildTree { - node(data = "Root", onClick = { println("Root clicked") }) { - node("Folder 1") { - leaf(data = "File 1-1", onClick = { println("File 1-1 clicked") }) + node(data = "Root", onClick = { isExpanded -> + println("Root clicked, expanded: $isExpanded") + }) { + node("Folder 1", onClick = { isExpanded -> + println("Folder 1 clicked, expanded: $isExpanded") + }) { + leaf(data = "File 1-1", onClick = { _ -> println("File 1-1 clicked") }) leaf("File 1-2") - node("Folder 1-3") { + node("Folder 1-3", onClick = { isExpanded -> + println("Folder 1-3 clicked, expanded: $isExpanded") + }) { leaf("File 1-3-1") } } - node("Folder 2") { + node("Folder 2", onClick = { isExpanded -> + println("Folder 2 clicked, expanded: $isExpanded") + }) { leaf("File 2-1") } } @@ -58,3 +66,4 @@ fun TreeViewSample() { modifier = Modifier.fillMaxWidth() ) } +