diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt index 5d96b98..c00b8bb 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/Menu.kt @@ -28,6 +28,7 @@ import com.jetpackduba.gitnuro.extensions.handMouseClickable import com.jetpackduba.gitnuro.extensions.handOnHover import com.jetpackduba.gitnuro.extensions.ignoreKeyEvents import com.jetpackduba.gitnuro.git.remote_operations.PullType +import com.jetpackduba.gitnuro.ui.components.InstantTooltip import com.jetpackduba.gitnuro.ui.components.gitnuroViewModel import com.jetpackduba.gitnuro.ui.context_menu.* import com.jetpackduba.gitnuro.viewmodels.MenuViewModel @@ -50,19 +51,31 @@ fun Menu( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { - MenuButton( - modifier = Modifier - .padding(start = 16.dp), - title = "Open", - icon = painterResource(AppIcons.OPEN), - onClick = onOpenAnotherRepository, - ) + InstantTooltip( + text = "Open a different repository" + ) { + MenuButton( + modifier = Modifier + .padding(start = 16.dp), + title = "Open", + icon = painterResource(AppIcons.OPEN), + onClick = onOpenAnotherRepository, + ) + } Spacer(modifier = Modifier.weight(1f)) + val pullTooltip = if (isPullWithRebaseDefault) { + "Pull current branch with rebase" + } else { + "Pull current branch" + } + + ExtendedMenuButton( modifier = Modifier.padding(end = 4.dp), title = "Pull", + tooltipText = pullTooltip, icon = painterResource(AppIcons.DOWNLOAD), onClick = { menuViewModel.pull(PullType.DEFAULT) }, extendedListItems = pullContextMenuItems( @@ -85,6 +98,7 @@ fun Menu( ExtendedMenuButton( title = "Push", + tooltipText = "Push current branch changes", icon = painterResource(AppIcons.UPLOAD), onClick = { menuViewModel.push() }, extendedListItems = pushContextMenuItems( @@ -99,25 +113,24 @@ fun Menu( Spacer(modifier = Modifier.width(32.dp)) - MenuButton( - title = "Branch", - icon = painterResource(AppIcons.BRANCH), + InstantTooltip( + text = "Create a new branch", ) { - onCreateBranch() + MenuButton( + title = "Branch", + icon = painterResource(AppIcons.BRANCH), + ) { + onCreateBranch() + } } -// MenuButton( -// title = "Merge", -// icon = painterResource("merge.svg"), -// ) { -// onCreateBranch() -// } Spacer(modifier = Modifier.width(32.dp)) ExtendedMenuButton( modifier = Modifier.padding(end = 4.dp), title = "Stash", + tooltipText = "Stash uncommitted changes", icon = painterResource(AppIcons.STASH), onClick = { menuViewModel.stash() }, extendedListItems = stashContextMenuItems( @@ -125,19 +138,27 @@ fun Menu( ) ) - MenuButton( - title = "Pop", - icon = painterResource(AppIcons.APPLY_STASH), - ) { menuViewModel.popStash() } + InstantTooltip( + text = "Pop the last stash" + ) { + MenuButton( + title = "Pop", + icon = painterResource(AppIcons.APPLY_STASH), + ) { menuViewModel.popStash() } + } Spacer(modifier = Modifier.weight(1f)) - MenuButton( - modifier = Modifier.padding(end = 4.dp), - title = "Terminal", - icon = painterResource(AppIcons.TERMINAL), - onClick = { menuViewModel.openTerminal() }, - ) + InstantTooltip( + text = "Open a terminal in the repository's path" + ) { + MenuButton( + modifier = Modifier.padding(end = 4.dp), + title = "Terminal", + icon = painterResource(AppIcons.TERMINAL), + onClick = { menuViewModel.openTerminal() }, + ) + } MenuButton( modifier = Modifier.padding(end = 4.dp), @@ -146,12 +167,16 @@ fun Menu( onClick = onQuickActions, ) - MenuButton( - modifier = Modifier.padding(end = 16.dp), - title = "Settings", - icon = painterResource(AppIcons.SETTINGS), - onClick = onShowSettingsDialog, - ) + InstantTooltip( + text = "Gitnuro's settings", + modifier = Modifier.padding(end = 16.dp) + ) { + MenuButton( + title = "Settings", + icon = painterResource(AppIcons.SETTINGS), + onClick = onShowSettingsDialog, + ) + } } } @@ -195,6 +220,7 @@ fun ExtendedMenuButton( modifier: Modifier = Modifier, enabled: Boolean = true, title: String, + tooltipText: String, icon: Painter, onClick: () -> Unit, extendedListItems: List, @@ -207,26 +233,32 @@ fun ExtendedMenuButton( .background(MaterialTheme.colors.surface) .handMouseClickable { if (enabled) onClick() } ) { - Column( + InstantTooltip( + text = tooltipText, modifier = Modifier .fillMaxHeight() .weight(1f), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, ) { - Icon( - painter = icon, - contentDescription = title, + Column( modifier = Modifier - .size(24.dp), - tint = MaterialTheme.colors.onBackground, - ) - Text( - text = title, - style = MaterialTheme.typography.caption, - color = MaterialTheme.colors.onBackground, - maxLines = 1, - ) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + painter = icon, + contentDescription = title, + modifier = Modifier + .size(24.dp), + tint = MaterialTheme.colors.onBackground, + ) + Text( + text = title, + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onBackground, + maxLines = 1, + ) + } } DropdownMenu( diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/InstantTooltip.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/InstantTooltip.kt new file mode 100644 index 0000000..d5868cc --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/components/InstantTooltip.kt @@ -0,0 +1,143 @@ +package com.jetpackduba.gitnuro.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.unit.* +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupPositionProvider +import androidx.compose.ui.window.PopupProperties +import com.jetpackduba.gitnuro.theme.isDark + +@Composable +fun InstantTooltip( + text: String, + modifier: Modifier = Modifier, + position: InstantTooltipPosition = InstantTooltipPosition.BOTTOM, + content: @Composable () -> Unit, +) { + val hoverInteractionSource = remember { MutableInteractionSource() } + val isHovered by hoverInteractionSource.collectIsHoveredAsState() + val (coordinates, setCoordinates) = remember { mutableStateOf(null) } + + Box( + modifier = modifier + .hoverable(hoverInteractionSource) + .onGloballyPositioned { + setCoordinates(it) + }, + ) { + content() + } + + if (isHovered && coordinates != null) { + Popup( + properties = PopupProperties( + focusable = false, + ), + popupPositionProvider = object : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize + ): IntOffset { + val positionInWindow = coordinates.positionInWindow() + val contentSize = coordinates.size + + val x = getXBasedOnTooltipPosition(position, positionInWindow, contentSize, popupContentSize) // + val y = getYBasedOnTooltipPosition(position, positionInWindow, contentSize, popupContentSize) + + return IntOffset(x, y) + } + }, + onDismissRequest = {} + ) { + + val padding = when(position) { + InstantTooltipPosition.TOP -> PaddingValues(bottom = 4.dp) + InstantTooltipPosition.BOTTOM -> PaddingValues(top = 4.dp) + InstantTooltipPosition.LEFT -> PaddingValues(end = 4.dp) + InstantTooltipPosition.RIGHT -> PaddingValues(start = 4.dp) + } + + Box( + modifier = Modifier + .padding(padding) + .shadow(8.dp) + .clip(MaterialTheme.shapes.small) + .background(MaterialTheme.colors.background) + .width(IntrinsicSize.Max) + .run { + if (MaterialTheme.colors.isDark) { + this.border( + 2.dp, + MaterialTheme.colors.onBackground.copy(alpha = 0.2f), + shape = MaterialTheme.shapes.small + ) + } else + this + }, + ) { + Text( + text = text, + fontSize = 12.sp, + maxLines = 1, + color = MaterialTheme.colors.onBackground, + modifier = Modifier.padding(8.dp) + ) + } + + } + } +} + +fun getXBasedOnTooltipPosition( + position: InstantTooltipPosition, + positionInWindow: Offset, + contentSize: IntSize, + popupContentSize: IntSize +): Int { + return when (position) { + InstantTooltipPosition.TOP, InstantTooltipPosition.BOTTOM -> (positionInWindow.x + (contentSize.width / 2)) - (popupContentSize.width / 2) + InstantTooltipPosition.LEFT -> positionInWindow.x - popupContentSize.width + InstantTooltipPosition.RIGHT -> positionInWindow.x + contentSize.width + }.toInt() +} + +fun getYBasedOnTooltipPosition( + position: InstantTooltipPosition, + positionInWindow: Offset, + contentSize: IntSize, + popupContentSize: IntSize +): Int { + return when (position) { + InstantTooltipPosition.TOP -> positionInWindow.y - popupContentSize.height + InstantTooltipPosition.BOTTOM -> positionInWindow.y + contentSize.height + InstantTooltipPosition.LEFT, InstantTooltipPosition.RIGHT -> (positionInWindow.y + (contentSize.height / 2)) - (popupContentSize.height / 2) + }.toInt() +} + +enum class InstantTooltipPosition { + TOP, + BOTTOM, + LEFT, + RIGHT +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/ContextMenu.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/ContextMenu.kt index e64b50b..ed26fe2 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/ContextMenu.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/ContextMenu.kt @@ -49,7 +49,6 @@ import kotlin.math.abs private var lastCheck: Long = 0 private const val MIN_TIME_BETWEEN_POPUPS_IN_MS = 20 -private const val BORDER_RADIUS = 4 @Composable fun ContextMenu(items: () -> List, function: @Composable () -> Unit) { @@ -180,7 +179,7 @@ fun showPopup(x: Int, y: Int, contextMenuElements: List, onD Box( modifier = Modifier .shadow(8.dp) - .clip(RoundedCornerShape(BORDER_RADIUS.dp)) + .clip(MaterialTheme.shapes.small) .background(MaterialTheme.colors.background) .width(IntrinsicSize.Max) .widthIn(min = 180.dp) @@ -189,7 +188,7 @@ fun showPopup(x: Int, y: Int, contextMenuElements: List, onD this.border( 2.dp, MaterialTheme.colors.onBackground.copy(alpha = 0.2f), - shape = RoundedCornerShape(BORDER_RADIUS.dp) + shape = MaterialTheme.shapes.small ) } else this @@ -380,14 +379,14 @@ class AppContextMenuRepresentation : ContextMenuRepresentation { Column( modifier = Modifier .shadow(8.dp) - .clip(RoundedCornerShape(BORDER_RADIUS.dp)) + .clip(MaterialTheme.shapes.small) .background(MaterialTheme.colors.background) .width(IntrinsicSize.Max) .widthIn(min = 180.dp) .verticalScroll(rememberScrollState()) .run { if (border != null) - border(border, RoundedCornerShape(BORDER_RADIUS.dp)) + border(border, MaterialTheme.shapes.small) else this } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt index a24ac89..a81fdae 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/log/Log.kt @@ -1058,7 +1058,10 @@ fun CommitNode( ) } } else { - Tooltip("${author.name} <${author.emailAddress}>") { + InstantTooltip( + "${author.name} <${author.emailAddress}>", + position = InstantTooltipPosition.RIGHT, + ) { Box( modifier = modifier .size(30.dp)