diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/outputs/impl/CollapsingComponent.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/outputs/impl/CollapsingComponent.kt index a7f5f05a4bef..ca54d41ce5ea 100644 --- a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/outputs/impl/CollapsingComponent.kt +++ b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/outputs/impl/CollapsingComponent.kt @@ -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() } - @JvmStatic fun collapseRectHorizontalLeft(editor: EditorEx): Int = (editor.gutterComponentEx.width diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/outputs/impl/ResizeHandlebarUpdater.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/outputs/impl/ResizeHandlebarUpdater.kt deleted file mode 100644 index d8e79a635960..000000000000 --- a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/outputs/impl/ResizeHandlebarUpdater.kt +++ /dev/null @@ -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() - } - } - - private var currentCollapsingComponent = WeakReference(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) - } - } -} \ No newline at end of file diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/InlayComponent.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/InlayComponent.kt index 273cc6ab7614..ffaa47e97ca6 100644 --- a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/InlayComponent.kt +++ b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/InlayComponent.kt @@ -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) diff --git a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/ResizeController.kt b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/ResizeController.kt index 30b7529cdfe2..a6d5c1b1bc40 100644 --- a/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/ResizeController.kt +++ b/notebooks/visualization/src/org/jetbrains/plugins/notebooks/visualization/r/inlays/ResizeController.kt @@ -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() } \ No newline at end of file