diff --git a/platform/platform-api/src/com/intellij/openapi/ui/SimpleToolWindowPanel.java b/platform/platform-api/src/com/intellij/openapi/ui/SimpleToolWindowPanel.java index a75662ca1dda..317cd9dfd4ff 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/SimpleToolWindowPanel.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/SimpleToolWindowPanel.java @@ -69,7 +69,7 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick }); if (ExperimentalUI.isNewUI()) { - myScrollPaneTracker = new ScrollPaneTracker(this, this::isInContent, () -> { + myScrollPaneTracker = new ScrollPaneTracker(this, this::isInContent, tracker -> { updateScrolledState(); return Unit.INSTANCE; }); @@ -223,7 +223,7 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick var newState = false; for (ScrollPaneScrolledState scrollPaneState : myScrollPaneTracker.getScrollPaneStates()) { var scrollPane = scrollPaneState.getScrollPane(); - boolean scrolled = myVertical ? !scrollPaneState.isVerticalAtStart() : !scrollPaneState.isHorizontalAtStart(); + boolean scrolled = myVertical ? !scrollPaneState.getState().isVerticalAtStart() : !scrollPaneState.getState().isHorizontalAtStart(); if (isTouchingToolbar(scrollPane) && scrolled) { newState = true; break; diff --git a/platform/platform-api/src/com/intellij/ui/ScrollPaneTracker.kt b/platform/platform-api/src/com/intellij/ui/ScrollPaneTracker.kt index 504cacc967ce..19aa0c27f104 100644 --- a/platform/platform-api/src/com/intellij/ui/ScrollPaneTracker.kt +++ b/platform/platform-api/src/com/intellij/ui/ScrollPaneTracker.kt @@ -19,6 +19,11 @@ import javax.swing.JScrollPane @Internal data class ScrollPaneScrolledState( val scrollPane: JScrollPane, + val state: ScrolledState, +) + +@Internal +data class ScrolledState( val isHorizontalAtStart: Boolean, val isHorizontalAtEnd: Boolean, val isVerticalAtStart: Boolean, @@ -47,7 +52,7 @@ data class ScrollPaneScrolledState( class ScrollPaneTracker( container: Component, private val filter: (JScrollPane) -> Boolean, - private val callback: () -> Unit, + private val callback: (ScrollPaneTracker) -> Unit, ) { val scrollPaneStates: List @@ -75,9 +80,9 @@ class ScrollPaneTracker( private fun registerScrollPane(scrollPane: JScrollPane) { myTrackers.add(ScrollPaneScrolledStateTracker(scrollPane) { - callback() + callback(this) }) - callback() + callback(this) } private fun unregisterScrollPane(scrollPane: JScrollPane) { @@ -113,10 +118,12 @@ class ScrollPaneScrolledStateTracker(val scrollPane: JScrollPane, private val ca var state: ScrollPaneScrolledState = ScrollPaneScrolledState( scrollPane, - isHorizontalAtStart = true, - isHorizontalAtEnd = true, - isVerticalAtStart = true, - isVerticalAtEnd = true, + ScrolledState( + isHorizontalAtStart = true, + isHorizontalAtEnd = true, + isVerticalAtStart = true, + isVerticalAtEnd = true, + ) ); private set private val scrollBarListener = PropertyChangeListener { e -> @@ -149,10 +156,12 @@ class ScrollPaneScrolledStateTracker(val scrollPane: JScrollPane, private val ca private fun fireCallback(fireAnyway: Boolean) { val newState = ScrollPaneScrolledState( scrollPane, - isHorizontalAtStart = horizontalListener.isAtStart, - isHorizontalAtEnd = horizontalListener.isAtEnd, - isVerticalAtStart = verticalListener.isAtStart, - isVerticalAtEnd = verticalListener.isAtEnd, + ScrolledState( + isHorizontalAtStart = horizontalListener.isAtStart, + isHorizontalAtEnd = horizontalListener.isAtEnd, + isVerticalAtStart = verticalListener.isAtStart, + isVerticalAtEnd = verticalListener.isAtEnd, + ) ) if (fireAnyway || newState != state) { state = newState diff --git a/platform/platform-api/src/com/intellij/ui/ScrollableContentBorder.kt b/platform/platform-api/src/com/intellij/ui/ScrollableContentBorder.kt index cd094de985b0..5354af22099c 100644 --- a/platform/platform-api/src/com/intellij/ui/ScrollableContentBorder.kt +++ b/platform/platform-api/src/com/intellij/ui/ScrollableContentBorder.kt @@ -13,6 +13,7 @@ import java.beans.PropertyChangeListener import java.lang.ref.WeakReference import javax.swing.JComponent import javax.swing.JScrollPane +import javax.swing.SwingUtilities import javax.swing.border.Border @ApiStatus.Experimental @@ -78,8 +79,8 @@ class ScrollableContentBorder private constructor( val borders = sides.associateWith { side -> ScrollableContentBorder(side.toMask()) } - val tracker = ScrollPaneScrolledStateTracker(scrollPane) { state -> - updateBorderVisibility(targetComponent, borders, state) + val tracker = ScrollPaneScrolledStateTracker(scrollPane) { scrollPaneState -> + updateBorderVisibility(targetComponent, borders, scrollPaneState.state) } targetComponent.border = if (borders.size == 1) borders.values.single() else JBUI.Borders.compound(*borders.values.toTypedArray()) @@ -92,6 +93,52 @@ class ScrollableContentBorder private constructor( }) } + @JvmStatic + fun setup(container: JComponent, side: Side) { + setup(container, setOf(side)) + } + + @JvmStatic + fun setup(container: JComponent, sides: Set) { + val borders = sides.associateWith { side -> ScrollableContentBorder(side.toMask()) } + container.border = if (borders.size == 1) borders.values.single() else JBUI.Borders.compound(*borders.values.toTypedArray()) + ScrollPaneTracker(container, { true }) { tracker -> + updateScrollPaneStates(container, borders, tracker) + } + } + + private fun updateScrollPaneStates( + container: JComponent, + borders: Map, + tracker: ScrollPaneTracker, + ) { + for (scrollPaneState in tracker.scrollPaneStates) { + ClientProperty.put(scrollPaneState.scrollPane, TARGET_COMPONENT, WeakReference(container)) + } + val state = ScrolledState( + isHorizontalAtStart = tracker.noneMatch { atLeft(container, it.scrollPane) && !it.state.isHorizontalAtStart }, + isHorizontalAtEnd = tracker.noneMatch { atRight(container, it.scrollPane) && !it.state.isHorizontalAtEnd }, + isVerticalAtStart = tracker.noneMatch { atTop(container, it.scrollPane) && !it.state.isVerticalAtStart }, + isVerticalAtEnd = tracker.noneMatch { atBottom(container, it.scrollPane) && !it.state.isVerticalAtEnd }, + ) + updateBorderVisibility(container, borders, state) + } + + private fun ScrollPaneTracker.noneMatch(predicate: (ScrollPaneScrolledState) -> Boolean): Boolean = + scrollPaneStates.none { it.scrollPane.isShowing && predicate(it) } + + private fun atLeft(container: JComponent, scrollPane: JScrollPane): Boolean = + SwingUtilities.convertPoint(scrollPane.parent, scrollPane.location, container).x == container.insets.left + + private fun atRight(container: JComponent, scrollPane: JScrollPane): Boolean = + SwingUtilities.convertRectangle(scrollPane.parent, scrollPane.bounds, container).let { it.x + it.width } == container.width - container.insets.right + + private fun atTop(container: JComponent, scrollPane: JScrollPane): Boolean = + SwingUtilities.convertPoint(scrollPane.parent, scrollPane.location, container).y == container.insets.top + + private fun atBottom(container: JComponent, scrollPane: JScrollPane): Boolean = + SwingUtilities.convertRectangle(scrollPane.parent, scrollPane.bounds, container).let { it.y + it.height } == container.height - container.insets.bottom + private fun isOneSideBorder(sideBorder: Border): Boolean { if (sideBorder !is SideBorder) return false @@ -108,7 +155,7 @@ class ScrollableContentBorder private constructor( private fun updateBorderVisibility( targetComponent: JComponent, borders: Map, - state: ScrollPaneScrolledState, + state: ScrolledState, ) { var changed = false for ((side, border) in borders) { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt index 90d71c3f41cd..5c5137164217 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt @@ -239,7 +239,7 @@ internal class ToolWindowImpl(val toolWindowManager: ToolWindowManagerImpl, var newState = false for (scrollPaneState in tracker.scrollPaneStates) { val scrollPane = scrollPaneState.scrollPane - if (isTouchingHeader(scrollPane) && !scrollPaneState.isVerticalAtStart) { + if (isTouchingHeader(scrollPane) && !scrollPaneState.state.isVerticalAtStart) { newState = true } }