IDEA-349803 Eliminate the gap before the tab actions' icons

The gap was caused by ToolWindowContentUi.TabPanel pushing the tab actions toolbar (ToolWindowHeader.toolbarWest) to the far right when the preferred width of TabPanel was larger than the sum of widths of its visible children. Since reducing the preferred width would prevent TabPanel from growing when its container becomes wider, the tab actions toolbar has been moved inside ToolWindowContentUi.TabPanel instead, This way the tab actions toolbar can be positioned adjacent to the rightmost visible tab regardless of the TabPanel's preferred width.

Code related to the tab actions toolbar has been moved from ToolWindowHeader to ToolWindowContentUi.

closes https://github.com/JetBrains/intellij-community/pull/2723

GitOrigin-RevId: c653d274efe723066325cac51e8ef48ccc87bad8
This commit is contained in:
Sergey Prigogin
2024-03-27 17:14:59 +00:00
committed by intellij-monorepo-bot
parent 761e92518c
commit 7337a36bd9
6 changed files with 203 additions and 139 deletions
@@ -2,6 +2,7 @@
package com.intellij.openapi.wm.impl.content;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.NlsActions;
import com.intellij.ui.ExperimentalUI;
@@ -9,6 +10,8 @@ import com.intellij.ui.MouseDragHelper;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.ui.JBUI;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
@@ -39,7 +42,6 @@ final class ComboContentLayout extends ContentLayout {
public void layout() {
Rectangle bounds = ui.getTabComponent().getBounds();
Dimension idSize = isIdVisible() ? idLabel.getPreferredSize() : JBUI.emptySize();
int eachX = 0;
int eachY = 0;
@@ -47,7 +49,8 @@ final class ComboContentLayout extends ContentLayout {
eachX += idSize.width;
Dimension comboSize = comboLabel.getPreferredSize();
int spaceLeft = bounds.width - eachX - (isToDrawCombo() && isIdVisible() ? 3 : 0);
int nonLabelWidth = getTabToolbarPreferredWidth();
int spaceLeft = bounds.width - eachX - nonLabelWidth - (isToDrawCombo() && isIdVisible() ? 3 : 0);
int width = comboSize.width;
if (width > spaceLeft) {
@@ -55,11 +58,21 @@ final class ComboContentLayout extends ContentLayout {
}
comboLabel.setBounds(eachX, eachY, width, bounds.height);
eachX += width;
// Tab toolbar is positioned at the end.
ActionToolbar tabToolbar = ui.getTabToolbar();
if (tabToolbar != null) {
JComponent component = tabToolbar.getComponent();
Dimension size = component.getPreferredSize();
component.setBounds(eachX, eachY + (bounds.height - size.height) / 2, size.width, size.height);
eachX += component.getWidth();
}
}
@Override
public int getMinimumWidth() {
return idLabel == null ? 0 : idLabel.getPreferredSize().width;
return (idLabel == null ? 0 : idLabel.getPreferredSize().width) + getTabToolbarPreferredWidth();
}
@Override
@@ -70,13 +83,16 @@ final class ComboContentLayout extends ContentLayout {
@Override
public void rebuild() {
ui.getTabComponent().removeAll();
JPanel tabComponent = ui.getTabComponent();
tabComponent.removeAll();
ui.getTabComponent().add(idLabel);
tabComponent.add(idLabel);
ToolWindowContentUi.initMouseListeners(idLabel, ui, true);
ui.getTabComponent().add(comboLabel);
tabComponent.add(comboLabel);
ToolWindowContentUi.initMouseListeners(comboLabel, ui, false);
ui.connectTabToolbar();
}
boolean isToDrawCombo() {
@@ -1,6 +1,7 @@
// 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.openapi.wm.impl.content;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.NlsActions.ActionText;
import com.intellij.ui.ClientProperty;
@@ -68,6 +69,14 @@ public abstract class ContentLayout {
label.setVisible(shouldShowId());
}
/**
* Returns the preferred width of the tab toolbar if present, otherwise 0.
*/
protected int getTabToolbarPreferredWidth() {
ActionToolbar tabToolbar = ui.getTabToolbar();
return tabToolbar == null ? 0 : tabToolbar.getComponent().getPreferredSize().width;
}
private String getTitleSuffix() {
ContentManager manager = ui.getContentManager();
return switch (manager.getContentCount()) {
@@ -5,6 +5,7 @@ import com.intellij.ide.ActivityTracker;
import com.intellij.ide.dnd.DnDSupport;
import com.intellij.ide.dnd.DnDTarget;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
@@ -147,6 +148,7 @@ class TabContentLayout extends ContentLayout implements MorePopupAware {
ContentManager manager = ui.getContentManager();
LayoutData data = new LayoutData(ui);
data.toolbarWidth = getTabToolbarPreferredWidth();
data.eachX = getTabLayoutStart();
data.eachY = 0;
@@ -159,102 +161,112 @@ class TabContentLayout extends ContentLayout implements MorePopupAware {
}
int tabsStart = data.eachX;
if (manager.getContentCount() == 0) return;
boolean toolbarUpdateNeeded = false;
if (manager.getContentCount() != 0) {
Content selected = manager.getSelectedContent();
if (selected == null) {
selected = manager.getContents()[0];
}
Content selected = manager.getSelectedContent();
if (selected == null) {
selected = manager.getContents()[0];
}
if (lastLayout != null &&
(idLabel == null || idLabel.isValid()) &&
lastLayout.layoutSize.equals(bounds.getSize()) &&
lastLayout.contentCount == manager.getContentCount() &&
ContainerUtil.all(tabs, Component::isValid)) {
for (ContentTabLabel each : tabs) {
if (each.getContent() == selected && each.getBounds().width != 0) {
return; // keep last layout
if (lastLayout != null &&
(idLabel == null || idLabel.isValid()) &&
lastLayout.layoutSize.equals(bounds.getSize()) &&
lastLayout.contentCount == manager.getContentCount() &&
lastLayout.toolbarWidth == data.toolbarWidth &&
ContainerUtil.all(tabs, Component::isValid)) {
for (ContentTabLabel each : tabs) {
if (each.getContent() == selected && each.getBounds().width != 0) {
return; // keep last layout
}
}
}
}
ArrayList<JLabel> toLayout = new ArrayList<>();
Collection<JLabel> toDrop = new HashSet<>();
ArrayList<JLabel> toLayout = new ArrayList<>();
Collection<JLabel> toDrop = new HashSet<>();
for (JLabel eachTab : tabs) {
final Dimension eachSize = eachTab.getPreferredSize();
data.requiredWidth += eachSize.width;
toLayout.add(eachTab);
}
if (ui.dropOverIndex != -1 && !isSingleContentView) {
data.requiredWidth += ui.dropOverWidth;
int index = Math.min(toLayout.size(), Math.max(0, ui.dropOverIndex - 1));
toLayout.add(index, dropOverPlaceholder);
}
data.toFitWidth = bounds.getSize().width - data.eachX;
final ContentTabLabel selectedTab = contentToTabs.get(selected);
while (true) {
if (data.requiredWidth <= data.toFitWidth) break;
if (toLayout.size() <= 1) break;
JLabel firstLabel = toLayout.get(0);
JLabel lastLabel = toLayout.get(toLayout.size() - 1);
JLabel labelToDrop;
if (firstLabel != selectedTab && firstLabel != dropOverPlaceholder) {
labelToDrop = firstLabel;
for (JLabel eachTab : tabs) {
final Dimension eachSize = eachTab.getPreferredSize();
data.requiredWidth += eachSize.width;
toLayout.add(eachTab);
}
else if (lastLabel != selectedTab && lastLabel != dropOverPlaceholder) {
labelToDrop = lastLabel;
}
else {
break;
}
data.requiredWidth -= (labelToDrop.getPreferredSize().width + 1);
toDrop.add(labelToDrop);
toLayout.remove(labelToDrop);
}
boolean reachedBounds = false;
TabsDrawMode toDrawTabs = isToDrawTabs();
for (JLabel each : toLayout) {
if (toDrawTabs == TabsDrawMode.HIDE) {
each.setBounds(0, 0, 0, 0);
continue;
if (ui.dropOverIndex != -1 && !isSingleContentView) {
data.requiredWidth += ui.dropOverWidth;
int index = Math.min(toLayout.size(), Math.max(0, ui.dropOverIndex - 1));
toLayout.add(index, dropOverPlaceholder);
}
data.eachY = 0;
final Dimension eachSize = each.getPreferredSize();
if (data.eachX + eachSize.width < data.toFitWidth + tabsStart) {
each.setBounds(data.eachX, data.eachY, eachSize.width, bounds.height - data.eachY);
data.eachX += eachSize.width;
}
else {
if (!reachedBounds) {
final int width = bounds.width - data.eachX;
each.setBounds(data.eachX, data.eachY, width, bounds.height - data.eachY);
data.eachX += width;
data.toFitWidth = bounds.getSize().width - data.toolbarWidth - data.eachX;
final ContentTabLabel selectedTab = contentToTabs.get(selected);
while (true) {
if (data.requiredWidth <= data.toFitWidth) break;
if (toLayout.size() <= 1) break;
JLabel firstLabel = toLayout.get(0);
JLabel lastLabel = toLayout.get(toLayout.size() - 1);
JLabel labelToDrop;
if (firstLabel != selectedTab && firstLabel != dropOverPlaceholder) {
labelToDrop = firstLabel;
}
else if (lastLabel != selectedTab && lastLabel != dropOverPlaceholder) {
labelToDrop = lastLabel;
}
else {
each.setBounds(0, 0, 0, 0);
break;
}
reachedBounds = true;
data.requiredWidth -= (labelToDrop.getPreferredSize().width + 1);
toDrop.add(labelToDrop);
toLayout.remove(labelToDrop);
}
boolean reachedBounds = false;
TabsDrawMode toDrawTabs = isToDrawTabs();
for (JLabel each : toLayout) {
if (toDrawTabs == TabsDrawMode.HIDE) {
each.setBounds(0, 0, 0, 0);
continue;
}
data.eachY = 0;
final Dimension eachSize = each.getPreferredSize();
if (data.eachX + eachSize.width < data.toFitWidth + tabsStart) {
each.setBounds(data.eachX, data.eachY, eachSize.width, bounds.height - data.eachY);
data.eachX += eachSize.width;
}
else {
if (!reachedBounds) {
final int width = bounds.width - data.eachX - data.toolbarWidth;
each.setBounds(data.eachX, data.eachY, width, bounds.height - data.eachY);
data.eachX += width;
}
else {
each.setBounds(0, 0, 0, 0);
}
reachedBounds = true;
}
}
for (JLabel each : toDrop) {
each.setBounds(0, 0, 0, 0);
}
if (toDrop.isEmpty()) {
toolbarUpdateNeeded = lastLayout != null && lastLayout.morePopupOffset != null;
data.morePopupOffset = null;
}
else {
toolbarUpdateNeeded = lastLayout != null && lastLayout.morePopupOffset == null;
data.morePopupOffset = new Point(data.eachX + data.toolbarWidth + MORE_ICON_BORDER, bounds.height);
}
}
for (JLabel each : toDrop) {
each.setBounds(0, 0, 0, 0);
}
boolean toolbarUpdateNeeded;
if (!toDrop.isEmpty()) {
toolbarUpdateNeeded = lastLayout != null && lastLayout.morePopupOffset == null;
data.morePopupOffset = new Point(data.eachX + MORE_ICON_BORDER, bounds.height);
}
else {
toolbarUpdateNeeded = lastLayout != null && lastLayout.morePopupOffset != null;
data.morePopupOffset = null;
// Tab toolbar is positioned at the end.
ActionToolbar tabToolbar = ui.getTabToolbar();
if (tabToolbar != null) {
JComponent component = tabToolbar.getComponent();
Dimension size = component.getPreferredSize();
component.setBounds(data.eachX, data.eachY + (bounds.height - size.height) / 2, size.width, size.height);
data.eachX += component.getWidth();
}
lastLayout = data;
@@ -289,6 +301,9 @@ class TabContentLayout extends ContentLayout implements MorePopupAware {
}
}
}
result += getTabToolbarPreferredWidth();
return result;
}
@@ -327,6 +342,7 @@ class TabContentLayout extends ContentLayout implements MorePopupAware {
public int eachX;
public int eachY;
public int contentCount;
public int toolbarWidth;
LayoutData(ToolWindowContentUi ui) {
layoutSize = ui.getTabComponent().getSize();
@@ -374,20 +390,24 @@ class TabContentLayout extends ContentLayout implements MorePopupAware {
@Override
public void rebuild() {
ui.getTabComponent().removeAll();
JPanel tabComponent = ui.getTabComponent();
ui.getTabComponent().add(idLabel);
tabComponent.removeAll();
tabComponent.add(idLabel);
ToolWindowContentUi.initMouseListeners(idLabel, ui, true);
for (ContentTabLabel each : tabs) {
ui.getTabComponent().add(each);
tabComponent.add(each);
ToolWindowContentUi.initMouseListeners(each, ui, false);
}
if ((!isSingleContentView || !Registry.is("debugger.new.tool.window.layout.dnd", false))
&& ui.dropOverIndex >= 0 && !tabs.isEmpty()) {
int index = Math.min(ui.dropOverIndex, ui.getTabComponent().getComponentCount());
ui.getTabComponent().add(dropOverPlaceholder, index);
int index = Math.min(ui.dropOverIndex, tabComponent.getComponentCount());
tabComponent.add(dropOverPlaceholder, index);
}
ui.connectTabToolbar();
}
@Override
@@ -9,6 +9,8 @@ import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
import com.intellij.openapi.actionSystem.toolbarLayout.ToolbarLayoutStrategy;
import com.intellij.openapi.options.advanced.AdvancedSettings;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.ui.Splitter;
@@ -24,8 +26,10 @@ import com.intellij.openapi.wm.impl.ToolWindowImpl;
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl;
import com.intellij.toolWindow.InternalDecoratorImpl;
import com.intellij.toolWindow.ToolWindowEventSource;
import com.intellij.toolWindow.ToolWindowHeader;
import com.intellij.toolWindow.ToolWindowPane;
import com.intellij.ui.ClientProperty;
import com.intellij.ui.ComponentUtil;
import com.intellij.ui.ExperimentalUI;
import com.intellij.ui.MouseDragHelper;
import com.intellij.ui.PopupHandler;
@@ -33,8 +37,6 @@ import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.content.*;
import com.intellij.ui.content.tabs.PinToolwindowTabAction;
import com.intellij.ui.content.tabs.TabbedContentAction;
import com.intellij.ui.layout.migLayout.MigLayoutUtilKt;
import com.intellij.ui.layout.migLayout.patched.MigLayout;
import com.intellij.ui.tabs.impl.MorePopupAware;
import com.intellij.util.Alarm;
import com.intellij.util.ContentUtilEx;
@@ -96,10 +98,8 @@ public final class ToolWindowContentUi implements ContentUI, DataProvider {
public Predicate<Point> isResizableArea = __ -> true;
private final JPanel tabComponent = new TabPanel();
public @NotNull JPanel getTabComponent() {
return tabComponent;
}
private final DefaultActionGroup tabActionGroup = new DefaultActionGroup();
private ActionToolbar tabToolbar = null;
public ToolWindowContentUi(@NotNull ToolWindowImpl window,
@NotNull ContentManager contentManager,
@@ -231,6 +231,36 @@ public final class ToolWindowContentUi implements ContentUI, DataProvider {
return window;
}
public @NotNull JPanel getTabComponent() {
return tabComponent;
}
public @NotNull DefaultActionGroup getTabToolbarActions() {
return tabActionGroup;
}
public @Nullable ActionToolbar getTabToolbar() {
return tabToolbar;
}
/**
* Adds tab toolbar to the tab panel.
*/
public void connectTabToolbar() {
if (tabToolbar != null) {
tabComponent.add(tabToolbar.getComponent());
}
}
/**
* Removes tab toolbar from the tab panel.
*/
public void disconnectTabToolbar() {
if (tabToolbar != null) {
tabComponent.remove(tabToolbar.getComponent());
}
}
private boolean isResizeable() {
if (window.getType() == ToolWindowType.FLOATING || window.getType() == ToolWindowType.WINDOWED) {
return false;
@@ -697,6 +727,28 @@ public final class ToolWindowContentUi implements ContentUI, DataProvider {
return null;
}
public void setTabActions(@NotNull List<AnAction> actions) {
if (tabToolbar == null) {
tabToolbar =
ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLWINDOW_TITLE, new DefaultActionGroup(tabActionGroup), true);
ActionToolbarImpl tabToolbarImpl = (ActionToolbarImpl)tabToolbar;
ToolWindowHeader header = ComponentUtil.getParentOfType(ToolWindowHeader.class, tabComponent);
tabToolbarImpl.setTargetComponent(header);
tabToolbarImpl.setForceMinimumSize(true);
tabToolbarImpl.setLayoutStrategy(ToolbarLayoutStrategy.NOWRAP_STRATEGY);
tabToolbarImpl.setReservePlaceAutoPopupIcon(false);
tabToolbarImpl.setOpaque(false);
tabToolbarImpl.setBorder(JBUI.Borders.empty());
if (tabComponent.isShowing()) {
tabComponent.add(tabToolbarImpl);
}
}
tabActionGroup.removeAll();
tabActionGroup.addSeparator();
tabActionGroup.addAll(actions);
tabToolbar.updateActionsImmediately();
}
private @NotNull CloseAction.CloseTarget computeCloseTarget() {
if (contentManager.canCloseContents()) {
Content selected = contentManager.getSelectedContent();
@@ -763,7 +815,6 @@ public final class ToolWindowContentUi implements ContentUI, DataProvider {
public final class TabPanel extends NonOpaquePanel implements UISettingsListener {
private TabPanel() {
super(new MigLayout(MigLayoutUtilKt.createLayoutConstraints(0, 0).noVisualPadding().fillY()));
setBorder(JBUI.Borders.emptyRight(2));
if (ExperimentalUI.isNewUI()) {
setBorder(JBUI.Borders.empty());
@@ -800,7 +851,7 @@ public final class ToolWindowContentUi implements ContentUI, DataProvider {
size.width = TabContentLayout.getTabLayoutStart() + getInsets().left + getInsets().right;
for (int i = 0; i < getComponentCount(); i++) {
final Component each = getComponent(i);
if (each.isVisible()) {
if (each.isVisible() || each == tabToolbar.getComponent()) {
size.height = Math.max(each.getPreferredSize().height, size.height);
size.width += each.getPreferredSize().width;
}
@@ -440,8 +440,6 @@ class InternalDecoratorImpl internal constructor(
val headerToolbarActions: ActionGroup
get() = header.getToolbarActions()
val headerToolbarWestActions: ActionGroup
get() = header.getToolbarWestActions()
override fun toString(): String {
return toolWindow.id + ": " + StringUtil.trimMiddle(contentManager.contents.joinToString { it.displayName ?: "null" }, 40) +
@@ -528,7 +526,7 @@ class InternalDecoratorImpl internal constructor(
fun setTabActions(actions: List<AnAction>) {
tabActions = actions
header.setTabActions(actions)
contentUi.setTabActions(actions)
firstDecorator?.setTabActions(actions)
secondDecorator?.setTabActions(actions)
}
@@ -786,9 +784,9 @@ class InternalDecoratorImpl internal constructor(
toolbar.alphaContext.isVisible = isVisible
}
val toolbarWest = header.getToolbarWest()
if (toolbarWest != null && toolbarWest is AlphaAnimated) {
toolbarWest.alphaContext.isVisible = isVisible
val tabToolbar = contentUi.tabToolbar
if (tabToolbar != null && tabToolbar is AlphaAnimated) {
tabToolbar.alphaContext.isVisible = isVisible
}
}
@@ -17,7 +17,6 @@ import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.SimpleToolWindowPanel
import com.intellij.openapi.wm.ToolWindowAnchor
import com.intellij.openapi.wm.ToolWindowContentUiType
import com.intellij.openapi.wm.ToolWindowType
import com.intellij.openapi.wm.impl.DockToolWindowAction
import com.intellij.openapi.wm.impl.ToolWindowImpl
import com.intellij.openapi.wm.impl.content.SingleContentLayout
@@ -57,9 +56,7 @@ abstract class ToolWindowHeader internal constructor(
) : BorderLayoutPanel(), DataProvider, PropertyChangeListener {
private val actionGroup = DefaultActionGroup()
private val actionGroupWest = DefaultActionGroup()
private val toolbar: ActionToolbar
private var toolbarWest: ActionToolbar? = null
private val westPanel: JPanel
private val popupMenuListener = object : PopupMenuListener {
override fun popupMenuWillBecomeVisible(event: PopupMenuEvent) = setPopupShowing(true)
@@ -242,13 +239,12 @@ abstract class ToolWindowHeader internal constructor(
private fun manageWestPanelTabComponentAndToolbar(init: Boolean) {
if (!init) { // remove to avoid extra events, toolbars update on addNotify!
westPanel.remove(contentUi.tabComponent)
toolbarWest?.apply { westPanel.remove(component) }
contentUi.disconnectTabToolbar()
return
}
// Makes sure toolbar stays after the tab component
val allowDnd = ClientProperty.isTrue(toolWindow.component as Component?, ToolWindowContentUi.ALLOW_DND_FOR_TABS)
westPanel.add(contentUi.tabComponent, if (allowDnd) CC().grow() else CC().growY())
toolbarWest?.apply { westPanel.add(component, CC().pushX()) }
contentUi.connectTabToolbar()
}
override fun propertyChange(evt: PropertyChangeEvent?) {
@@ -269,12 +265,8 @@ abstract class ToolWindowHeader internal constructor(
fun getToolbar(): ActionToolbar = toolbar
fun getToolbarWest(): ActionToolbar? = toolbarWest
fun getToolbarActions(): DefaultActionGroup = actionGroup
fun getToolbarWestActions(): DefaultActionGroup = actionGroupWest
override fun getData(dataId: String): Any? {
if (MorePopupAware.KEY.`is`(dataId)) {
return contentUi.getData(dataId)
@@ -284,28 +276,6 @@ abstract class ToolWindowHeader internal constructor(
}
}
fun setTabActions(actions: List<AnAction>) {
if (toolbarWest == null) {
toolbarWest = ActionManager.getInstance().createActionToolbar(
ActionPlaces.TOOLWINDOW_TITLE, DefaultActionGroup(actionGroupWest), true)
with(toolbarWest as ActionToolbarImpl) {
targetComponent = this
setForceMinimumSize(true)
layoutStrategy = ToolbarLayoutStrategy.NOWRAP_STRATEGY
setReservePlaceAutoPopupIcon(false)
isOpaque = false
border = JBUI.Borders.empty()
if (westPanel.isShowing) {
westPanel.add(this, CC().pushX())
}
}
}
actionGroupWest.removeAll()
actionGroupWest.addSeparator()
actionGroupWest.addAll(actions)
toolbarWest?.updateActionsImmediately()
}
fun setAdditionalTitleActions(actions: List<AnAction>) {
actionGroup.removeAll()
actionGroup.addAll(actions)