mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[gh] Dim comments when showing the on-hover outline
#IJPL-200029 Fixed GitOrigin-RevId: 2c894f76709047b62fe26db035ee79c3589ef370
This commit is contained in:
committed by
intellij-monorepo-bot
parent
eb41f6abd9
commit
1c604ef58a
+66
-5
@@ -2,10 +2,12 @@
|
||||
package org.jetbrains.plugins.github.pullrequest.ui
|
||||
|
||||
import com.intellij.collaboration.async.combineState
|
||||
import com.intellij.collaboration.async.flatMapLatestEach
|
||||
import com.intellij.collaboration.async.launchNow
|
||||
import com.intellij.collaboration.ui.codereview.diff.DiffLineLocation
|
||||
import com.intellij.collaboration.ui.codereview.editor.CodeReviewCommentableEditorModel
|
||||
import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterControlsModel
|
||||
import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorInlaysModel
|
||||
import com.intellij.diff.util.LineRange
|
||||
import com.intellij.diff.util.Side
|
||||
import com.intellij.openapi.editor.Editor
|
||||
@@ -19,13 +21,18 @@ import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.wm.IdeGlassPaneUtil
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.comment.CommentedCodeFrameRenderer
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.editor.GHPREditorMappedComponentModel
|
||||
import java.awt.Cursor
|
||||
import java.awt.Point
|
||||
import java.awt.*
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLayer
|
||||
import javax.swing.plaf.LayerUI
|
||||
import kotlin.math.abs
|
||||
|
||||
internal object GHPRInlayUtils {
|
||||
@@ -78,9 +85,7 @@ internal object GHPRInlayUtils {
|
||||
finally {
|
||||
if (frameResizer != null) {
|
||||
val editorEx = editor as EditorEx
|
||||
val gutterGlassComp = IdeGlassPaneUtil.find(editorEx.gutterComponentEx)
|
||||
editorEx.setCustomCursor(frameResizer, null)
|
||||
gutterGlassComp.setCursor(null, frameResizer)
|
||||
editor.removeEditorMouseListener(frameResizer)
|
||||
editor.removeEditorMouseMotionListener(frameResizer)
|
||||
}
|
||||
@@ -106,7 +111,6 @@ internal object GHPRInlayUtils {
|
||||
Cursor.getDefaultCursor()
|
||||
}
|
||||
|
||||
|
||||
override fun mouseDragged(e: EditorMouseEvent) {
|
||||
if (!isDraggingFrame || edge == null || oldRange == null) return
|
||||
e.consume() // to prevent selecting text while dragging
|
||||
@@ -203,4 +207,61 @@ internal object GHPRInlayUtils {
|
||||
TOP, BOTTOM
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun installInlaysDimming(cs: CoroutineScope, model: CodeReviewEditorInlaysModel<*>) {
|
||||
cs.launchNow {
|
||||
model.inlays
|
||||
.map { it.filterIsInstance<GHPREditorMappedComponentModel>() }
|
||||
.flatMapLatestEach { item ->
|
||||
combine(item.shouldShowOutline, item.range) { shouldShowOutline, range ->
|
||||
InlayState(item, shouldShowOutline, range)
|
||||
}
|
||||
}
|
||||
.collectLatest { inlayStates ->
|
||||
val rangesToDim = inlayStates
|
||||
.filter { it.shouldShowOutline }
|
||||
.mapNotNull {
|
||||
val (_, lines) = it.range ?: return@mapNotNull null
|
||||
lines.first..<lines.last
|
||||
}
|
||||
|
||||
inlayStates.forEach { (vm, _, range) ->
|
||||
val (_, lines) = range ?: return@forEach
|
||||
val onLine = lines.last
|
||||
|
||||
vm.setDimmed(rangesToDim.any { dimRange -> dimRange.contains(onLine) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class InlayState(
|
||||
val inlay: GHPREditorMappedComponentModel,
|
||||
val shouldShowOutline: Boolean,
|
||||
val range: Pair<Side, IntRange>?,
|
||||
)
|
||||
}
|
||||
|
||||
internal class FadeLayerUI : LayerUI<JComponent>() {
|
||||
private var alpha: Float = 1f
|
||||
|
||||
fun setAlpha(a: Float, jLayer: JLayer<JComponent>) {
|
||||
alpha = a.coerceIn(0f, 1f)
|
||||
jLayer.repaint()
|
||||
}
|
||||
|
||||
override fun paint(g: Graphics, c: JComponent) {
|
||||
val g2 = g.create() as Graphics2D
|
||||
try {
|
||||
val old = g2.composite
|
||||
g2.composite = AlphaComposite.SrcOver.derive(alpha)
|
||||
super.paint(g2, c)
|
||||
g2.composite = old
|
||||
}
|
||||
finally {
|
||||
g2.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -6,4 +6,6 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
interface GHPRHoverableReviewComment {
|
||||
val shouldShowOutline: StateFlow<Boolean>
|
||||
fun showOutline(isHovered: Boolean)
|
||||
val isDimmed: StateFlow<Boolean>
|
||||
fun setDimmed(isDimmed: Boolean)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.github.pullrequest.ui.comment
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class GHPRHoverableReviewCommentImpl : GHPRHoverableReviewComment {
|
||||
private val _isHovered: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
override val shouldShowOutline: StateFlow<Boolean> = _isHovered.asStateFlow()
|
||||
|
||||
override fun showOutline(isHovered: Boolean) {
|
||||
_isHovered.value = isHovered
|
||||
}
|
||||
|
||||
private val _isDimmed: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
override val isDimmed: StateFlow<Boolean> = _isDimmed.asStateFlow()
|
||||
|
||||
override fun setDimmed(isDimmed: Boolean) {
|
||||
_isDimmed.value = isDimmed
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -100,11 +100,15 @@ internal class GHPRReviewDiffExtension : DiffExtension() {
|
||||
val (leftLine, rightLine) = lineToUnified(it)
|
||||
GHPRReviewUnifiedPosition(change, leftLine, rightLine)
|
||||
}.apply {
|
||||
editor.putUserData(GHPRReviewDiffEditorModel.KEY, this)
|
||||
cs.launchNow {
|
||||
inlays
|
||||
.mapStatefulToStateful { inlayModel -> GHPRInlayUtils.installInlayHoverOutline(this, editor, side, locationToLine, inlayModel) }
|
||||
.mapStatefulToStateful { inlayModel ->
|
||||
GHPRInlayUtils.installInlayHoverOutline(this, editor, side, locationToLine, inlayModel)
|
||||
}
|
||||
.collect()
|
||||
}
|
||||
GHPRInlayUtils.installInlaysDimming(cs, this@apply)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -121,6 +121,7 @@ private suspend fun showReview(project: Project, settings: GithubPullRequestsPro
|
||||
launchNow {
|
||||
val userIcon = fileVm.iconProvider.getIcon(fileVm.currentUser.url, 16)
|
||||
editor.renderInlays(model.inlays, HashingUtil.mappingStrategy(GHPREditorMappedComponentModel::key)) {
|
||||
GHPRInlayUtils.installInlaysDimming(this, model)
|
||||
launchNow {
|
||||
model.inlays
|
||||
.mapStatefulToStateful { inlayModel -> GHPRInlayUtils.installInlayHoverOutline(this, editor, Side.RIGHT, null, inlayModel) }
|
||||
|
||||
+7
-25
@@ -12,36 +12,25 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.jetbrains.plugins.github.ai.GHPRAICommentViewModel
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.comment.GHPRCompactReviewThreadViewModel
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.comment.GHPRHoverableReviewComment
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.comment.GHPRHoverableReviewCommentImpl
|
||||
import javax.swing.Icon
|
||||
|
||||
internal sealed interface GHPREditorMappedComponentModel : CodeReviewInlayModel, GHPRHoverableReviewComment {
|
||||
val range: StateFlow<Pair<Side, IntRange>?>
|
||||
|
||||
abstract class Thread<VM : GHPRCompactReviewThreadViewModel>(val vm: VM)
|
||||
: GHPREditorMappedComponentModel, Hideable {
|
||||
: GHPREditorMappedComponentModel, Hideable,
|
||||
GHPRHoverableReviewComment by GHPRHoverableReviewCommentImpl() {
|
||||
final override val key: Any = vm.id
|
||||
final override val hiddenState = MutableStateFlow(false)
|
||||
final override fun setHidden(hidden: Boolean) {
|
||||
hiddenState.value = hidden
|
||||
}
|
||||
|
||||
private val _isHovered: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
override val shouldShowOutline: StateFlow<Boolean> = _isHovered.asStateFlow()
|
||||
|
||||
override fun showOutline(isHovered: Boolean) {
|
||||
_isHovered.value = isHovered
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NewComment<VM : GHPRReviewNewCommentEditorViewModel>(val vm: VM) : GHPREditorMappedComponentModel {
|
||||
private val _isHovered: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
override val shouldShowOutline: StateFlow<Boolean> = _isHovered.asStateFlow()
|
||||
|
||||
override fun showOutline(isHovered: Boolean) {
|
||||
_isHovered.value = isHovered
|
||||
}
|
||||
abstract class NewComment<VM : GHPRReviewNewCommentEditorViewModel>(val vm: VM)
|
||||
: GHPREditorMappedComponentModel, GHPRHoverableReviewComment by GHPRHoverableReviewCommentImpl() {
|
||||
abstract fun setRange(range: Pair<Side, IntRange>?)
|
||||
|
||||
private val _isHidden: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
val isHidden: StateFlow<Boolean> = _isHidden.asStateFlow()
|
||||
fun isHidden(hidden: Boolean) {
|
||||
@@ -50,19 +39,12 @@ internal sealed interface GHPREditorMappedComponentModel : CodeReviewInlayModel,
|
||||
}
|
||||
|
||||
abstract class AIComment(val vm: GHPRAICommentViewModel)
|
||||
: GHPREditorMappedComponentModel, Hideable {
|
||||
: GHPREditorMappedComponentModel, Hideable, GHPRHoverableReviewComment by GHPRHoverableReviewCommentImpl() {
|
||||
final override val key: Any = vm.key
|
||||
final override val hiddenState = MutableStateFlow(false)
|
||||
final override fun setHidden(hidden: Boolean) {
|
||||
hiddenState.value = hidden
|
||||
}
|
||||
|
||||
private val _isHovered: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
override val shouldShowOutline: StateFlow<Boolean> = _isHovered.asStateFlow()
|
||||
|
||||
override fun showOutline(isHovered: Boolean) {
|
||||
_isHovered.value = isHovered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +53,4 @@ internal fun CoroutineScope.createRenderer(model: GHPREditorMappedComponentModel
|
||||
is GHPREditorMappedComponentModel.Thread<*> -> GHPRReviewThreadEditorInlayRenderer(this, model, model.vm)
|
||||
is GHPREditorMappedComponentModel.NewComment<*> -> GHPRNewCommentEditorInlayRenderer(this, model, model.vm)
|
||||
is GHPREditorMappedComponentModel.AIComment -> GHPRAICommentEditorInlayRenderer(userIcon, model.vm)
|
||||
}
|
||||
}
|
||||
+35
-8
@@ -4,17 +4,22 @@ package org.jetbrains.plugins.github.pullrequest.ui.editor
|
||||
import com.intellij.collaboration.async.launchNow
|
||||
import com.intellij.collaboration.ui.codereview.editor.CodeReviewComponentInlayRenderer
|
||||
import com.intellij.collaboration.ui.util.bindContent
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.observable.util.addMouseHoverListener
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import com.intellij.ui.hover.HoverStateListener
|
||||
import com.intellij.util.ui.launchOnShow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.plugins.github.ai.GHPRAICommentViewModel
|
||||
import org.jetbrains.plugins.github.ai.GHPRAIReviewExtension
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.FadeLayerUI
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.comment.GHPRCompactReviewThreadViewModel
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.comment.GHPRHoverableReviewComment
|
||||
import java.awt.Component
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JLayer
|
||||
|
||||
@ApiStatus.Internal
|
||||
class GHPRReviewThreadEditorInlayRenderer internal constructor(
|
||||
@@ -22,8 +27,19 @@ class GHPRReviewThreadEditorInlayRenderer internal constructor(
|
||||
hoverableVm: GHPRHoverableReviewComment,
|
||||
vm: GHPRCompactReviewThreadViewModel,
|
||||
) : CodeReviewComponentInlayRenderer(
|
||||
GHPRReviewEditorComponentsFactory.createThreadIn(cs, vm).apply {
|
||||
addMouseHoverListener(null, MouseOverInlayListener(hoverableVm))
|
||||
run {
|
||||
val fadeLayerUI = FadeLayerUI()
|
||||
val layer = JLayer(
|
||||
GHPRReviewEditorComponentsFactory.createThreadIn(cs, vm).apply {
|
||||
addMouseHoverListener(null, MouseOverInlayListener(hoverableVm))
|
||||
}, fadeLayerUI
|
||||
)
|
||||
layer.launchOnShow("Inlay.Dimming.${vm::javaClass.name}", Dispatchers.EDT) {
|
||||
hoverableVm.isDimmed.collect { isDimmed: Boolean ->
|
||||
fadeLayerUI.setAlpha(if (isDimmed) 0.5f else 1f, layer)
|
||||
}
|
||||
}
|
||||
layer
|
||||
}
|
||||
)
|
||||
|
||||
@@ -34,13 +50,23 @@ class GHPRNewCommentEditorInlayRenderer internal constructor(
|
||||
hoverableVm: GHPRHoverableReviewComment,
|
||||
vm: GHPRReviewNewCommentEditorViewModel,
|
||||
) : CodeReviewComponentInlayRenderer(
|
||||
GHPRReviewEditorComponentsFactory.createNewCommentIn(cs, vm).also { comp ->
|
||||
hoverableVm.showOutline(true)
|
||||
cs.launchNow {
|
||||
(hoverableVm as GHPREditorMappedComponentModel.NewComment<*>).isHidden.collect {
|
||||
comp.isVisible = !it
|
||||
run {
|
||||
val fadeLayerUI = FadeLayerUI()
|
||||
val layer = JLayer(
|
||||
GHPRReviewEditorComponentsFactory.createNewCommentIn(cs, vm).also {
|
||||
hoverableVm.showOutline(true)
|
||||
}, fadeLayerUI)
|
||||
layer.launchOnShow("Inlay.Dimming.${vm::javaClass.name}", Dispatchers.EDT) {
|
||||
hoverableVm.isDimmed.collect { isDimmed: Boolean ->
|
||||
fadeLayerUI.setAlpha(if (isDimmed) 0.5f else 1f, layer)
|
||||
}
|
||||
}
|
||||
cs.launchNow {
|
||||
(hoverableVm as GHPREditorMappedComponentModel.NewComment<*>).isHidden.collect {
|
||||
layer.isVisible = !it
|
||||
}
|
||||
}
|
||||
layer
|
||||
}
|
||||
)
|
||||
|
||||
@@ -52,8 +78,9 @@ internal class GHPRAICommentEditorInlayRenderer internal constructor(userIcon: I
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
private class MouseOverInlayListener(val vm: GHPRHoverableReviewComment) : HoverStateListener() {
|
||||
override fun hoverChanged(component: Component, hovered: Boolean) {
|
||||
vm.showOutline(hovered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user