Created custom implementation of context menu and added new icons

This commit is contained in:
Abdelilah El Aissaoui 2022-09-28 15:07:42 +02:00
parent 8df62ef2b7
commit e7c36c6e90
35 changed files with 354 additions and 146 deletions

View File

@ -27,6 +27,7 @@ dependencies {
implementation(compose.desktop.currentOs)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.desktop.components.splitPane)
implementation(compose("org.jetbrains.compose.ui:ui-util"))
implementation("org.eclipse.jgit:org.eclipse.jgit:6.2.0.202206071550-r")
implementation("org.apache.sshd:sshd-core:2.9.0")
implementation("com.google.dagger:dagger:2.43.2")
@ -56,7 +57,7 @@ tasks.withType<KotlinCompile> {
compose.desktop {
application {
mainClass = "MainKt"
mainClass = "com.jetpackduba.gitnuro.MainKt"
nativeDistributions {
includeAllModules = true

View File

@ -63,7 +63,7 @@ fun rememberNetworkImageOrNull(url: String, placeHolderImageRes: String? = null)
val cacheImageUsed = remember { ValueHolder(false) }
var image by remember(url) {
val cachedImage = NetworkImageLoader.loadCachedImage(url)
val cachedImage = networkImageLoader.loadCachedImage(url)
val image: ImageBitmap? = when {
cachedImage != null -> {

View File

@ -1,4 +1,4 @@
import com.jetpackduba.gitnuro.App
package com.jetpackduba.gitnuro
fun main() {
val main = App()

View File

@ -27,6 +27,7 @@ val lightTheme = ColorsScheme(
hoverScrollbar = Color(0xFF0070D8),
diffLineAdded = Color(0xFFd7ebd0),
diffLineRemoved = Color(0xFFf0d4d4),
isLight = true,
)
@ -55,7 +56,7 @@ val darkBlueTheme = ColorsScheme(
hoverScrollbar = Color(0xFFCCCCCC),
diffLineAdded = Color(0xFF566f5a),
diffLineRemoved = Color(0xFF6f585e),
isLight = false,
)
val darkGrayTheme = ColorsScheme(
@ -83,4 +84,5 @@ val darkGrayTheme = ColorsScheme(
hoverScrollbar = Color(0xFFCCCCCC),
diffLineAdded = Color(0xFF5b7059),
diffLineRemoved = Color(0xFF74595c),
isLight = false,
)

View File

@ -14,7 +14,6 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
// TODO Add line added + removed colors, graph colors and icons color for added/modified/removed files.
@Serializable
data class ColorsScheme(
val primary: Color,
@ -42,6 +41,7 @@ data class ColorsScheme(
val hoverScrollbar: Color,
val diffLineAdded: Color,
val diffLineRemoved: Color,
val isLight: Boolean,
) {
fun toComposeColors(): Colors {
return Colors(
@ -57,7 +57,7 @@ data class ColorsScheme(
onBackground = this.primaryText,
onSurface = this.primaryText,
onError = this.onError,
isLight = true, // property specific for some colors, we don't care about this as all our components are customized
isLight = isLight,
)
}
}

View File

@ -55,6 +55,7 @@ fun typography() = Typography(
body2 = TextStyle(
fontSize = 13.sp,
color = MaterialTheme.colors.primaryTextColor,
fontWeight = FontWeight.Normal,
letterSpacing = LETTER_SPACING.sp,
),
caption = TextStyle(

View File

@ -1,6 +1,5 @@
package com.jetpackduba.gitnuro.ui
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
@ -16,11 +15,11 @@ import com.jetpackduba.gitnuro.extensions.simpleName
import com.jetpackduba.gitnuro.theme.secondaryTextColor
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.branchContextMenuItems
import com.jetpackduba.gitnuro.viewmodels.BranchesViewModel
import org.eclipse.jgit.lib.Ref
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Branches(
branchesViewModel: BranchesViewModel,
@ -69,7 +68,7 @@ private fun BranchLineEntry(
onPushToRemoteBranch: () -> Unit,
onPullFromRemoteBranch: () -> Unit,
) {
ContextMenuArea(
ContextMenu(
items = {
branchContextMenuItems(
branch = branch,

View File

@ -21,6 +21,7 @@ import com.jetpackduba.gitnuro.viewmodels.CommitChangesStatus
import com.jetpackduba.gitnuro.viewmodels.CommitChangesViewModel
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.theme.*
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.eclipse.jgit.diff.DiffEntry
@ -228,7 +229,7 @@ fun CommitLogChanges(
.fillMaxSize()
) {
items(items = diffEntries) { diffEntry ->
ContextMenuArea(
ContextMenu(
items = {
commitedChangesEntriesContextMenuItems(
diffEntry,

View File

@ -1,9 +1,7 @@
@file:OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@file:OptIn(ExperimentalComposeUiApi::class)
package com.jetpackduba.gitnuro.ui
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -23,6 +21,7 @@ import com.jetpackduba.gitnuro.theme.primaryTextColor
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
import com.jetpackduba.gitnuro.ui.components.VerticalExpandable
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.remoteBranchesContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.remoteContextMenu
import com.jetpackduba.gitnuro.ui.dialogs.EditRemotesDialog
@ -30,6 +29,7 @@ import com.jetpackduba.gitnuro.viewmodels.RemoteView
import com.jetpackduba.gitnuro.viewmodels.RemotesViewModel
import org.eclipse.jgit.lib.Ref
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun Remotes(
remotesViewModel: RemotesViewModel,
@ -85,7 +85,6 @@ fun Remotes(
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun RemoteRow(
remote: RemoteView,
@ -106,7 +105,7 @@ private fun RemoteRow(
val branches = remote.remoteInfo.branchesList
Column {
branches.forEach { branch ->
ContextMenuArea(
ContextMenu(
items = {
remoteBranchesContextMenu(
onDeleteBranch = { onDeleteBranch(branch) }

View File

@ -1,9 +1,5 @@
@file:OptIn(ExperimentalFoundationApi::class)
package com.jetpackduba.gitnuro.ui
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -11,6 +7,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.res.painterResource
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenuElement
import com.jetpackduba.gitnuro.ui.context_menu.stashesContextMenuItems
import com.jetpackduba.gitnuro.viewmodels.StashStatus
import com.jetpackduba.gitnuro.viewmodels.StashesViewModel
@ -58,9 +56,9 @@ fun Stashes(
private fun StashRow(
stash: RevCommit,
onClick: () -> Unit,
contextItems: List<ContextMenuItem>,
contextItems: List<ContextMenuElement>,
) {
ContextMenuArea(
ContextMenu(
items = { contextItems }
) {
SideMenuSubentry(

View File

@ -1,7 +1,5 @@
package com.jetpackduba.gitnuro.ui
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
@ -15,11 +13,11 @@ import com.jetpackduba.gitnuro.theme.secondaryTextColor
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
import com.jetpackduba.gitnuro.ui.components.Tooltip
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.submoduleContextMenuItems
import com.jetpackduba.gitnuro.viewmodels.SubmodulesViewModel
import org.eclipse.jgit.submodule.SubmoduleStatus
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Submodules(
submodulesViewModel: SubmodulesViewModel,
@ -42,13 +40,12 @@ fun Submodules(
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun SubmoduleLineEntry(
submodulePair: Pair<String, SubmoduleStatus>,
onInitializeModule: () -> Unit,
) {
ContextMenuArea(
ContextMenu(
items = {
submoduleContextMenuItems(
submodulePair.second,

View File

@ -1,6 +1,5 @@
package com.jetpackduba.gitnuro.ui
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -9,6 +8,7 @@ import androidx.compose.ui.res.painterResource
import com.jetpackduba.gitnuro.extensions.simpleName
import com.jetpackduba.gitnuro.ui.components.SideMenuPanel
import com.jetpackduba.gitnuro.ui.components.SideMenuSubentry
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.tagContextMenuItems
import com.jetpackduba.gitnuro.viewmodels.TagsViewModel
import org.eclipse.jgit.lib.Ref
@ -47,7 +47,7 @@ private fun TagRow(
onCheckoutTag: () -> Unit,
onDeleteTag: () -> Unit,
) {
ContextMenuArea(
ContextMenu(
items = {
tagContextMenuItems(
onCheckoutTag = onCheckoutTag,

View File

@ -35,14 +35,11 @@ import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
import com.jetpackduba.gitnuro.ui.components.SecondaryButton
import com.jetpackduba.gitnuro.ui.context_menu.DropDownContent
import com.jetpackduba.gitnuro.ui.context_menu.DropDownContentData
import com.jetpackduba.gitnuro.ui.context_menu.EntryType
import com.jetpackduba.gitnuro.ui.context_menu.statusEntriesContextMenuItems
import com.jetpackduba.gitnuro.viewmodels.StageStatus
import com.jetpackduba.gitnuro.viewmodels.StatusViewModel
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.theme.*
import com.jetpackduba.gitnuro.ui.context_menu.*
import org.eclipse.jgit.lib.RepositoryState
@Composable
@ -496,7 +493,7 @@ private fun EntriesList(
lazyListState: LazyListState,
onDiffEntrySelected: (StatusEntry) -> Unit,
onDiffEntryOptionSelected: (StatusEntry) -> Unit,
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuElement>,
onAllAction: () -> Unit,
allActionTitle: String,
selectedEntryType: DiffEntryType?,
@ -571,7 +568,7 @@ private fun FileEntry(
actionColor: Color,
onClick: () -> Unit,
onButtonClick: () -> Unit,
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuItem>,
onGenerateContextMenu: (StatusEntry) -> List<ContextMenuElement>,
) {
val hoverInteraction = remember { MutableInteractionSource() }
val isHovered by hoverInteraction.collectIsHoveredAsState()
@ -582,7 +579,7 @@ private fun FileEntry(
.fillMaxWidth()
.hoverable(hoverInteraction)
) {
ContextMenuArea(
ContextMenu(
items = {
onGenerateContextMenu(statusEntry)
},

View File

@ -1,7 +1,5 @@
package com.jetpackduba.gitnuro.ui.components
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
@ -10,6 +8,8 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenuElement
@OptIn(ExperimentalFoundationApi::class)
@Composable
@ -21,13 +21,13 @@ fun <T> SideMenuPanel(
onExpand: () -> Unit,
itemContent: @Composable (T) -> Unit,
headerHoverIcon: @Composable (() -> Unit)? = null,
contextItems: () -> List<ContextMenuItem> = { emptyList() },
contextItems: () -> List<ContextMenuElement> = { emptyList() },
) {
VerticalExpandable(
isExpanded = isExpanded,
onExpand = onExpand,
header = {
ContextMenuArea(
ContextMenu(
items = contextItems
) {
SideMenuEntry(

View File

@ -1,12 +1,10 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.res.painterResource
import com.jetpackduba.gitnuro.extensions.isHead
import com.jetpackduba.gitnuro.extensions.simpleLogName
import org.eclipse.jgit.lib.Ref
@OptIn(ExperimentalFoundationApi::class)
fun branchContextMenuItems(
branch: Ref,
isCurrentBranch: Boolean,
@ -19,47 +17,51 @@ fun branchContextMenuItems(
onDeleteRemoteBranch: () -> Unit = {},
onPushToRemoteBranch: () -> Unit,
onPullFromRemoteBranch: () -> Unit,
): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply {
): List<ContextMenuElement> {
return mutableListOf<ContextMenuElement>().apply {
if (!isCurrentBranch) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Checkout branch",
icon = { painterResource("start.svg") },
onClick = onCheckoutBranch
)
)
if (currentBranch != null && !currentBranch.isHead) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Merge branch",
onClick = onMergeBranch
)
)
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Rebase branch",
onClick = onRebaseBranch
)
)
add(ContextMenuElement.ContextSeparator)
}
}
if (isLocal && !isCurrentBranch) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Delete branch",
icon = { painterResource("delete.svg") },
onClick = onDeleteBranch
)
)
}
if (!isLocal && currentBranch != null && !currentBranch.isHead) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Push ${currentBranch.simpleLogName} to ${branch.simpleLogName}",
onClick = onPushToRemoteBranch
)
)
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Pull ${branch.simpleLogName} to ${currentBranch.simpleLogName}",
onClick = onPullFromRemoteBranch
)
@ -67,11 +69,16 @@ fun branchContextMenuItems(
}
if (!isLocal) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Delete remote branch",
icon = { painterResource("delete.svg") },
onClick = onDeleteRemoteBranch
),
)
}
if(lastOrNull() == ContextMenuElement.ContextSeparator) {
removeLast()
}
}
}

View File

@ -2,6 +2,7 @@ package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.res.painterResource
import org.eclipse.jgit.diff.DiffEntry
@OptIn(ExperimentalFoundationApi::class)
@ -9,19 +10,20 @@ fun commitedChangesEntriesContextMenuItems(
diffEntry: DiffEntry,
onBlame: () -> Unit,
onHistory: () -> Unit,
): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply {
): List<ContextMenuElement> {
return mutableListOf<ContextMenuElement>().apply {
if (diffEntry.changeType != DiffEntry.ChangeType.ADD ||
diffEntry.changeType != DiffEntry.ChangeType.DELETE
) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Blame file",
icon = { painterResource("blame.svg") },
onClick = onBlame,
)
)
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "File history",
onClick = onHistory,
)

View File

@ -0,0 +1,226 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.awtEventOrNull
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.unit.*
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.theme.primaryTextColor
import com.jetpackduba.gitnuro.theme.secondaryTextColor
import java.awt.event.MouseEvent
import kotlin.math.abs
private var lastCheck: Long = 0
private const val MIN_TIME_BETWEEN_POPUPS = 20
@Composable
fun ContextMenu(items: () -> List<ContextMenuElement>, function: @Composable () -> Unit) {
Box(modifier = Modifier.contextMenu(items), propagateMinConstraints = true) {
function()
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun Modifier.contextMenu(items: () -> List<ContextMenuElement>): Modifier {
var lastMouseEventState by remember { mutableStateOf<MouseEvent?>(null) }
val mod = this.pointerInput(Unit) {
while (true) {
val lastMouseEvent = awaitPointerEventScope { awaitEventFirstDown() }
val mouseEvent = lastMouseEvent.awtEventOrNull
if (mouseEvent != null) {
if (!lastMouseEvent.toString().contains("MOUSE_MOVED"))
println(lastMouseEvent.toString())
if (lastMouseEvent.button.isSecondary) {
val currentCheck = System.currentTimeMillis()
if (lastCheck != 0L && currentCheck - lastCheck < MIN_TIME_BETWEEN_POPUPS) {
println("IGNORE POPUP TRIGGERED!")
} else {
println("POPUP TRIGGERED!")
println("X: ${mouseEvent.x}\nY: ${mouseEvent.y}")
lastCheck = currentCheck
lastMouseEventState = mouseEvent
}
}
}
}
}
if (lastMouseEventState != null) {
showPopup(
lastMouseEventState!!.x,
lastMouseEventState!!.y,
items(),
onDismissRequest = { lastMouseEventState = null })
}
return mod
}
@Composable
fun showPopup(x: Int, y: Int, contextMenuElements: List<ContextMenuElement>, onDismissRequest: () -> Unit) {
LaunchedEffect(contextMenuElements) {
println("Items count ${contextMenuElements.count()}")
}
Popup(
focusable = true,
popupPositionProvider = object : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
val resultY = if (popupContentSize.height > windowSize.height) {
0 // If the popup is taller than the window itself
} else if (y + popupContentSize.height > windowSize.height) {
// If the end of the popup would go out of bounds.
// Move the Y a bit to the top to make it fit
y - abs(windowSize.height - (y + popupContentSize.height))
} else {
y
}
val resultX = if (x + popupContentSize.width > windowSize.width && popupContentSize.width < x) {
// If the end of the popup would go out of bounds.
// Move the X a bit to the left to make it fit
x - abs(windowSize.width - (x + popupContentSize.width))
} else {
x
}
return IntOffset(resultX, resultY)
}
},
onDismissRequest = onDismissRequest
) {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Box(
modifier = Modifier
.shadow(4.dp)
.width(300.dp)
.background(MaterialTheme.colors.background)
.run {
return@run if (!MaterialTheme.colors.isLight) {
this.border(1.dp, MaterialTheme.colors.primaryTextColor.copy(alpha = 0.2f))
} else
this
}
.focusRequester(focusRequester)
.focusable()
.onPreviewKeyEvent { keyEvent ->
if (keyEvent.matchesBinding(KeybindingOption.EXIT)) {
onDismissRequest()
true
} else
false
},
) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
for (item in contextMenuElements) {
when (item) {
is ContextMenuElement.ContextTextEntry -> TextEntry(item, onDismissRequest = onDismissRequest)
ContextMenuElement.ContextSeparator -> Separator()
}
}
}
}
}
}
@Composable
fun Separator() {
Box(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
.height(1.dp)
.background(MaterialTheme.colors.primaryTextColor.copy(alpha = 0.4f))
)
}
@Composable
internal fun focusRequesterAndModifier(): Pair<FocusRequester, Modifier> {
val focusRequester = remember { FocusRequester() }
return focusRequester to Modifier.focusRequester(focusRequester)
}
@Composable
fun TextEntry(contextTextEntry: ContextMenuElement.ContextTextEntry, onDismissRequest: () -> Unit) {
val icon = contextTextEntry.icon
Row(
modifier = Modifier
.clickable {
onDismissRequest()
contextTextEntry.onClick()
}
.padding(horizontal = 16.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.size(24.dp).padding(end = 8.dp)) {
if (icon != null) {
Icon(
painter = icon(),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
tint = (MaterialTheme.colors.secondaryTextColor.copy(alpha = 0.8f))
)
}
}
Text(
contextTextEntry.label,
style = MaterialTheme.typography.body2,
modifier = Modifier.fillMaxWidth(),
)
}
}
sealed interface ContextMenuElement {
data class ContextTextEntry(
val label: String,
val icon: @Composable (() -> Painter)? = null,
val onClick: () -> Unit = {}
) : ContextMenuElement
object ContextSeparator : ContextMenuElement
}
private suspend fun AwaitPointerEventScope.awaitEventFirstDown(): PointerEvent {
var event: PointerEvent
do {
event = awaitPointerEvent()
} while (
!event.changes.fastAll { it.changedToDown() }
)
return event
}

View File

@ -1,7 +1,6 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.res.painterResource
fun logContextMenu(
onCheckoutCommit: () -> Unit,
@ -12,32 +11,38 @@ fun logContextMenu(
onResetBranch: () -> Unit,
onRebaseInteractive: () -> Unit,
) = listOf(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Checkout commit",
icon = { painterResource("start.svg") },
onClick = onCheckoutCommit
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Create branch",
icon = { painterResource("branch.svg") },
onClick = onCreateNewBranch
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Create tag",
icon = { painterResource("tag.svg") },
onClick = onCreateNewTag
),
ContextMenuItem(
ContextMenuElement.ContextSeparator,
ContextMenuElement.ContextTextEntry(
label = "Rebase interactive",
onClick = onRebaseInteractive
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Revert commit",
icon = { painterResource("revert.svg") },
onClick = onRevertCommit
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Cherry-pick commit",
onClick = onCherryPickCommit
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Reset current branch to this commit",
icon = { painterResource("undo.svg") },
onClick = onResetBranch
),
)

View File

@ -4,13 +4,15 @@ package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.res.painterResource
fun remoteBranchesContextMenu(
onDeleteBranch: () -> Unit
): List<ContextMenuItem> {
return mutableListOf(
ContextMenuItem(
): List<ContextMenuElement> {
return listOf(
ContextMenuElement.ContextTextEntry(
label = "Delete remote branch",
icon = { painterResource("delete.svg") },
onClick = onDeleteBranch
),
)

View File

@ -1,13 +1,9 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
@OptIn(ExperimentalFoundationApi::class)
fun remoteContextMenu(
onEditRemotes: () -> Unit,
) = listOf(
ContextMenuItem(
): List<ContextMenuElement> = listOf(
ContextMenuElement.ContextTextEntry(
label = "Edit remotes",
onClick = onEditRemotes
),

View File

@ -1,23 +0,0 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import com.jetpackduba.gitnuro.git.workspace.StatusEntry
import com.jetpackduba.gitnuro.git.workspace.StatusType
@OptIn(ExperimentalFoundationApi::class)
fun stagedEntriesContextMenuItems(
diffEntry: StatusEntry,
onReset: () -> Unit,
): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply {
if (diffEntry.statusType != StatusType.ADDED) {
add(
ContextMenuItem(
label = "Reset",
onClick = onReset,
)
)
}
}
}

View File

@ -1,25 +1,26 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.res.painterResource
@OptIn(ExperimentalFoundationApi::class)
fun stashesContextMenuItems(
onApply: () -> Unit,
onPop: () -> Unit,
onDelete: () -> Unit,
): List<ContextMenuItem> {
return mutableListOf(
ContextMenuItem(
): List<ContextMenuElement> {
return listOf(
ContextMenuElement.ContextTextEntry(
label = "Apply stash",
icon = { painterResource("apply_stash.svg") },
onClick = onApply
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Pop stash",
icon = { painterResource("apply_stash.svg") },
onClick = onPop
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Drop stash",
icon = { painterResource("delete.svg") },
onClick = onDelete
),
)

View File

@ -1,11 +1,9 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.res.painterResource
import com.jetpackduba.gitnuro.git.workspace.StatusEntry
import com.jetpackduba.gitnuro.git.workspace.StatusType
@OptIn(ExperimentalFoundationApi::class)
fun statusEntriesContextMenuItems(
statusEntry: StatusEntry,
entryType: EntryType,
@ -13,26 +11,28 @@ fun statusEntriesContextMenuItems(
onDelete: () -> Unit = {},
onBlame: () -> Unit,
onHistory: () -> Unit,
): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply {
): List<ContextMenuElement> {
return mutableListOf<ContextMenuElement>().apply {
if (statusEntry.statusType != StatusType.ADDED) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Reset",
icon = { painterResource("undo.svg") },
onClick = onReset,
)
)
if (statusEntry.statusType != StatusType.REMOVED) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Blame file",
icon = { painterResource("blame.svg") },
onClick = onBlame,
)
)
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "File history",
onClick = onHistory,
)
@ -45,8 +45,9 @@ fun statusEntriesContextMenuItems(
statusEntry.statusType != StatusType.REMOVED
) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Delete file",
icon = { painterResource("delete.svg") },
onClick = onDelete,
)
)

View File

@ -1,19 +1,16 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import org.eclipse.jgit.submodule.SubmoduleStatus
import org.eclipse.jgit.submodule.SubmoduleStatusType
@OptIn(ExperimentalFoundationApi::class)
fun submoduleContextMenuItems(
submoduleStatus: SubmoduleStatus,
onInitializeModule: () -> Unit,
): List<ContextMenuItem> {
return mutableListOf<ContextMenuItem>().apply {
): List<ContextMenuElement> {
return mutableListOf<ContextMenuElement>().apply {
if (submoduleStatus.type == SubmoduleStatusType.UNINITIALIZED) {
add(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Initialize submodule",
onClick = onInitializeModule
)

View File

@ -1,20 +1,21 @@
package com.jetpackduba.gitnuro.ui.context_menu
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.res.painterResource
@OptIn(ExperimentalFoundationApi::class)
fun tagContextMenuItems(
onCheckoutTag: () -> Unit,
onDeleteTag: () -> Unit,
): List<ContextMenuItem> {
): List<ContextMenuElement> {
return mutableListOf(
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Checkout tag",
icon = { painterResource("start.svg") },
onClick = onCheckoutTag
),
ContextMenuItem(
ContextMenuElement.ContextTextEntry(
label = "Delete tag",
icon = { painterResource("delete.svg") },
onClick = onDeleteTag
)
)

View File

@ -38,19 +38,14 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.app.extensions.*
import com.jetpackduba.gitnuro.git.workspace.StatusSummary
import com.jetpackduba.gitnuro.git.graph.GraphCommitList
import com.jetpackduba.gitnuro.git.graph.GraphNode
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.app.theme.*
import com.jetpackduba.gitnuro.ui.SelectedItem
import com.jetpackduba.gitnuro.ui.components.AvatarImage
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
import com.jetpackduba.gitnuro.ui.context_menu.branchContextMenuItems
import com.jetpackduba.gitnuro.ui.context_menu.logContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.tagContextMenuItems
import com.jetpackduba.gitnuro.ui.dialogs.NewBranchDialog
import com.jetpackduba.gitnuro.ui.dialogs.NewTagDialog
import com.jetpackduba.gitnuro.ui.dialogs.ResetBranchDialog
@ -59,6 +54,7 @@ import com.jetpackduba.gitnuro.viewmodels.LogStatus
import com.jetpackduba.gitnuro.viewmodels.LogViewModel
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.theme.*
import com.jetpackduba.gitnuro.ui.context_menu.*
import kotlinx.coroutines.launch
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.RepositoryState
@ -750,7 +746,7 @@ fun CommitLine(
onRevCommitSelected: () -> Unit,
onRebaseInteractive: () -> Unit,
) {
ContextMenuArea(
ContextMenu(
items = {
logContextMenu(
onCheckoutCommit = { logViewModel.checkoutCommit(graphNode) },
@ -1122,7 +1118,7 @@ fun TagChip(
)
}
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@Composable
fun RefChip(
modifier: Modifier = Modifier,
@ -1130,7 +1126,7 @@ fun RefChip(
icon: String,
color: Color,
onCheckoutRef: () -> Unit,
contextMenuItemsList: () -> List<ContextMenuItem>,
contextMenuItemsList: () -> List<ContextMenuElement>,
endingContent: @Composable () -> Unit = {},
) {
Box(
@ -1141,7 +1137,7 @@ fun RefChip(
.combinedClickable(onDoubleClick = onCheckoutRef, onClick = {})
.pointerHoverIcon(PointerIconDefaults.Hand)
) {
ContextMenuArea(
ContextMenu(
items = contextMenuItemsList
) {
Row(

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><path d="M7,9H2V7h5V9z M7,12H2v2h5V12z M20.59,19l-3.83-3.83C15.96,15.69,15.02,16,14,16c-2.76,0-5-2.24-5-5s2.24-5,5-5s5,2.24,5,5 c0,1.02-0.31,1.96-0.83,2.75L22,17.59L20.59,19z M17,11c0-1.65-1.35-3-3-3s-3,1.35-3,3s1.35,3,3,3S17,12.65,17,11z M2,19h10v-2H2 V19z"/></g></svg>

After

Width:  |  Height:  |  Size: 455 B

View File

@ -1,4 +1,7 @@
<?xml version="1.0" ?>
<svg height="1024" width="640" xmlns="http://www.w3.org/2000/svg">
<path d="M512 192c-70.625 0-128 57.344-128 128 0 47.219 25.875 88.062 64 110.281V448c0 0 0 128-128 128-53.062 0-94.656 11.375-128 28.812V302.28099999999995c38.156-22.219 64-63.062 64-110.281 0-70.656-57.344-128-128-128S0 121.34400000000005 0 192c0 47.219 25.844 88.062 64 110.281V721.75C25.844 743.938 0 784.75 0 832c0 70.625 57.344 128 128 128s128-57.375 128-128c0-33.5-13.188-63.75-34.25-86.625C240.375 722.5 270.656 704 320 704c254 0 256-256 256-256v-17.719c38.125-22.219 64-63.062 64-110.281C640 249.34400000000005 582.625 192 512 192zM128 128c35.406 0 64 28.594 64 64s-28.594 64-64 64-64-28.594-64-64S92.594 128 128 128zM128 896c-35.406 0-64-28.625-64-64 0-35.312 28.594-64 64-64s64 28.688 64 64C192 867.375 163.406 896 128 896zM512 384c-35.375 0-64-28.594-64-64s28.625-64 64-64 64 28.594 64 64S547.375 384 512 384z"/>
</svg>
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="14" y1="15" x2="14" y2="40" stroke="black" stroke-width="4"/>
<circle cx="14" cy="8" r="6" stroke="black" stroke-width="4"/>
<path d="M46 19C46 22.3137 43.3137 25 40 25C36.6863 25 34 22.3137 34 19C34 15.6863 36.6863 13 40 13C43.3137 13 46 15.6863 46 19Z" stroke="black" stroke-width="4"/>
<circle cx="14" cy="46" r="6" stroke="black" stroke-width="4"/>
<path d="M13.8484 38.636L15.9135 36.8638C18.4322 34.7022 21.6179 33.4731 24.9357 33.3828L31.5028 33.204C35.676 33.0904 39 29.6747 39 25.5V25.5" stroke="black" stroke-width="4"/>
</svg>

Before

Width:  |  Height:  |  Size: 924 B

After

Width:  |  Height:  |  Size: 644 B

View File

@ -1,4 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6m0-2C9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96C18.67 6.59 15.64 4 12 4z"/></svg>

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 480 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z"/></svg>

After

Width:  |  Height:  |  Size: 252 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z"/></svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><rect fill="none" height="24" width="24"/><path d="M14.59,7.41L18.17,11H6v2h12.17l-3.59,3.59L16,18l6-6l-6-6L14.59,7.41z M2,6v12h2V6H2z"/></svg>

After

Width:  |  Height:  |  Size: 279 B

View File

@ -1,4 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58s1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41s-.23-1.06-.59-1.42zM13 20.01L4 11V4h7v-.01l9 9-7 7.02z"/><circle cx="6.5" cy="6.5" r="1.5"/></svg>

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 402 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/><path d="M20,6h-8l-2-2H4C2.9,4,2.01,4.9,2.01,6L2,18c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8C22,6.9,21.1,6,20,6z M14,16H6v-2h8V16z M18,12H6v-2h12V12z"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/><path d="M20,6h-8l-2-2H4C2.9,4,2.01,4.9,2.01,6L2,18c0,1.1,0.9,2,2,2h16.77c0.68,0,1.23-0.56,1.23-1.23V8C22,6.9,21.1,6,20,6z M20,18L4,18V6h5.17l2,2H20V18z M18,12H6v-2h12V12z M14,16H6v-2h8V16z"/></g></svg>

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/></svg>

After

Width:  |  Height:  |  Size: 301 B