mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-317864 Use the new scroll pane tracker for toolbars and headers
Specifically, in SimpleToolWindowPanel and ToolWindowImpl for painting the border and the tool window header. To avoid double borders with ScrollableContentBorder, introduce client properties that indicate that there's already a border nearby, so there's no need to paint another one. Because ScrollableContentBorder can be installed on a different component than the scroll pane itself, also introduce a weak reference property to indicate which target component the border is installed on, so that all checks work properly even if the scroll pane is located elsewhere. GitOrigin-RevId: 602998a9346ce2503e56bc9823fb53bc6af8c248
This commit is contained in:
committed by
intellij-monorepo-bot
parent
322e2e9db2
commit
a018819d3a
@@ -5,21 +5,19 @@ import com.intellij.openapi.actionSystem.ActionToolbar;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.DataProvider;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.ui.ClientProperty;
|
||||
import com.intellij.ui.ExperimentalUI;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.components.JBPanelWithEmptyText;
|
||||
import com.intellij.ui.paint.LinePainter2D;
|
||||
import com.intellij.ui.switcher.QuickActionProvider;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.AdjustmentListener;
|
||||
import java.awt.event.ContainerAdapter;
|
||||
import java.awt.event.ContainerEvent;
|
||||
import java.util.Collections;
|
||||
@@ -27,6 +25,7 @@ import java.util.List;
|
||||
|
||||
public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements QuickActionProvider, DataProvider {
|
||||
public static final Key<Boolean> SCROLLED_STATE = Key.create("ScrolledState");
|
||||
private static final int GAP = 1;
|
||||
|
||||
private JComponent myToolbar;
|
||||
private JComponent myContent;
|
||||
@@ -35,6 +34,8 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick
|
||||
protected boolean myVertical;
|
||||
private boolean myProvideQuickActions = true;
|
||||
|
||||
private @Nullable ScrollPaneTracker myScrollPaneTracker;
|
||||
|
||||
public SimpleToolWindowPanel(boolean vertical) {
|
||||
this(vertical, false);
|
||||
}
|
||||
@@ -66,6 +67,28 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (ExperimentalUI.isNewUI()) {
|
||||
myScrollPaneTracker = new ScrollPaneTracker(this, this::isInContent, () -> {
|
||||
updateScrolledState();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInContent(@NotNull Component component) {
|
||||
// This check is just in case we have something scrollable in the toolbar.
|
||||
var parent = component;
|
||||
while (parent != null) {
|
||||
if (parent == myToolbar) {
|
||||
return false;
|
||||
}
|
||||
else if (parent == myContent) {
|
||||
return true;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isVertical() {
|
||||
@@ -90,7 +113,7 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick
|
||||
}
|
||||
|
||||
private void updateLayout() {
|
||||
setLayout(new BorderLayout(myVertical ? 0 : 1, myVertical ? 1 : 0));
|
||||
setLayout(new BorderLayout(myVertical ? 0 : GAP, myVertical ? GAP : 0));
|
||||
}
|
||||
|
||||
public void setToolbar(@Nullable JComponent c) {
|
||||
@@ -147,24 +170,12 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick
|
||||
|
||||
public void setContent(@NotNull JComponent c) {
|
||||
if (myContent != null) {
|
||||
ClientProperty.remove(myContent, SCROLLED_STATE);
|
||||
remove(myContent);
|
||||
}
|
||||
|
||||
myContent = c;
|
||||
|
||||
if (ExperimentalUI.isNewUI()) {
|
||||
JScrollPane scrollPane = UIUtil.findComponentOfType(myContent, JScrollPane.class);
|
||||
AdjustmentListener listener = event -> {
|
||||
ClientProperty.put(myContent, SCROLLED_STATE, event.getAdjustable().getValue() != 0);
|
||||
repaint();
|
||||
};
|
||||
|
||||
if (scrollPane != null) {
|
||||
scrollPane.getVerticalScrollBar().addAdjustmentListener(listener);
|
||||
scrollPane.getHorizontalScrollBar().addAdjustmentListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
add(c, BorderLayout.CENTER);
|
||||
|
||||
if (myBorderless) {
|
||||
@@ -182,11 +193,8 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick
|
||||
if (myToolbar != null && myToolbar.getParent() == this && myContent != null && myContent.getParent() == this) {
|
||||
g.setColor(JBColor.border());
|
||||
|
||||
if (ExperimentalUI.isNewUI()) {
|
||||
//don't draw line for scrolled content
|
||||
if (Boolean.FALSE.equals(ClientProperty.get(myContent, SCROLLED_STATE))) {
|
||||
if (ExperimentalUI.isNewUI() && !isScrolled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (myVertical) {
|
||||
int y = (int)myToolbar.getBounds().getMaxY();
|
||||
@@ -206,4 +214,53 @@ public class SimpleToolWindowPanel extends JBPanelWithEmptyText implements Quick
|
||||
}
|
||||
return toolbars.flatten(ActionToolbar::getActions).toList();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateScrolledState() {
|
||||
if (myContent == null || myScrollPaneTracker == null) {
|
||||
return;
|
||||
}
|
||||
var oldState = isScrolled();
|
||||
var newState = false;
|
||||
for (ScrollPaneScrolledState scrollPaneState : myScrollPaneTracker.getScrollPaneStates()) {
|
||||
var scrollPane = scrollPaneState.getScrollPane();
|
||||
boolean scrolled = myVertical ? !scrollPaneState.isVerticalAtStart() : !scrollPaneState.isHorizontalAtStart();
|
||||
if (isTouchingToolbar(scrollPane) && scrolled) {
|
||||
newState = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newState != oldState) {
|
||||
ClientProperty.put(myContent, SCROLLED_STATE, newState);
|
||||
repaint();
|
||||
}
|
||||
var key = myVertical ? ScrollableContentBorder.TOOLBAR_WITH_BORDER_ABOVE : ScrollableContentBorder.TOOLBAR_WITH_BORDER_LEFT;
|
||||
for (ScrollPaneScrolledState scrollPaneState : myScrollPaneTracker.getScrollPaneStates()) {
|
||||
var targetComponent = ScrollableContentBorder.getTargetComponent(scrollPaneState.getScrollPane());
|
||||
if (targetComponent == null) {
|
||||
continue;
|
||||
}
|
||||
var hadToolbarWithBorder = ClientProperty.isTrue(targetComponent, key);
|
||||
var hasToolbarWithBorder = isTouchingToolbar(targetComponent) && (!ExperimentalUI.isNewUI() || newState);
|
||||
if (hasToolbarWithBorder != hadToolbarWithBorder) {
|
||||
ClientProperty.put(targetComponent, key, hasToolbarWithBorder);
|
||||
targetComponent.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isScrolled() {
|
||||
return ClientProperty.isTrue(myContent, SCROLLED_STATE);
|
||||
}
|
||||
|
||||
private boolean isTouchingToolbar(JComponent component) {
|
||||
var toolbar = getToolbar();
|
||||
if (toolbar == null || !toolbar.isVisible() || !component.isShowing()) {
|
||||
return false;
|
||||
}
|
||||
var expectedCoordinate = (myVertical ? toolbar.getHeight() : toolbar.getWidth()) + GAP;
|
||||
var paneLocation = SwingUtilities.convertPoint(component.getParent(), component.getLocation(), this);
|
||||
var actualCoordinate = myVertical ? paneLocation.getY() : paneLocation.getX();
|
||||
return expectedCoordinate == actualCoordinate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ui
|
||||
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.awt.Color
|
||||
import java.awt.Component
|
||||
import java.awt.Graphics
|
||||
import java.beans.PropertyChangeEvent
|
||||
import java.beans.PropertyChangeListener
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JScrollPane
|
||||
import javax.swing.border.Border
|
||||
@@ -29,7 +33,33 @@ class ScrollableContentBorder private constructor(
|
||||
|
||||
fun isVisible(): Boolean = lineColor != UIUtil.TRANSPARENT_COLOR
|
||||
|
||||
override fun paintBorder(c: Component, g: Graphics, x: Int, y: Int, width: Int, height: Int) {
|
||||
val alreadyHasBorder = when (mySideMask) {
|
||||
TOP -> ClientProperty.isTrue(c, HEADER_WITH_BORDER_ABOVE) || ClientProperty.isTrue(c, TOOLBAR_WITH_BORDER_ABOVE)
|
||||
LEFT -> ClientProperty.isTrue(c, TOOLBAR_WITH_BORDER_LEFT)
|
||||
else -> false
|
||||
}
|
||||
if (!alreadyHasBorder) {
|
||||
super.paintBorder(c, g, x, y, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@ApiStatus.Internal
|
||||
@JvmField
|
||||
val HEADER_WITH_BORDER_ABOVE: Key<Boolean> = Key.create("HEADER_WITH_BORDER_ABOVE")
|
||||
|
||||
@ApiStatus.Internal
|
||||
@JvmField
|
||||
val TOOLBAR_WITH_BORDER_ABOVE: Key<Boolean> = Key.create("TOOLBAR_WITH_BORDER_ABOVE")
|
||||
|
||||
@ApiStatus.Internal
|
||||
@JvmField
|
||||
val TOOLBAR_WITH_BORDER_LEFT: Key<Boolean> = Key.create("TOOLBAR_WITH_BORDER_LEFT")
|
||||
|
||||
private val TARGET_COMPONENT: Key<WeakReference<JComponent>> = Key.create("ScrollableContentBorder.TARGET_COMPONENT")
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun setup(scrollPane: JScrollPane,
|
||||
@@ -44,6 +74,8 @@ class ScrollableContentBorder private constructor(
|
||||
sides: Set<Side>,
|
||||
targetComponent: JComponent = scrollPane) {
|
||||
|
||||
ClientProperty.put(scrollPane, TARGET_COMPONENT, WeakReference(targetComponent))
|
||||
|
||||
val borders = sides.associateWith { side -> ScrollableContentBorder(side.toMask()) }
|
||||
|
||||
val tracker = ScrollPaneScrolledStateTracker(scrollPane) { state ->
|
||||
@@ -55,6 +87,7 @@ class ScrollableContentBorder private constructor(
|
||||
override fun propertyChange(evt: PropertyChangeEvent?) {
|
||||
targetComponent.removePropertyChangeListener("border", this)
|
||||
tracker.detach()
|
||||
ClientProperty.put(scrollPane, TARGET_COMPONENT, null)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -66,6 +99,9 @@ class ScrollableContentBorder private constructor(
|
||||
sequenceOf(top, bottom, right, left).filter { it != 0 }.count() == 1
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getTargetComponent(scrollPane: JScrollPane): JComponent? = ClientProperty.get(scrollPane, TARGET_COMPONENT)?.get()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,7 @@ import com.intellij.toolWindow.FocusTask
|
||||
import com.intellij.toolWindow.InternalDecoratorImpl
|
||||
import com.intellij.toolWindow.ToolWindowEventSource
|
||||
import com.intellij.toolWindow.ToolWindowProperty
|
||||
import com.intellij.ui.ClientProperty
|
||||
import com.intellij.ui.ComponentTreeWatcher
|
||||
import com.intellij.ui.LayeredIcon
|
||||
import com.intellij.ui.UIBundle
|
||||
import com.intellij.ui.*
|
||||
import com.intellij.ui.content.Content
|
||||
import com.intellij.ui.content.ContentManager
|
||||
import com.intellij.ui.content.ContentManagerEvent
|
||||
@@ -98,6 +95,7 @@ internal class ToolWindowImpl(val toolWindowManager: ToolWindowManagerImpl,
|
||||
|
||||
internal var decorator: InternalDecoratorImpl? = null
|
||||
private set
|
||||
private var scrollPaneTracker: ScrollPaneTracker? = null
|
||||
|
||||
private var hideOnEmptyContent = false
|
||||
var isPlaceholderMode: Boolean = false
|
||||
@@ -149,7 +147,6 @@ internal class ToolWindowImpl(val toolWindowManager: ToolWindowManagerImpl,
|
||||
private class UpdateBackgroundContentManager(private val decorator: InternalDecoratorImpl?) : ContentManagerListener {
|
||||
override fun contentAdded(event: ContentManagerEvent) {
|
||||
InternalDecoratorImpl.setBackgroundRecursively(event.content.component, JBUI.CurrentTheme.ToolWindow.background())
|
||||
addAdjustListener(decorator, event.content.component)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +205,11 @@ internal class ToolWindowImpl(val toolWindowManager: ToolWindowManagerImpl,
|
||||
}
|
||||
override fun unprocessComponent(component: Component) = Unit
|
||||
}.register(decorator)
|
||||
if (ExperimentalUI.isNewUI()) {
|
||||
scrollPaneTracker = ScrollPaneTracker(decorator, { true }) {
|
||||
updateScrolledState()
|
||||
}
|
||||
}
|
||||
|
||||
toolWindowFocusWatcher = ToolWindowFocusWatcher(toolWindow = this, component = decorator)
|
||||
contentManager.addContentManagerListener(object : ContentManagerListener {
|
||||
@@ -230,6 +232,45 @@ internal class ToolWindowImpl(val toolWindowManager: ToolWindowManagerImpl,
|
||||
ToggleToolbarAction.updateToolbarsVisibility(this, PropertiesComponent.getInstance(project))
|
||||
}
|
||||
|
||||
private fun updateScrolledState() {
|
||||
val decorator = this.decorator ?: return
|
||||
val tracker = scrollPaneTracker ?: return
|
||||
val oldState = ClientProperty.isTrue(decorator, SimpleToolWindowPanel.SCROLLED_STATE)
|
||||
var newState = false
|
||||
for (scrollPaneState in tracker.scrollPaneStates) {
|
||||
val scrollPane = scrollPaneState.scrollPane
|
||||
if (isTouchingHeader(scrollPane) && !scrollPaneState.isVerticalAtStart) {
|
||||
newState = true
|
||||
}
|
||||
}
|
||||
if (oldState != newState) {
|
||||
ClientProperty.put(decorator, SimpleToolWindowPanel.SCROLLED_STATE, newState)
|
||||
decorator.header.repaint()
|
||||
}
|
||||
for (scrollPaneState in tracker.scrollPaneStates) {
|
||||
val targetComponent = ScrollableContentBorder.getTargetComponent(scrollPaneState.scrollPane) ?: continue
|
||||
val hadHeaderWithBorder = ClientProperty.isTrue(targetComponent, ScrollableContentBorder.HEADER_WITH_BORDER_ABOVE)
|
||||
val hasHeaderWithBorder = isTouchingHeader(targetComponent) && (anchor == ToolWindowAnchor.BOTTOM || newState)
|
||||
if (hasHeaderWithBorder != hadHeaderWithBorder) {
|
||||
ClientProperty.put(targetComponent, ScrollableContentBorder.HEADER_WITH_BORDER_ABOVE, hasHeaderWithBorder)
|
||||
targetComponent.repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTouchingHeader(component: JComponent): Boolean {
|
||||
val decorator = this.decorator
|
||||
if (decorator == null || !component.isShowing) {
|
||||
return false
|
||||
}
|
||||
else {
|
||||
val header = decorator.header
|
||||
val headerBounds = SwingUtilities.convertRectangle(header.parent, header.bounds, decorator)
|
||||
val paneLocation = SwingUtilities.convertPoint(component.parent, component.location, decorator)
|
||||
return paneLocation.y == headerBounds.y + headerBounds.height
|
||||
}
|
||||
}
|
||||
|
||||
fun onMovedOrResized() {
|
||||
moveOrResizeAlarm.cancelAndRequest()
|
||||
}
|
||||
@@ -593,7 +634,6 @@ internal class ToolWindowImpl(val toolWindowManager: ToolWindowManagerImpl,
|
||||
|
||||
if (toolWindowManager.isNewUi) {
|
||||
InternalDecoratorImpl.setBackgroundRecursively(component = contentManager.value.component, bg = JBUI.CurrentTheme.ToolWindow.background())
|
||||
addAdjustListener(decorator = decorator, component = contentManager.value.component)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,12 +928,3 @@ private class ToolWindowFocusWatcher(private val toolWindow: ToolWindowImpl, com
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAdjustListener(decorator: InternalDecoratorImpl?, component: JComponent) {
|
||||
UIUtil.findComponentOfType(component, JScrollPane::class.java)?.verticalScrollBar?.addAdjustmentListener { event ->
|
||||
decorator?.let {
|
||||
ClientProperty.put(it, SimpleToolWindowPanel.SCROLLED_STATE, event.adjustable?.value != 0)
|
||||
it.header.repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
package com.intellij.ui;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.swing.border.LineBorder;
|
||||
import java.awt.*;
|
||||
@@ -18,7 +19,8 @@ public class SideBorder extends LineBorder {
|
||||
@MagicConstant(flags = {NONE, LEFT, TOP, RIGHT, BOTTOM, ALL})
|
||||
public @interface SideMask {}
|
||||
|
||||
private final int mySideMask;
|
||||
@ApiStatus.Internal
|
||||
protected final int mySideMask;
|
||||
|
||||
public SideBorder(Color color, @SideMask int mask) {
|
||||
this(color, mask, 1);
|
||||
|
||||
Reference in New Issue
Block a user