mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
initial implementation of scrollable single-row layout for editor tabs (idea.is.internal for now)
This commit is contained in:
@@ -17,11 +17,14 @@ package com.intellij.ui.tabs.impl;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
import com.intellij.ui.tabs.JBTabsPosition;
|
||||
import com.intellij.ui.tabs.TabInfo;
|
||||
import com.intellij.ui.tabs.TabsUtil;
|
||||
import com.intellij.ui.tabs.impl.singleRow.ScrollableSingleRowLayout;
|
||||
import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout;
|
||||
import com.intellij.ui.tabs.impl.table.TableLayout;
|
||||
import com.intellij.util.ui.SameColor;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
@@ -40,6 +43,14 @@ public class JBEditorTabs extends JBTabsImpl {
|
||||
super(project, actionManager, focusManager, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SingleRowLayout createSingleRowLayout() {
|
||||
if (ApplicationManager.getApplication().isInternal()) {
|
||||
return new ScrollableSingleRowLayout(this);
|
||||
}
|
||||
return super.createSingleRowLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditorTabs() {
|
||||
return true;
|
||||
|
||||
@@ -103,11 +103,11 @@ public class JBTabsImpl extends JComponent
|
||||
|
||||
private final WeakHashMap<Component, Component> myDeferredToRemove = new WeakHashMap<Component, Component>();
|
||||
|
||||
private final SingleRowLayout mySingleRowLayout = new SingleRowLayout(this);
|
||||
private final SingleRowLayout mySingleRowLayout;
|
||||
private final TableLayout myTableLayout = new TableLayout(this);
|
||||
|
||||
|
||||
private TabLayout myLayout = mySingleRowLayout;
|
||||
private TabLayout myLayout;
|
||||
private LayoutPassInfo myLastLayoutPass;
|
||||
private TabInfo myLastPaintedSelection;
|
||||
|
||||
@@ -206,6 +206,9 @@ public class JBTabsImpl extends JComponent
|
||||
|
||||
setUiDecorator(null);
|
||||
|
||||
mySingleRowLayout = createSingleRowLayout();
|
||||
myLayout = mySingleRowLayout;
|
||||
|
||||
myPopupListener = new PopupMenuListener() {
|
||||
public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
|
||||
}
|
||||
@@ -228,6 +231,15 @@ public class JBTabsImpl extends JComponent
|
||||
}
|
||||
}
|
||||
});
|
||||
addMouseWheelListener(new MouseWheelListener() {
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
if (mySingleRowLayout.myLastSingRowLayout != null) {
|
||||
mySingleRowLayout.scroll(e.getUnitsToScroll() * 10);
|
||||
revalidateAndRepaint(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myAnimator = new Animator("JBTabs Attractions", 2, 500, true) {
|
||||
public void paintNow(final int frame, final int totalFrames, final int cycle) {
|
||||
@@ -284,6 +296,10 @@ public class JBTabsImpl extends JComponent
|
||||
};
|
||||
}
|
||||
|
||||
protected SingleRowLayout createSingleRowLayout() {
|
||||
return new SingleRowLayout(this);
|
||||
}
|
||||
|
||||
|
||||
public JBTabs setNavigationActionBinding(String prevActionId, String nextActionId) {
|
||||
if (myNextAction != null) {
|
||||
@@ -2491,8 +2507,6 @@ public class JBTabsImpl extends JComponent
|
||||
}
|
||||
|
||||
private void updateContainer(boolean forced, final boolean layoutNow) {
|
||||
final TabLabel selectedLabel = getSelectedLabel();
|
||||
|
||||
for (TabInfo each : myVisibleInfos) {
|
||||
final JComponent eachComponent = each.getComponent();
|
||||
if (getSelectedInfo() == each && getSelectedInfo() != null) {
|
||||
@@ -2518,6 +2532,7 @@ public class JBTabsImpl extends JComponent
|
||||
}
|
||||
}
|
||||
|
||||
mySingleRowLayout.scrollSelectionInView(myVisibleInfos);
|
||||
relayout(forced, layoutNow);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -250,6 +250,10 @@ public class TabLabel extends JPanel {
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
final Dimension size = super.getPreferredSize();
|
||||
if (myActionPanel != null && !myActionPanel.isVisible()) {
|
||||
final Dimension actionPanelSize = myActionPanel.getPreferredSize();
|
||||
size.width += actionPanelSize.width;
|
||||
}
|
||||
|
||||
final JBTabsPosition pos = myTabs.getTabsPosition();
|
||||
switch (pos) {
|
||||
@@ -526,6 +530,12 @@ public class TabLabel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public void setActionPanelVisible(boolean visible) {
|
||||
if (myActionPanel != null) {
|
||||
myActionPanel.setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myInfo.getText();
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.ui.tabs.impl.singleRow;
|
||||
|
||||
import com.intellij.ui.tabs.TabInfo;
|
||||
import com.intellij.ui.tabs.impl.JBTabsImpl;
|
||||
import com.intellij.ui.tabs.impl.TabLabel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class ScrollableSingleRowLayout extends SingleRowLayout {
|
||||
private int myScrollOffset = 0;
|
||||
private boolean myScrollSelectionInViewPending = false;
|
||||
|
||||
public ScrollableSingleRowLayout(final JBTabsImpl tabs) {
|
||||
super(tabs);
|
||||
}
|
||||
|
||||
@Override
|
||||
int getScrollOffset() {
|
||||
return myScrollOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scroll(int units) {
|
||||
myScrollOffset += units;
|
||||
if (myLastSingRowLayout != null) {
|
||||
clampScrollOffsetToBounds(myLastSingRowLayout);
|
||||
}
|
||||
}
|
||||
|
||||
private void clampScrollOffsetToBounds(SingleRowPassInfo data) {
|
||||
if (data.requiredLength < data.toFitLength) {
|
||||
myScrollOffset = 0;
|
||||
}
|
||||
else {
|
||||
myScrollOffset = Math.max(0, Math.min(myScrollOffset, data.requiredLength - data.toFitLength + getStrategy().getMoreRectAxisSize()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollSelectionInView(List<TabInfo> visibleInfos) {
|
||||
myScrollSelectionInViewPending = true;
|
||||
}
|
||||
|
||||
private void doScrollSelectionInView(SingleRowPassInfo passInfo) {
|
||||
int offset = -myScrollOffset;
|
||||
for (TabInfo info : passInfo.myVisibleInfos) {
|
||||
final int length = getRequiredLength(info);
|
||||
if (info == myTabs.getSelectedInfo()) {
|
||||
if (offset < 0) {
|
||||
scroll(offset);
|
||||
}
|
||||
else if (offset + length > passInfo.toFitLength - getStrategy().getMoreRectAxisSize()) {
|
||||
scroll(offset + length - passInfo.toFitLength + getStrategy().getMoreRectAxisSize());
|
||||
}
|
||||
break;
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recomputeToLayout(SingleRowPassInfo data) {
|
||||
calculateRequiredLength(data);
|
||||
clampScrollOffsetToBounds(data);
|
||||
if (myScrollSelectionInViewPending || !data.layoutSize.equals(myLastSingRowLayout.layoutSize)) {
|
||||
myScrollSelectionInViewPending = false;
|
||||
doScrollSelectionInView(data);
|
||||
}
|
||||
}
|
||||
|
||||
protected void layoutMoreButton(SingleRowPassInfo data) {
|
||||
if (data.requiredLength > data.toFitLength) {
|
||||
data.moreRect = getStrategy().getMoreRect(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateMoreIconVisibility(SingleRowPassInfo data) {
|
||||
myMoreIcon.setPainted(data.requiredLength > data.toFitLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean applyTabLayout(SingleRowPassInfo data, TabLabel label, int length, int deltaToFit) {
|
||||
if (data.requiredLength > data.toFitLength) {
|
||||
length = getStrategy().getLengthIncrement(label.getPreferredSize());
|
||||
final int moreRectSize = getStrategy().getMoreRectAxisSize();
|
||||
if (data.position + length > data.toFitLength - moreRectSize) {
|
||||
super.applyTabLayout(data, label, data.toFitLength - data.position - moreRectSize - 4, deltaToFit);
|
||||
label.setAlignmentToCenter(false);
|
||||
label.setActionPanelVisible(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
label.setActionPanelVisible(true);
|
||||
return super.applyTabLayout(data, label, length, deltaToFit);
|
||||
}
|
||||
}
|
||||
+74
-28
@@ -63,7 +63,6 @@ public class SingleRowLayout extends TabLayout {
|
||||
|
||||
private RowDropPolicy myRowDropPolicy = RowDropPolicy.first;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSideComponentOnTabs() {
|
||||
return getStrategy().isSideComponentOnTabs();
|
||||
@@ -108,7 +107,8 @@ public class SingleRowLayout extends TabLayout {
|
||||
if (!myTabs.myForcedRelayout &&
|
||||
myLastSingRowLayout != null &&
|
||||
myLastSingRowLayout.contentCount == myTabs.getTabCount() &&
|
||||
myLastSingRowLayout.layoutSize.equals(myTabs.getSize())) {
|
||||
myLastSingRowLayout.layoutSize.equals(myTabs.getSize()) &&
|
||||
myLastSingRowLayout.scrollOffset == getScrollOffset()) {
|
||||
for (TabInfo each : data.myVisibleInfos) {
|
||||
final TabLabel eachLabel = myTabs.myInfo2Label.get(each);
|
||||
if (!eachLabel.isValid()) {
|
||||
@@ -125,10 +125,18 @@ public class SingleRowLayout extends TabLayout {
|
||||
return layoutLabels;
|
||||
}
|
||||
|
||||
int getScrollOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void scroll(int units) {
|
||||
}
|
||||
|
||||
public void scrollSelectionInView(List<TabInfo> visibleInfos) {
|
||||
}
|
||||
|
||||
public LayoutPassInfo layoutSingleRow(List<TabInfo> visibleInfos) {
|
||||
SingleRowPassInfo data = new SingleRowPassInfo(this, visibleInfos);
|
||||
final TabInfo selected = myTabs.getSelectedInfo();
|
||||
final JBTabsImpl.Toolbar selectedToolbar = myTabs.myInfo2Toolbar.get(selected);
|
||||
|
||||
final boolean layoutLabels = checkLayoutLabels(data);
|
||||
if (!layoutLabels) {
|
||||
@@ -136,24 +144,20 @@ public class SingleRowLayout extends TabLayout {
|
||||
}
|
||||
|
||||
|
||||
data.insets = myTabs.getLayoutInsets();
|
||||
|
||||
data.hToolbar = selectedToolbar != null && myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
|
||||
data.vToolbar = selectedToolbar != null && !myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
|
||||
final TabInfo selected = myTabs.getSelectedInfo();
|
||||
prepareLayoutPassInfo(data, selected);
|
||||
|
||||
myTabs.resetLayout(layoutLabels || myTabs.isHideTabs());
|
||||
|
||||
|
||||
if (layoutLabels && !myTabs.isHideTabs()) {
|
||||
data.position = getStrategy().getStartPosition(data);
|
||||
data.position = getStrategy().getStartPosition(data) - getScrollOffset();
|
||||
|
||||
recomputeToLayout(data);
|
||||
|
||||
layoutLabelsAndGhosts(data);
|
||||
|
||||
if (data.toDrop.size() > 0) {
|
||||
data.moreRect = getStrategy().getMoreRect(data);
|
||||
}
|
||||
layoutMoreButton(data);
|
||||
}
|
||||
|
||||
if (selected != null) {
|
||||
@@ -161,7 +165,7 @@ public class SingleRowLayout extends TabLayout {
|
||||
getStrategy().layoutComp(data);
|
||||
}
|
||||
|
||||
myMoreIcon.setPainted(data.toLayout.size() > 0 && data.myVisibleInfos.size() > data.toLayout.size());
|
||||
updateMoreIconVisibility(data);
|
||||
|
||||
data.tabRectangle = new Rectangle();
|
||||
|
||||
@@ -180,6 +184,29 @@ public class SingleRowLayout extends TabLayout {
|
||||
return data;
|
||||
}
|
||||
|
||||
protected void prepareLayoutPassInfo(SingleRowPassInfo data, TabInfo selected) {
|
||||
data.insets = myTabs.getLayoutInsets();
|
||||
|
||||
final JBTabsImpl.Toolbar selectedToolbar = myTabs.myInfo2Toolbar.get(selected);
|
||||
data.hToolbar = selectedToolbar != null && myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
|
||||
data.vToolbar = selectedToolbar != null && !myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
|
||||
data.toFitLength = getStrategy().getToFitLength(data);
|
||||
|
||||
if (myTabs.isGhostsAlwaysVisible()) {
|
||||
data.toFitLength -= myTabs.getGhostTabLength() * 2 + (myTabs.getInterTabSpaceLength() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateMoreIconVisibility(SingleRowPassInfo data) {
|
||||
myMoreIcon.setPainted(data.toLayout.size() > 0 && data.myVisibleInfos.size() > data.toLayout.size());
|
||||
}
|
||||
|
||||
protected void layoutMoreButton(SingleRowPassInfo data) {
|
||||
if (data.toDrop.size() > 0) {
|
||||
data.moreRect = getStrategy().getMoreRect(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void layoutLabelsAndGhosts(final SingleRowPassInfo data) {
|
||||
if (data.firstGhostVisible || myTabs.isGhostsAlwaysVisible()) {
|
||||
data.firstGhost = getStrategy().getLayoutRect(data, data.position, myTabs.getGhostTabLength());
|
||||
@@ -196,8 +223,17 @@ public class SingleRowLayout extends TabLayout {
|
||||
|
||||
int totalLength = 0;
|
||||
int positionStart = data.position;
|
||||
boolean layoutStopped = false;
|
||||
for (TabInfo eachInfo : data.toLayout) {
|
||||
final TabLabel label = myTabs.myInfo2Label.get(eachInfo);
|
||||
if (layoutStopped) {
|
||||
label.setActionPanelVisible(false);
|
||||
final Rectangle rec = getStrategy().getLayoutRect(data, 0, 0);
|
||||
myTabs.layout(label, rec);
|
||||
continue;
|
||||
}
|
||||
|
||||
label.setActionPanelVisible(true);
|
||||
final Dimension eachSize = label.getPreferredSize();
|
||||
|
||||
boolean isLast = data.toLayout.indexOf(eachInfo) == data.toLayout.size() - 1;
|
||||
@@ -209,15 +245,15 @@ public class SingleRowLayout extends TabLayout {
|
||||
else {
|
||||
length = data.toFitLength - totalLength;
|
||||
}
|
||||
final Rectangle rec = getStrategy().getLayoutRect(data, data.position, length);
|
||||
myTabs.layout(label, rec);
|
||||
|
||||
label.setAlignmentToCenter((deltaToFit > 0 || myTabs.isEditorTabs()) && getStrategy().isToCenterTextWhenStretched());
|
||||
boolean continueLayout = applyTabLayout(data, label, length, deltaToFit);
|
||||
|
||||
data.position = getStrategy().getMaxPosition(label.getBounds());
|
||||
data.position += myTabs.getInterTabSpaceLength();
|
||||
|
||||
totalLength = getStrategy().getMaxPosition(label.getBounds()) - positionStart + myTabs.getInterTabSpaceLength();
|
||||
if (!continueLayout) {
|
||||
layoutStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (TabInfo eachInfo : data.toDrop) {
|
||||
@@ -230,19 +266,17 @@ public class SingleRowLayout extends TabLayout {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean applyTabLayout(SingleRowPassInfo data, TabLabel label, int length, int deltaToFit) {
|
||||
final Rectangle rec = getStrategy().getLayoutRect(data, data.position, length);
|
||||
myTabs.layout(label, rec);
|
||||
|
||||
private void recomputeToLayout(final SingleRowPassInfo data) {
|
||||
data.toFitLength = getStrategy().getToFitLength(data);
|
||||
label.setAlignmentToCenter((deltaToFit > 0 || myTabs.isEditorTabs()) && getStrategy().isToCenterTextWhenStretched());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (myTabs.isGhostsAlwaysVisible()) {
|
||||
data.toFitLength -= myTabs.getGhostTabLength() * 2 + (myTabs.getInterTabSpaceLength() * 2);
|
||||
}
|
||||
|
||||
for (TabInfo eachInfo : data.myVisibleInfos) {
|
||||
data.requiredLength += getStrategy().getLengthIncrement(myTabs.myInfo2Label.get(eachInfo).getPreferredSize())
|
||||
+ (myTabs.isEditorTabs() ? myTabs.getInterTabSpaceLength() : 0);
|
||||
data.toLayout.add(eachInfo);
|
||||
}
|
||||
|
||||
protected void recomputeToLayout(final SingleRowPassInfo data) {
|
||||
calculateRequiredLength(data);
|
||||
|
||||
while (true) {
|
||||
if (data.requiredLength <= data.toFitLength - data.position) break;
|
||||
@@ -292,6 +326,18 @@ public class SingleRowLayout extends TabLayout {
|
||||
|
||||
}
|
||||
|
||||
protected void calculateRequiredLength(SingleRowPassInfo data) {
|
||||
for (TabInfo eachInfo : data.myVisibleInfos) {
|
||||
data.requiredLength += getRequiredLength(eachInfo);
|
||||
data.toLayout.add(eachInfo);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getRequiredLength(TabInfo eachInfo) {
|
||||
return getStrategy().getLengthIncrement(myTabs.myInfo2Label.get(eachInfo).getPreferredSize())
|
||||
+ (myTabs.isEditorTabs() ? myTabs.getInterTabSpaceLength() : 0);
|
||||
}
|
||||
|
||||
|
||||
public class GhostComponent extends JLabel {
|
||||
private TabInfo myInfo;
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,6 +51,7 @@ public class SingleRowPassInfo extends LayoutPassInfo {
|
||||
private final JBTabsImpl myTabs;
|
||||
public JComponent comp;
|
||||
public Rectangle tabRectangle;
|
||||
int scrollOffset;
|
||||
|
||||
|
||||
public SingleRowPassInfo(SingleRowLayout layout, List<TabInfo> visibleInfos) {
|
||||
@@ -61,6 +62,7 @@ public class SingleRowPassInfo extends LayoutPassInfo {
|
||||
toLayout = new ArrayList<TabInfo>();
|
||||
toDrop = new ArrayList<TabInfo>();
|
||||
moreRectAxisSize = layout.getStrategy().getMoreRectAxisSize();
|
||||
scrollOffset = layout.getScrollOffset();
|
||||
}
|
||||
|
||||
public TabInfo getPreviousFor(final TabInfo info) {
|
||||
|
||||
Reference in New Issue
Block a user