Added instant tooltip for log avatar and menu entries
This commit is contained in:
parent
12b370ea79
commit
5a4f67bad6
@ -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
|
||||||
@ -49,6 +50,9 @@ fun Menu(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
InstantTooltip(
|
||||||
|
text = "Open a different repository"
|
||||||
) {
|
) {
|
||||||
MenuButton(
|
MenuButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -57,12 +61,21 @@ fun Menu(
|
|||||||
icon = painterResource(AppIcons.OPEN),
|
icon = painterResource(AppIcons.OPEN),
|
||||||
onClick = onOpenAnotherRepository,
|
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))
|
||||||
|
|
||||||
|
InstantTooltip(
|
||||||
|
text = "Create a new branch",
|
||||||
|
) {
|
||||||
MenuButton(
|
MenuButton(
|
||||||
title = "Branch",
|
title = "Branch",
|
||||||
icon = painterResource(AppIcons.BRANCH),
|
icon = painterResource(AppIcons.BRANCH),
|
||||||
) {
|
) {
|
||||||
onCreateBranch()
|
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(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
InstantTooltip(
|
||||||
|
text = "Pop the last stash"
|
||||||
|
) {
|
||||||
MenuButton(
|
MenuButton(
|
||||||
title = "Pop",
|
title = "Pop",
|
||||||
icon = painterResource(AppIcons.APPLY_STASH),
|
icon = painterResource(AppIcons.APPLY_STASH),
|
||||||
) { menuViewModel.popStash() }
|
) { menuViewModel.popStash() }
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
InstantTooltip(
|
||||||
|
text = "Open a terminal in the repository's path"
|
||||||
|
) {
|
||||||
MenuButton(
|
MenuButton(
|
||||||
modifier = Modifier.padding(end = 4.dp),
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
title = "Terminal",
|
title = "Terminal",
|
||||||
icon = painterResource(AppIcons.TERMINAL),
|
icon = painterResource(AppIcons.TERMINAL),
|
||||||
onClick = { menuViewModel.openTerminal() },
|
onClick = { menuViewModel.openTerminal() },
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
MenuButton(
|
MenuButton(
|
||||||
modifier = Modifier.padding(end = 4.dp),
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
@ -146,14 +167,18 @@ fun Menu(
|
|||||||
onClick = onQuickActions,
|
onClick = onQuickActions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
InstantTooltip(
|
||||||
|
text = "Gitnuro's settings",
|
||||||
|
modifier = Modifier.padding(end = 16.dp)
|
||||||
|
) {
|
||||||
MenuButton(
|
MenuButton(
|
||||||
modifier = Modifier.padding(end = 16.dp),
|
|
||||||
title = "Settings",
|
title = "Settings",
|
||||||
icon = painterResource(AppIcons.SETTINGS),
|
icon = painterResource(AppIcons.SETTINGS),
|
||||||
onClick = onShowSettingsDialog,
|
onClick = onShowSettingsDialog,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MenuButton(
|
fun MenuButton(
|
||||||
@ -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,10 +233,15 @@ 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),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
@ -228,6 +259,7 @@ fun ExtendedMenuButton(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
items = { extendedListItems }
|
items = { extendedListItems }
|
||||||
|
@ -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 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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user