Added instant tooltip for log avatar and menu entries

This commit is contained in:
Abdelilah El Aissaoui 2024-01-06 20:03:54 +01:00
parent 12b370ea79
commit 5a4f67bad6
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
4 changed files with 231 additions and 54 deletions

View File

@ -28,6 +28,7 @@ import com.jetpackduba.gitnuro.extensions.handMouseClickable
import com.jetpackduba.gitnuro.extensions.handOnHover import com.jetpackduba.gitnuro.extensions.handOnHover
import com.jetpackduba.gitnuro.extensions.ignoreKeyEvents import com.jetpackduba.gitnuro.extensions.ignoreKeyEvents
import com.jetpackduba.gitnuro.git.remote_operations.PullType 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.components.gitnuroViewModel
import com.jetpackduba.gitnuro.ui.context_menu.* import com.jetpackduba.gitnuro.ui.context_menu.*
import com.jetpackduba.gitnuro.viewmodels.MenuViewModel import com.jetpackduba.gitnuro.viewmodels.MenuViewModel
@ -50,19 +51,31 @@ fun Menu(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
MenuButton( InstantTooltip(
modifier = Modifier text = "Open a different repository"
.padding(start = 16.dp), ) {
title = "Open", MenuButton(
icon = painterResource(AppIcons.OPEN), modifier = Modifier
onClick = onOpenAnotherRepository, .padding(start = 16.dp),
) title = "Open",
icon = painterResource(AppIcons.OPEN),
onClick = onOpenAnotherRepository,
)
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
val pullTooltip = if (isPullWithRebaseDefault) {
"Pull current branch with rebase"
} else {
"Pull current branch"
}
ExtendedMenuButton( ExtendedMenuButton(
modifier = Modifier.padding(end = 4.dp), modifier = Modifier.padding(end = 4.dp),
title = "Pull", title = "Pull",
tooltipText = pullTooltip,
icon = painterResource(AppIcons.DOWNLOAD), icon = painterResource(AppIcons.DOWNLOAD),
onClick = { menuViewModel.pull(PullType.DEFAULT) }, onClick = { menuViewModel.pull(PullType.DEFAULT) },
extendedListItems = pullContextMenuItems( extendedListItems = pullContextMenuItems(
@ -85,6 +98,7 @@ fun Menu(
ExtendedMenuButton( ExtendedMenuButton(
title = "Push", title = "Push",
tooltipText = "Push current branch changes",
icon = painterResource(AppIcons.UPLOAD), icon = painterResource(AppIcons.UPLOAD),
onClick = { menuViewModel.push() }, onClick = { menuViewModel.push() },
extendedListItems = pushContextMenuItems( extendedListItems = pushContextMenuItems(
@ -99,25 +113,24 @@ fun Menu(
Spacer(modifier = Modifier.width(32.dp)) Spacer(modifier = Modifier.width(32.dp))
MenuButton( InstantTooltip(
title = "Branch", text = "Create a new branch",
icon = painterResource(AppIcons.BRANCH),
) { ) {
onCreateBranch() MenuButton(
title = "Branch",
icon = painterResource(AppIcons.BRANCH),
) {
onCreateBranch()
}
} }
// MenuButton(
// title = "Merge",
// icon = painterResource("merge.svg"),
// ) {
// onCreateBranch()
// }
Spacer(modifier = Modifier.width(32.dp)) Spacer(modifier = Modifier.width(32.dp))
ExtendedMenuButton( ExtendedMenuButton(
modifier = Modifier.padding(end = 4.dp), modifier = Modifier.padding(end = 4.dp),
title = "Stash", title = "Stash",
tooltipText = "Stash uncommitted changes",
icon = painterResource(AppIcons.STASH), icon = painterResource(AppIcons.STASH),
onClick = { menuViewModel.stash() }, onClick = { menuViewModel.stash() },
extendedListItems = stashContextMenuItems( extendedListItems = stashContextMenuItems(
@ -125,19 +138,27 @@ fun Menu(
) )
) )
MenuButton( InstantTooltip(
title = "Pop", text = "Pop the last stash"
icon = painterResource(AppIcons.APPLY_STASH), ) {
) { menuViewModel.popStash() } MenuButton(
title = "Pop",
icon = painterResource(AppIcons.APPLY_STASH),
) { menuViewModel.popStash() }
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
MenuButton( InstantTooltip(
modifier = Modifier.padding(end = 4.dp), text = "Open a terminal in the repository's path"
title = "Terminal", ) {
icon = painterResource(AppIcons.TERMINAL), MenuButton(
onClick = { menuViewModel.openTerminal() }, modifier = Modifier.padding(end = 4.dp),
) title = "Terminal",
icon = painterResource(AppIcons.TERMINAL),
onClick = { menuViewModel.openTerminal() },
)
}
MenuButton( MenuButton(
modifier = Modifier.padding(end = 4.dp), modifier = Modifier.padding(end = 4.dp),
@ -146,12 +167,16 @@ fun Menu(
onClick = onQuickActions, onClick = onQuickActions,
) )
MenuButton( InstantTooltip(
modifier = Modifier.padding(end = 16.dp), text = "Gitnuro's settings",
title = "Settings", modifier = Modifier.padding(end = 16.dp)
icon = painterResource(AppIcons.SETTINGS), ) {
onClick = onShowSettingsDialog, MenuButton(
) title = "Settings",
icon = painterResource(AppIcons.SETTINGS),
onClick = onShowSettingsDialog,
)
}
} }
} }
@ -195,6 +220,7 @@ fun ExtendedMenuButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
title: String, title: String,
tooltipText: String,
icon: Painter, icon: Painter,
onClick: () -> Unit, onClick: () -> Unit,
extendedListItems: List<ContextMenuElement>, extendedListItems: List<ContextMenuElement>,
@ -207,26 +233,32 @@ fun ExtendedMenuButton(
.background(MaterialTheme.colors.surface) .background(MaterialTheme.colors.surface)
.handMouseClickable { if (enabled) onClick() } .handMouseClickable { if (enabled) onClick() }
) { ) {
Column( InstantTooltip(
text = tooltipText,
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.weight(1f), .weight(1f),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Icon( Column(
painter = icon,
contentDescription = title,
modifier = Modifier modifier = Modifier
.size(24.dp), .fillMaxSize(),
tint = MaterialTheme.colors.onBackground, verticalArrangement = Arrangement.Center,
) horizontalAlignment = Alignment.CenterHorizontally,
Text( ) {
text = title, Icon(
style = MaterialTheme.typography.caption, painter = icon,
color = MaterialTheme.colors.onBackground, contentDescription = title,
maxLines = 1, modifier = Modifier
) .size(24.dp),
tint = MaterialTheme.colors.onBackground,
)
Text(
text = title,
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onBackground,
maxLines = 1,
)
}
} }
DropdownMenu( DropdownMenu(

View File

@ -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<LayoutCoordinates?>(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
}

View File

@ -49,7 +49,6 @@ import kotlin.math.abs
private var lastCheck: Long = 0 private var lastCheck: Long = 0
private const val MIN_TIME_BETWEEN_POPUPS_IN_MS = 20 private const val MIN_TIME_BETWEEN_POPUPS_IN_MS = 20
private const val BORDER_RADIUS = 4
@Composable @Composable
fun ContextMenu(items: () -> List<ContextMenuElement>, function: @Composable () -> Unit) { fun ContextMenu(items: () -> List<ContextMenuElement>, function: @Composable () -> Unit) {
@ -180,7 +179,7 @@ fun showPopup(x: Int, y: Int, contextMenuElements: List<ContextMenuElement>, onD
Box( Box(
modifier = Modifier modifier = Modifier
.shadow(8.dp) .shadow(8.dp)
.clip(RoundedCornerShape(BORDER_RADIUS.dp)) .clip(MaterialTheme.shapes.small)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.width(IntrinsicSize.Max) .width(IntrinsicSize.Max)
.widthIn(min = 180.dp) .widthIn(min = 180.dp)
@ -189,7 +188,7 @@ fun showPopup(x: Int, y: Int, contextMenuElements: List<ContextMenuElement>, onD
this.border( this.border(
2.dp, 2.dp,
MaterialTheme.colors.onBackground.copy(alpha = 0.2f), MaterialTheme.colors.onBackground.copy(alpha = 0.2f),
shape = RoundedCornerShape(BORDER_RADIUS.dp) shape = MaterialTheme.shapes.small
) )
} else } else
this this
@ -380,14 +379,14 @@ class AppContextMenuRepresentation : ContextMenuRepresentation {
Column( Column(
modifier = Modifier modifier = Modifier
.shadow(8.dp) .shadow(8.dp)
.clip(RoundedCornerShape(BORDER_RADIUS.dp)) .clip(MaterialTheme.shapes.small)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.width(IntrinsicSize.Max) .width(IntrinsicSize.Max)
.widthIn(min = 180.dp) .widthIn(min = 180.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.run { .run {
if (border != null) if (border != null)
border(border, RoundedCornerShape(BORDER_RADIUS.dp)) border(border, MaterialTheme.shapes.small)
else else
this this
} }

View File

@ -1058,7 +1058,10 @@ fun CommitNode(
) )
} }
} else { } else {
Tooltip("${author.name} <${author.emailAddress}>") { InstantTooltip(
"${author.name} <${author.emailAddress}>",
position = InstantTooltipPosition.RIGHT,
) {
Box( Box(
modifier = modifier modifier = modifier
.size(30.dp) .size(30.dp)