Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions src/main/kotlin/fr/quentixx/kfilebuilder/components/Icons.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package fr.quentixx.kfilebuilder.components

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
Expand Down Expand Up @@ -27,43 +30,53 @@ fun IconDir() = Icon(
@Composable
fun TemplateAddDirIcon(
onClick: () -> Unit,
size: Dp = 20.dp
) = Icon(
painterResource("icons/Template_AddDirectory.png"),
size: Dp = 30.dp
) = Image(
painterResource("icons/Icon_NewDirectory.png"),
null, Modifier
.size(size)
.clickable { onClick.invoke() }
.setOnHoverHandCursorEnabled(),
Color.Black
)

@Composable
fun TemplateAddFileIcon(
onClick: () -> Unit,
size: Dp = 20.dp
) = Icon(
painterResource("icons/Template_AddFile.png"),
size: Dp = 32.dp
) = Image(
painterResource("icons/Icon_NewFile.png"),
null, Modifier
.size(size)
.clickable { onClick.invoke() }
.setOnHoverHandCursorEnabled(),
)

@Composable
fun SearchDirectoryIcon(
onClick: () -> Unit,
size: Dp = 30.dp
) = Image(
painterResource("icons/Icon_SearchDirectory.png"),
null, Modifier
.size(size)
.clickable { onClick.invoke() }
.setOnHoverHandCursorEnabled(),
Color.Black
)

@Composable
fun IconArrowDown(
size: Dp = 10.dp
) = Icon(
painterResource("icons/TreeArrow_Down.png"),
null, Modifier.size(size), Color.LightGray
null, Modifier.size(size), Color.Black
)

@Composable
fun IconArrowRight(
size: Dp = 10.dp
) = Icon(
painterResource("icons/TreeArrow_Right.png"),
null, Modifier.size(size), Color.LightGray
null, Modifier.size(size), Color.Black
)

@Composable
Expand Down
170 changes: 150 additions & 20 deletions src/main/kotlin/fr/quentixx/kfilebuilder/treeview/TreeViewBuilder.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
package fr.quentixx.kfilebuilder.treeview

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.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import fr.quentixx.kfilebuilder.components.TemplateAddDirIcon
import fr.quentixx.kfilebuilder.components.TemplateAddFileIcon
import fr.quentixx.kfilebuilder.components.*
import fr.quentixx.kfilebuilder.ext.setOnHoverHandCursorEnabled
import fr.quentixx.kfilebuilder.data.Node
import java.io.File

private data class NodeRowData(
val node: MutableState<Node>,
val hovered: Boolean = false,
)

private fun interface NodeRowScope {
@Composable
fun consume(data: MutableState<NodeRowData>)
}

/**
* Shows a tree view builder.
*/
Expand All @@ -33,7 +46,7 @@ fun TreeViewBuilder(mutableNode: MutableState<Node>) {
Box(
Modifier
.fillMaxSize()
.background(Color.DarkGray)
.background(Color.LightGray)
) {
LazyColumn(
state = listState,
Expand Down Expand Up @@ -62,7 +75,11 @@ fun TreeViewBuilder(mutableNode: MutableState<Node>) {
*/
@Composable
private fun SourceNodeLine(mutableNode: MutableState<Node>) {
Row {
Spacer(Modifier.height(4.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Spacer(Modifier.width(16.dp))
RenderNodeElement(mutableNode, true) {
Spacer(Modifier.width(32.dp))
it.value.apply {
Expand All @@ -74,16 +91,23 @@ private fun SourceNodeLine(mutableNode: MutableState<Node>) {
}

@Composable
fun DirectoryNodeControls(node: MutableState<Node>) {
Spacer(Modifier.width(32.dp))
private fun DirectoryNodeControls(node: MutableState<Node>) {
val NEW_DIR_PATH = "Nouveau dossier"
val NEW_FILE_PATH = "Nouveau fichier"

Spacer(Modifier.width(32.dp))
TemplateAddDirIcon(
onClick = {
val idOfDirectory = node.value.children.count { it.path.contains(NEW_DIR_PATH) }
val nodeName = NEW_DIR_PATH + if (idOfDirectory > 0) {
" $idOfDirectory"
} else ""

node.value = node.value.copy(
lastUpdated = System.currentTimeMillis()
).apply {
children.add(
Node("New dir", true)
Node(nodeName, true)
)
}
}
Expand All @@ -93,11 +117,16 @@ fun DirectoryNodeControls(node: MutableState<Node>) {

TemplateAddFileIcon(
onClick = {
val idOFile = node.value.children.count { it.path.contains(NEW_FILE_PATH) }
val nodeName = NEW_FILE_PATH + if (idOFile > 0) {
" $idOFile"
} else ""

node.value = node.value.copy(
lastUpdated = System.currentTimeMillis()
).apply {
children.add(
Node("New file", false)
Node( nodeName, false)
)
}
}
Expand All @@ -111,7 +140,7 @@ private fun DeleteNodeButton(nodeRow: MutableState<NodeRowData>) {
// TODO: Delete the node from parent children
},
modifier = Modifier
.size(18.dp)
.size(30.dp)
.setOnHoverHandCursorEnabled()
) {
Icon(
Expand All @@ -125,16 +154,11 @@ private fun DeleteNodeButton(nodeRow: MutableState<NodeRowData>) {
@Composable
private fun SelectNodeSourceButton(node: MutableState<Node>) {
val isOverlayVisible = remember { mutableStateOf(false) }
Button(
SearchDirectoryIcon(
onClick = {
isOverlayVisible.value = true
},
modifier = Modifier
.size(80.dp, 32.dp)
.setOnHoverHandCursorEnabled()
) {
Text(text = "Select")
}
)

if (isOverlayVisible.value) {
OpenTreeViewSelectorWindow(node, onClose = { isOverlayVisible.value = false })
Expand Down Expand Up @@ -170,3 +194,109 @@ private fun OpenTreeViewSelectorWindow(

}
}

@Composable
private fun NodeList(
mutableNode: MutableState<Node>,
paddingSave: Dp = 16.dp,
nodeRowScope: @Composable NodeRowScope
) {
val node = mutableNode.value

Column {
node.children.forEach {
val mutableChild = remember { mutableStateOf(it) }

NodeRow(mutableChild, paddingSave, nodeRowScope)

// Update the current children in the loop with the new children information
mutableChild.value.apply {
it.path = path
it.lastUpdated = lastUpdated
}
}
}
}

@Composable
private fun NodeRow(
mutableNode: MutableState<Node>,
paddingSave: Dp = 0.dp,
nodeRowConsumer: @Composable NodeRowScope
) {
var expanded by remember { mutableStateOf(true) }
val padding = paddingSave + 16.dp
val node = mutableNode.value
val isDir = node.isDirectory
val isEmptyDir = isDir && node.children.isEmpty()

val interactionSource = remember { MutableInteractionSource() }
val isHovered = interactionSource.collectIsHoveredAsState().value

val data = remember { mutableStateOf(NodeRowData(mutableNode, isHovered)) }
Row(
modifier = Modifier
.fillMaxWidth()
.height(36.dp)
.padding(start = padding)
.clickable {
if (!isEmptyDir) expanded = !expanded
}
.hoverable(interactionSource),
verticalAlignment = Alignment.CenterVertically
) {
data.value = data.value.copy(hovered = isHovered)

RenderNodeElement(mutableNode, expanded, data, nodeRowConsumer)
}

if (expanded && node.isDirectory) {
NodeList(mutableNode, padding, nodeRowConsumer)
}
}

@Composable
private fun RenderNodeElement(
mutableNode: MutableState<Node>,
expanded: Boolean,
data: MutableState<NodeRowData> = mutableStateOf(NodeRowData(mutableNode, false)),
nodeRowConsumer: @Composable NodeRowScope
) {
val node = mutableNode.value
var spacingSize = 8.dp

if (node.children.isEmpty()) {
spacingSize *= 2
}

if (node.isDirectory) {
if (node.children.isNotEmpty()) {
if (expanded) IconArrowDown(8.dp)
else IconArrowRight(8.dp)
}
Spacer(Modifier.width(spacingSize))
IconDir()
} else {
Spacer(Modifier.width(spacingSize))
IconFile()
}

Spacer(Modifier.width(8.dp))
EditableHighlightedText(mutableNode)

nodeRowConsumer.consume(data)
}

@Composable
private fun EditableHighlightedText(mutableNode: MutableState<Node>) {
BasicTextField(
value = mutableNode.value.path,
onValueChange = { newText ->
mutableNode.value = mutableNode.value.copy(
path = newText,
lastUpdated = System.currentTimeMillis()
)
},
singleLine = true,
)
}
Loading