Added instant tooltip for log avatar and menu entries
This commit is contained in:
parent
12b370ea79
commit
5a4f67bad6
4 changed files with 231 additions and 54 deletions
|
@ -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<ContextMenuElement>,
|
||||
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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<ContextMenuElement>, function: @Composable () -> Unit) {
|
||||
|
@ -180,7 +179,7 @@ fun showPopup(x: Int, y: Int, contextMenuElements: List<ContextMenuElement>, 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<ContextMenuElement>, 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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue