diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/App.kt b/src/main/kotlin/com/jetpackduba/gitnuro/App.kt index 30b7ddb..fcc508a 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/App.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/App.kt @@ -184,8 +184,7 @@ class App { ) { Row( modifier = Modifier - .fillMaxWidth() - .height(40.dp), + .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { RepositoriesTabPanel( @@ -195,7 +194,8 @@ class App { tabsManager.selectTab(selectedTab) }, onTabClosed = onCloseTab, - onAddNewTab = onAddedTab + onAddNewTab = onAddedTab, + tabsHeight = 40.dp, ) } } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt b/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt index 9970467..a682ad2 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt @@ -76,6 +76,9 @@ val Colors.diffLineAdded: Color val Colors.diffLineRemoved: Color get() = appTheme.diffLineRemoved +val Colors.isDark: Boolean + get() = !this.isLight + enum class Theme(val displayName: String) : DropDownOption { LIGHT("Light"), diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt index 6c9079c..9bbd5f5 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/WelcomePage.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.jetpackduba.gitnuro.AppConstants import com.jetpackduba.gitnuro.AppIcons @@ -221,14 +222,20 @@ fun RecentRepositories(appStateManager: AppStateManager, tabViewModel: TabViewMo maxLines = 1, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.primaryVariant, - modifier = Modifier.padding(8.dp) + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .padding(8.dp) + .widthIn(max = 600.dp), ) } Text( text = repoDirPath, style = MaterialTheme.typography.body1, - modifier = Modifier.padding(start = 4.dp), + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .padding(start = 4.dp) + .widthIn(max = 600.dp), maxLines = 1, color = MaterialTheme.colors.onBackgroundSecondary ) diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/RepositoriesTabPanel.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/RepositoriesTabPanel.kt index 802086e..1319a07 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/RepositoriesTabPanel.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/RepositoriesTabPanel.kt @@ -10,18 +10,22 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.v2.maxScrollOffset import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.jetpackduba.gitnuro.AppIcons import com.jetpackduba.gitnuro.AppStateManager import com.jetpackduba.gitnuro.LocalTabScope import com.jetpackduba.gitnuro.di.AppComponent @@ -31,6 +35,8 @@ import com.jetpackduba.gitnuro.extensions.handMouseClickable import com.jetpackduba.gitnuro.extensions.handOnHover import com.jetpackduba.gitnuro.viewmodels.TabViewModel import com.jetpackduba.gitnuro.viewmodels.TabViewModelsHolder +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.io.path.Path import kotlin.io.path.name @@ -39,19 +45,15 @@ import kotlin.io.path.name fun RepositoriesTabPanel( tabs: List, currentTab: TabInformation?, + tabsHeight: Dp, onTabSelected: (TabInformation) -> Unit, onTabClosed: (TabInformation) -> Unit, onAddNewTab: () -> Unit, ) { val stateHorizontal = rememberLazyListState() - -// LaunchedEffect(selectedTabKey) { -// val index = tabs.indexOfFirst { it.key == selectedTabKey } -// // todo sometimes it scrolls to (index - 1) for some weird reason -// if (index > -1) { -// stateHorizontal.scrollToItem(index) -// } -// } + val scrollAdapter = rememberScrollbarAdapter(stateHorizontal) + val scope = rememberCoroutineScope() + var latestTabCount by remember { mutableStateOf(tabs.count()) } val canBeScrolled by remember { derivedStateOf { @@ -73,126 +75,137 @@ fun RepositoriesTabPanel( } } + Column { + if (canBeScrolled) { + Tooltip( + "\"Shift + Mouse wheel\" to scroll" + ) { + HorizontalScrollbar( + modifier = Modifier + .fillMaxWidth(), + adapter = scrollAdapter + ) + } + } - Row { - Box( - modifier = Modifier - .weight(1f, false) - ) { + Row { LazyRow( modifier = Modifier - .fillMaxHeight(), + .height(tabsHeight) + .weight(1f, false), state = stateHorizontal, ) { items(items = tabs) { tab -> - Tab( - title = tab.tabName, - isSelected = currentTab == tab, - onClick = { - onTabSelected(tab) - }, - onCloseTab = { - onTabClosed(tab) - } - ) - } - } - - if (canBeScrolled) { - Tooltip( - "\"Shift + Mouse wheel\" to scroll" - ) { - HorizontalScrollbar( - modifier = Modifier - .align(Alignment.TopStart) - .width((tabs.count() * 180).dp), - adapter = rememberScrollbarAdapter(stateHorizontal) - ) - } - } - } - - IconButton( - onClick = { - onAddNewTab() - }, - modifier = Modifier - .size(36.dp) - .handOnHover() - .align(Alignment.CenterVertically), - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - tint = MaterialTheme.colors.primaryVariant, - ) - } - } -} - -@Composable -fun Tab(title: MutableState, isSelected: Boolean, onClick: () -> Unit, onCloseTab: () -> Unit) { - Box { - val backgroundColor = if (isSelected) - MaterialTheme.colors.surface - else - MaterialTheme.colors.background - - val hoverInteraction = remember { MutableInteractionSource() } - val isHovered by hoverInteraction.collectIsHoveredAsState() - - Box( - modifier = Modifier - .width(180.dp) - .fillMaxHeight() - .hoverable(hoverInteraction) - .handMouseClickable { onClick() } - .background(backgroundColor), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.CenterStart), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = title.value, - modifier = Modifier - .padding(start = 16.dp, end = 8.dp) - .weight(1f), - overflow = TextOverflow.Visible, - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.onBackground, - maxLines = 1, - ) - - if (isHovered || isSelected) { - IconButton( - onClick = onCloseTab, - modifier = Modifier - .padding(horizontal = 8.dp) - .size(14.dp) - ) { - Icon( - Icons.Default.Close, - contentDescription = null, - tint = MaterialTheme.colors.onBackground + Tooltip(tab.path) { + Tab( + title = tab.tabName, + isSelected = currentTab == tab, + onClick = { + onTabSelected(tab) + }, + onCloseTab = { + onTabClosed(tab) + } ) } } } - if (isSelected) { - Box( - modifier = Modifier - .align(Alignment.BottomCenter) - .fillMaxWidth() - .height(3.dp) - .background(MaterialTheme.colors.primaryVariant) + IconButton( + onClick = { + onAddNewTab() + }, + modifier = Modifier + .size(36.dp) + .handOnHover() + .align(Alignment.CenterVertically), + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + tint = MaterialTheme.colors.primaryVariant, ) } } } + + LaunchedEffect(tabs.count()) { + // Scroll to the end if a new tab has been added & it's empty (so it's not a new submodule tab) + if (latestTabCount < tabs.count() && currentTab?.path == null) { + scope.launch { + delay(50) // add small delay to wait until [scrollAdapter.maxScrollOffset] is recalculated. Seems more like a hack of some kind... + scrollAdapter.scrollTo(scrollAdapter.maxScrollOffset) + } + } + + latestTabCount = tabs.count() + } +} + +@Composable +fun Tab(title: MutableState, isSelected: Boolean, onClick: () -> Unit, onCloseTab: () -> Unit) { + val backgroundColor = if (isSelected) + MaterialTheme.colors.surface + else + MaterialTheme.colors.background + + val hoverInteraction = remember { MutableInteractionSource() } + val isHovered by hoverInteraction.collectIsHoveredAsState() + + Box( + modifier = Modifier + .widthIn(min = 200.dp) + .width(IntrinsicSize.Min) + .fillMaxHeight() + .hoverable(hoverInteraction) + .handMouseClickable { onClick() } + .background(backgroundColor), + ) { + Row( + modifier = Modifier + .align(Alignment.CenterStart), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = title.value.replace(" ", "-"), // TODO This replace is a workaround for https://issuetracker.google.com/issues/278044455 + modifier = Modifier + .padding(start = 16.dp) + .weight(1f) + .widthIn(max = 720.dp), + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onBackground, + maxLines = 1, + softWrap = false, + ) + + IconButton( + onClick = onCloseTab, + enabled = isHovered || isSelected, + modifier = Modifier + .alpha(if (isHovered || isSelected) 1f else 0f) + .padding(horizontal = 8.dp) + .size(14.dp) + ) { + Icon( + painterResource(AppIcons.CLOSE), + contentDescription = null, + tint = MaterialTheme.colors.onBackground + ) + } + } + + if (isSelected) { + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .height(3.dp) + .background(MaterialTheme.colors.primaryVariant) + ) + } + } } class TabInformation( diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/Tooltip.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/Tooltip.kt index d9e32d4..674ae8d 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/Tooltip.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/Tooltip.kt @@ -1,5 +1,6 @@ package com.jetpackduba.gitnuro.ui.components +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.TooltipArea import androidx.compose.foundation.layout.padding @@ -9,20 +10,30 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.jetpackduba.gitnuro.theme.isDark +import com.jetpackduba.gitnuro.theme.onBackgroundSecondary @OptIn(ExperimentalFoundationApi::class) @Composable -fun Tooltip(text: String, content: @Composable () -> Unit) { +fun Tooltip(text: String?, content: @Composable () -> Unit) { TooltipArea( tooltip = { - Card( - backgroundColor = MaterialTheme.colors.background, - elevation = 10.dp, - ) { - Text( - text = text, - modifier = Modifier.padding(8.dp) - ) + if (text != null) { + val border = if (MaterialTheme.colors.isDark) { + BorderStroke(2.dp, MaterialTheme.colors.onBackgroundSecondary.copy(alpha = 0.4f)) + } else + null + + Card( + backgroundColor = MaterialTheme.colors.background, + border = border, + elevation = 10.dp, + ) { + Text( + text = text, + modifier = Modifier.padding(8.dp) + ) + } } }, ) {