From 7e86e3b2fdd68da7a1c0ee65c5bd640d5c1559b5 Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Mon, 7 Feb 2022 15:34:21 +0100 Subject: [PATCH] Reimplemented log view. Now graph can be horizontally scrolled --- .../app/extensions/LazyListStateExtensions.kt | 9 + src/main/kotlin/app/ui/log/Log.kt | 275 ++++++++++++------ 2 files changed, 201 insertions(+), 83 deletions(-) create mode 100644 src/main/kotlin/app/extensions/LazyListStateExtensions.kt diff --git a/src/main/kotlin/app/extensions/LazyListStateExtensions.kt b/src/main/kotlin/app/extensions/LazyListStateExtensions.kt new file mode 100644 index 0000000..92cf460 --- /dev/null +++ b/src/main/kotlin/app/extensions/LazyListStateExtensions.kt @@ -0,0 +1,9 @@ +package app.extensions + +import androidx.compose.foundation.lazy.LazyListState + +fun LazyListState.observeScrollChanges() { + // When accessing this property, this parent composable is recomposed when the scroll changes + // because LazyListState is marked with @Stable + this.firstVisibleItemScrollOffset +} \ No newline at end of file diff --git a/src/main/kotlin/app/ui/log/Log.kt b/src/main/kotlin/app/ui/log/Log.kt index ec811a2..37a7a85 100644 --- a/src/main/kotlin/app/ui/log/Log.kt +++ b/src/main/kotlin/app/ui/log/Log.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape @@ -37,6 +39,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.extensions.* import app.git.StatusSummary +import app.git.graph.GraphCommitList import app.git.graph.GraphNode import app.theme.* import app.ui.SelectedItem @@ -61,9 +64,9 @@ private val colors = listOf( ) private const val CANVAS_MIN_WIDTH = 100 +private const val PADDING_BETWEEN_DIVIDER_AND_MESSAGE = 8 // TODO Min size for message column -// TODO Horizontal scroll for the graph @OptIn( ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class @@ -87,7 +90,9 @@ fun Log( if (logStatus is LogStatus.Loaded) { val hasUncommitedChanges = logStatus.hasUncommitedChanges val commitList = logStatus.plotCommitList - val scrollState = rememberLazyListState() + val verticalScrollState = rememberLazyListState() + verticalScrollState.isScrollInProgress + verticalScrollState.observeScrollChanges() LaunchedEffect(selectedCommit) { // Scroll to commit if a Ref is selected @@ -97,7 +102,7 @@ fun Log( // Index can be -1 if the ref points to a commit that is not shown in the graph due to the limited // number of displayed commits. if (index >= 0) - scrollState.scrollToItem(index) + verticalScrollState.scrollToItem(index) } } @@ -123,46 +128,175 @@ fun Log( graphWidth = graphWidth, weightMod = weightMod, ) - ScrollableLazyColumn( + + val horizontalScrollState = rememberScrollState(0) + Box { + GraphList( + commitList = commitList, + stateHorizontal = horizontalScrollState, + graphWidth = graphWidth, + scrollState = verticalScrollState, + hasUncommitedChanges = hasUncommitedChanges, + ) + + MessagesList( + scrollState = verticalScrollState, + hasUncommitedChanges = hasUncommitedChanges, + selectedCommit = selectedCommit, + logStatus = logStatus, + repositoryState = repositoryState, + selectedItem = selectedItem, + commitList = commitList, + logViewModel = logViewModel, + graphWidth = graphWidth, + onShowLogDialog = { dialog -> + showLogDialog.value = dialog + } + ) + + DividerLog( + modifier = Modifier + .draggable( + rememberDraggableState { + weightMod.value += it + }, + Orientation.Horizontal + ), + graphWidth = graphWidth, + ) + + // Scrollbar used to scroll horizontally the graph nodes + // Added after every component to have the highest priority when clicking + HorizontalScrollbar( + modifier = Modifier + .align(Alignment.BottomStart) + .width(graphWidth) + .padding(start = 4.dp, bottom = 4.dp), + adapter = rememberScrollbarAdapter(horizontalScrollState) + ) + } + } + } +} + +@Composable +fun MessagesList( + scrollState: LazyListState, + hasUncommitedChanges: Boolean, + selectedCommit: RevCommit?, + logStatus: LogStatus.Loaded, + repositoryState: RepositoryState, + selectedItem: SelectedItem, + commitList: GraphCommitList, + logViewModel: LogViewModel, + onShowLogDialog: (LogDialog) -> Unit, + graphWidth: Dp, +) { + ScrollableLazyColumn( + state = scrollState, + modifier = Modifier + .fillMaxSize(), + ) { + if (hasUncommitedChanges) + item { + UncommitedChangesLine( + graphWidth = graphWidth, + selected = selectedItem == SelectedItem.UncommitedChanges, + statusSummary = logStatus.statusSummary, + repositoryState = repositoryState, + onUncommitedChangesSelected = { + logViewModel.selectLogLine(SelectedItem.UncommitedChanges) + } + ) + } + items(items = commitList) { graphNode -> + CommitLine( + graphWidth = graphWidth, + logViewModel = logViewModel, + graphNode = graphNode, + selected = selectedCommit?.name == graphNode.name, + currentBranch = logStatus.currentBranch, + showCreateNewBranch = { onShowLogDialog(LogDialog.NewBranch(graphNode)) }, + showCreateNewTag = { onShowLogDialog(LogDialog.NewTag(graphNode)) }, + resetBranch = { onShowLogDialog(LogDialog.ResetBranch(graphNode)) }, + onMergeBranch = { ref -> onShowLogDialog(LogDialog.MergeBranch(ref)) }, + onRebaseBranch = { ref -> onShowLogDialog(LogDialog.RebaseBranch(ref)) }, + onRevCommitSelected = { + logViewModel.selectLogLine(SelectedItem.Commit(graphNode)) + } + ) + } + } +} + +@Composable +fun GraphList( + commitList: GraphCommitList, + stateHorizontal: ScrollState = rememberScrollState(0), + graphWidth: Dp, + scrollState: LazyListState, + hasUncommitedChanges: Boolean +) { + + val graphRealWidth = remember(commitList, graphWidth) { + val maxLinePosition = commitList.maxOf { it.lane.position } + val calculatedGraphWidth = ((maxLinePosition + 2) * 30f).dp + + if (calculatedGraphWidth < graphWidth) + graphWidth + else + calculatedGraphWidth + } + + Box( + Modifier + .width(graphWidth) + .fillMaxHeight() + ) { + + Box( + modifier = Modifier + .fillMaxSize() + .horizontalScroll(stateHorizontal) + .padding(bottom = 8.dp) + ) { + LazyColumn( state = scrollState, modifier = Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize(), + .width(graphRealWidth) ) { - if (hasUncommitedChanges) + if (hasUncommitedChanges) { item { - UncommitedChangesLine( - selected = selectedItem == SelectedItem.UncommitedChanges, - hasPreviousCommits = commitList.isNotEmpty(), - statusSummary = logStatus.statusSummary, - graphWidth = graphWidth, - weightMod = weightMod, - repositoryState = repositoryState, - onUncommitedChangesSelected = { - logViewModel.selectLogLine(SelectedItem.UncommitedChanges) - } + Row( + modifier = Modifier + .height(40.dp) + .fillMaxWidth(), + ) { + UncommitedChangesGraphNode( + modifier = Modifier + .width(graphWidth), + hasPreviousCommits = commitList.isNotEmpty(), + ) + } + } + } + + items(items = commitList) { graphNode -> + val nodeColor = colors[graphNode.lane.position % colors.size] + Row( + modifier = Modifier + .height(40.dp) + .fillMaxWidth(), + ) { + CommitsGraphLine( + modifier = Modifier + .fillMaxHeight(), + plotCommit = graphNode, + nodeColor = nodeColor, ) } - items(items = commitList) { graphNode -> - CommitLine( - logViewModel = logViewModel, - graphNode = graphNode, - selected = selectedCommit?.name == graphNode.name, - weightMod = weightMod, - graphWidth = graphWidth, - currentBranch = logStatus.currentBranch, - showCreateNewBranch = { showLogDialog.value = LogDialog.NewBranch(graphNode) }, - showCreateNewTag = { showLogDialog.value = LogDialog.NewTag(graphNode) }, - resetBranch = { showLogDialog.value = LogDialog.ResetBranch(graphNode) }, - onMergeBranch = { ref -> showLogDialog.value = LogDialog.MergeBranch(ref) }, - onRebaseBranch = { ref -> showLogDialog.value = LogDialog.RebaseBranch(ref) }, - onRevCommitSelected = { - logViewModel.selectLogLine(SelectedItem.Commit(graphNode)) - } - ) } } - } } } @@ -252,10 +386,14 @@ fun GraphHeader( maxLines = 1, ) - DividerLog( - modifier = Modifier.draggable(rememberDraggableState { - weightMod.value += it - }, Orientation.Horizontal) + SimpleDividerLog( + modifier = Modifier + .draggable( + rememberDraggableState { + weightMod.value += it + }, + Orientation.Horizontal + ), ) Text( @@ -272,13 +410,11 @@ fun GraphHeader( @Composable fun UncommitedChangesLine( - selected: Boolean, - hasPreviousCommits: Boolean, graphWidth: Dp, - weightMod: MutableState, - onUncommitedChangesSelected: () -> Unit, + selected: Boolean, repositoryState: RepositoryState, - statusSummary: StatusSummary + statusSummary: StatusSummary, + onUncommitedChangesSelected: () -> Unit, ) { val textColor = if (selected) { MaterialTheme.colors.primary @@ -291,25 +427,10 @@ fun UncommitedChangesLine( .fillMaxWidth() .clickable { onUncommitedChangesSelected() - }, + } + .padding(start = graphWidth + PADDING_BETWEEN_DIVIDER_AND_MESSAGE.dp), verticalAlignment = Alignment.CenterVertically, ) { - UncommitedChangesGraphNode( - modifier = Modifier - .width(graphWidth), - hasPreviousCommits = hasPreviousCommits, - ) - - DividerLog( - modifier = Modifier - .draggable( - rememberDraggableState { - weightMod.value += it - }, - Orientation.Horizontal - ) - ) - val text = when { repositoryState.isRebasing -> "Pending changes to rebase" repositoryState.isMerging -> "Pending changes to merge" @@ -391,11 +512,10 @@ fun SummaryEntry( @Composable fun CommitLine( + graphWidth: Dp, logViewModel: LogViewModel, graphNode: GraphNode, selected: Boolean, - weightMod: MutableState, - graphWidth: Dp, currentBranch: Ref?, showCreateNewBranch: () -> Unit, showCreateNewTag: () -> Unit, @@ -410,6 +530,7 @@ fun CommitLine( .clickable { onRevCommitSelected(graphNode) } + .padding(start = graphWidth + PADDING_BETWEEN_DIVIDER_AND_MESSAGE.dp) ) { ContextMenuArea( items = { @@ -447,25 +568,6 @@ fun CommitLine( .fillMaxWidth(), ) { val nodeColor = colors[graphNode.lane.position % colors.size] - - CommitsGraphLine( - modifier = Modifier - .width(graphWidth) - .fillMaxHeight(), - plotCommit = graphNode, - nodeColor = nodeColor, - ) - - DividerLog( - modifier = Modifier - .draggable( - rememberDraggableState { - weightMod.value += it - }, - Orientation.Horizontal - ) - ) - CommitMessage( modifier = Modifier.weight(1f), commit = graphNode, @@ -573,9 +675,10 @@ fun CommitMessage( @OptIn(ExperimentalComposeUiApi::class) @Composable -fun DividerLog(modifier: Modifier) { +fun DividerLog(modifier: Modifier, graphWidth: Dp) { Box( modifier = Modifier + .padding(start = graphWidth) .width(8.dp) .then(modifier) .pointerHoverIcon(PointerIconDefaults.Hand) @@ -590,6 +693,12 @@ fun DividerLog(modifier: Modifier) { } } +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun SimpleDividerLog(modifier: Modifier) { + DividerLog(modifier, graphWidth = 0.dp) +} + @Composable fun CommitsGraphLine(