[PyCharm] Jupyter (fix): Rewritten CollapsingComponent resize logic, removed subscription to IdeEventQueue. #PY-70825 Fixed

GitOrigin-RevId: 0f725da1fa4db2283d71a011cdea2afcfc9bf411
This commit is contained in:
Nikita Pavlenko
2024-08-08 18:44:19 +00:00
committed by intellij-monorepo-bot
parent db375a8c52
commit 0a99d93456
4 changed files with 46 additions and 109 deletions
@@ -1,7 +1,7 @@
package org.jetbrains.plugins.notebooks.visualization.outputs.impl
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.util.EditorUtil
@@ -24,10 +24,12 @@ import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
internal class CollapsingComponent(internal val editor: EditorImpl,
child: JComponent,
internal val resizable: Boolean,
private val collapsedTextSupplier: () -> @NlsSafe String) : JPanel(null) {
internal class CollapsingComponent(
internal val editor: EditorImpl,
child: JComponent,
internal val resizable: Boolean,
private val collapsedTextSupplier: () -> @NlsSafe String,
) : JPanel(null) {
private var customHeight: Int = -1
private val resizeController by lazy {
@@ -36,7 +38,13 @@ internal class CollapsingComponent(internal val editor: EditorImpl,
customHeight = height - insets.run { top + bottom }
}
customHeight += dy
setSize(width, customHeight)
mainComponent.revalidate()
}.apply {
resizeStateDispatcher.addListener { state ->
(border as? CollapsingComponentBorder)?.resized = state != ResizeController.ResizeState.NONE
repaint()
}
}
}
@@ -84,7 +92,7 @@ internal class CollapsingComponent(internal val editor: EditorImpl,
}
override fun remove(index: Int) {
LOG.error("Components should not be deleted from $this", Throwable())
thisLogger().error("Components should not be deleted from $this", Throwable())
super.remove(index)
}
@@ -203,8 +211,6 @@ internal class CollapsingComponent(internal val editor: EditorImpl,
const val COLLAPSING_RECT_WIDTH = 22
private const val COLLAPSING_RECT_MARGIN_Y_BOTTOM = 5
private val LOG by lazy { logger<CollapsingComponent>() }
@JvmStatic
fun collapseRectHorizontalLeft(editor: EditorEx): Int =
(editor.gutterComponentEx.width
@@ -1,77 +0,0 @@
package org.jetbrains.plugins.notebooks.visualization.outputs.impl
import com.intellij.ide.IdeEventQueue
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.ui.ComponentUtil
import com.intellij.util.concurrency.ThreadingAssertions
import org.jetbrains.plugins.notebooks.visualization.outputs.impl.ResizeHandlebarUpdater.Companion.ensureInstalled
import java.awt.AWTEvent
import java.awt.Component
import java.awt.Container
import java.awt.event.MouseEvent
import java.lang.ref.WeakReference
/**
* A global event listener that draws a resizing handlebar below hovered cell outputs. Since cell outputs may contain non-Swing
* components (like JCef), the usual MouseListener from Swing can't be used.
*
* Call [ensureInstalled] to initialize it.
*/
@Service
class ResizeHandlebarUpdater private constructor() : IdeEventQueue.EventDispatcher, Disposable {
companion object {
@JvmStatic
fun ensureInstalled() {
ThreadingAssertions.assertEventDispatchThread()
service<ResizeHandlebarUpdater>()
}
}
private var currentCollapsingComponent = WeakReference<CollapsingComponent?>(null)
init {
IdeEventQueue.getInstance().addDispatcher(this, this)
}
override fun dispatch(e: AWTEvent): Boolean {
if ((e.id != MouseEvent.MOUSE_MOVED && e.id != MouseEvent.MOUSE_WHEEL) || e !is MouseEvent) {
return false
}
setCurrentCollapsingComponent(getCollapsingComponentDepthFirst(e.component, e.x, e.y)?.takeIf { it.resizable })
return false
}
override fun dispose() = Unit
private fun getCollapsingComponentDepthFirst(component: Component, x: Int, y: Int): CollapsingComponent? =
when {
component !is Container || !component.isVisible || !component.contains(x, y) -> null
component is CollapsingComponent -> component
else ->
component.components.firstNotNullOfOrNull { child ->
val loc = child.location
getCollapsingComponentDepthFirst(child, x - loc.x, y - loc.y)
}
}
private fun setCurrentCollapsingComponent(new: CollapsingComponent?) {
val old = currentCollapsingComponent.get()
if (old != new) {
(old?.border as? CollapsingComponentBorder)?.resized = false
(new?.border as? CollapsingComponentBorder)?.resized = true
// We have overlapping elements over the CollapsingComponent - JupyterToolbar, and to draw it properly, we need to repaint the editor.
val component = old ?: new
ComponentUtil.getParentOfType(EditorComponentImpl::class.java, component)?.repaint()
currentCollapsingComponent = WeakReference(new)
}
}
}
@@ -40,7 +40,7 @@ open class InlayComponent : JPanel(BorderLayout()), EditorCustomElementRenderer
resizeController = ResizeController(
component = this,
editor = inlay!!.editor,
deltaSize = this::deltaSize,
deltaSize = ::deltaSize,
)
addMouseMotionListener(resizeController)
addMouseListener(resizeController)
@@ -5,32 +5,46 @@ import com.intellij.openapi.editor.colors.EditorFontType
import com.intellij.openapi.editor.impl.FontInfo
import com.intellij.openapi.editor.impl.view.EditorPainter
import com.intellij.openapi.editor.impl.view.FontLayoutService
import com.intellij.util.EventDispatcher
import com.intellij.util.ui.JBUI
import java.awt.Component
import java.awt.Cursor
import java.awt.Dimension
import java.awt.Point
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
import java.util.EventListener
import javax.swing.JComponent
import javax.swing.SwingUtilities
import kotlin.math.abs
/** Realizes resize of InlayComponent by dragging resize icon in right bottom corner of component. */
/** Realizes height-resize of InlayComponent by dragging resize area in the bottom of the component. */
class ResizeController(
private val component: Component,
private val component: JComponent,
private val editor: Editor,
private val deltaSize: (dx: Int, dy: Int) -> Unit = component::swingDeltaSize,
private val deltaSize: (dx: Int, dy: Int) -> Unit,
) : MouseListener, MouseMotionListener {
fun interface ResizeStateListener : EventListener {
fun onModeChanged(newState: ResizeState)
}
private var prevPoint: Point? = null
private enum class ScaleMode { NONE, N /*, W, NW*/ }
enum class ResizeState { NONE, HOVER, RESIZING }
private var scaleMode = ScaleMode.NONE
private val nResizeCursor = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)
private val defaultCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
private var resizeState: ResizeState = ResizeState.NONE
set(value) {
if (field != value) {
field = value
resizeStateDispatcher.multicaster.onModeChanged(value)
}
}
val resizeStateDispatcher = EventDispatcher.create(ResizeStateListener::class.java)
private fun setCursor(cursor: Cursor) {
if (component.cursor != cursor) {
@@ -38,8 +52,7 @@ class ResizeController(
}
}
override fun mouseReleased(e: MouseEvent?) {
override fun mouseReleased(e: MouseEvent) {
// Snapping to right margin.
if (EditorPainter.isMarginShown(editor) && prevPoint != null) {
@@ -64,10 +77,10 @@ class ResizeController(
prevPoint = null
scaleMode = ScaleMode.NONE
resizeState = if (mouseInResizeArea(e)) ResizeState.HOVER else ResizeState.NONE
}
override fun mousePressed(e: MouseEvent) {
val correctedHeight = component.height - InlayDimensions.bottomBorder
scaleMode = if (e.point.y > correctedHeight) {
@@ -78,10 +91,10 @@ class ResizeController(
}
prevPoint = e.locationOnScreen
resizeState = ResizeState.RESIZING
}
override fun mouseDragged(e: MouseEvent?) {
if (prevPoint == null) {
return
}
@@ -94,31 +107,26 @@ class ResizeController(
prevPoint = locationOnScreen
}
override fun mouseMoved(e: MouseEvent) {
private fun mouseInResizeArea(e: MouseEvent) = e.point.y > component.height - component.insets.bottom
override fun mouseMoved(e: MouseEvent) {
if (scaleMode != ScaleMode.NONE) {
return
}
val correctedHeight = component.height - InlayDimensions.bottomBorder
setCursor(if (e.point.y > correctedHeight) nResizeCursor else defaultCursor)
val canResize = mouseInResizeArea(e)
setCursor(if (canResize) Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR) else Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR))
resizeState = if (canResize) ResizeState.HOVER else ResizeState.NONE
}
override fun mouseExited(e: MouseEvent) {
if (scaleMode == ScaleMode.NONE) {
setCursor(defaultCursor)
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR))
resizeState = ResizeState.NONE
}
}
override fun mouseClicked(e: MouseEvent): Unit = Unit
override fun mouseEntered(e: MouseEvent): Unit = Unit
}
private fun Component.swingDeltaSize(dx: Int, dy: Int) {
val oldSize = size
size = Dimension(oldSize.width + dx, oldSize.height + dy)
preferredSize = size
revalidate()
repaint()
}