introduce EditorGutterLayout for managing component on the gutter

GitOrigin-RevId: 84415e7cab2e9084ef33c917e2c96d7fedc50c37
This commit is contained in:
Konstantin Bulenkov
2022-01-12 17:18:09 +00:00
committed by intellij-monorepo-bot
parent ffe9ad7a2e
commit e823f72e10
4 changed files with 264 additions and 60 deletions
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.diff;
import com.intellij.openapi.editor.Editor;
@@ -16,6 +16,7 @@ import com.intellij.ui.paint.LinePainter2D;
import com.intellij.ui.paint.RectanglePainter2D;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.util.IntPair;
import com.intellij.util.ui.JBUI;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -190,7 +191,7 @@ public class LineStatusMarkerDrawUtil {
EditorGutterComponentEx gutter = ((EditorEx)editor).getGutterComponentEx();
int x = gutter.getLineMarkerFreePaintersAreaOffset() + 1; // leave 1px for brace highlighters
if (ExperimentalUI.isNewUI()) {
return new IntPair(x, x + (int)(JBUIScale.scale(5 * getEditorScale(editor))));
return new IntPair(x, x + (int)(JBUIScale.scale(JBUI.getInt("Gutter.VcsChanges.width", 3) * getEditorScale(editor))));
}
int endX = gutter.getWhitespaceSeparatorOffset();
return new IntPair(x, endX);
@@ -198,7 +199,7 @@ public class LineStatusMarkerDrawUtil {
public static boolean isInsideMarkerArea(@NotNull MouseEvent e) {
final EditorGutterComponentEx gutter = (EditorGutterComponentEx)e.getComponent();
return e.getX() > gutter.getLineMarkerFreePaintersAreaOffset();
return gutter.isInsideMarkerArea(e);
}
public static void paintRect(@NotNull Graphics2D g, @Nullable Color color, @Nullable Color borderColor,
@@ -207,7 +208,7 @@ public class LineStatusMarkerDrawUtil {
if (color != null) {
g.setColor(color);
double width = x2 - x1;
RectanglePainter2D.FILL.paint(g, x1, y1, width, y2 - y1, width);
RectanglePainter2D.FILL.paint(g, x1, y1 + 1, width, y2 - y1 - 2, width);
}
return;
}
@@ -235,7 +236,7 @@ public class LineStatusMarkerDrawUtil {
if (color != null) {
g.setColor(color);
double width = x2 - x1;
RectanglePainter2D.FILL.paint(g, x1, y - size, width, 2 * size, width);
RectanglePainter2D.FILL.paint(g, x1, y - size + 1, width, 2 * size - 2, width);
}
return;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.editor.ex;
import com.intellij.codeInsight.daemon.GutterMark;
@@ -9,11 +9,13 @@ import com.intellij.openapi.editor.EditorGutter;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.TextAnnotationGutterProvider;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.List;
public abstract class EditorGutterComponentEx extends JComponent implements EditorGutter {
@@ -77,4 +79,7 @@ public abstract class EditorGutterComponentEx extends JComponent implements Edit
public abstract @Nullable GutterMark getGutterRenderer(Point p);
public abstract @Nullable Runnable setLoadingIconForCurrentGutterMark();
@ApiStatus.Internal
public abstract boolean isInsideMarkerArea(@NotNull MouseEvent e);
}
@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.editor.impl;
import com.intellij.codeInsight.daemon.GutterMark;
@@ -151,14 +151,14 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
private boolean myHasInlaysWithGutterIcons;
private int myStartIconAreaWidth = START_ICON_AREA_WIDTH.get();
private int myIconsAreaWidth;
private int myLineNumberAreaWidth = getInitialLineNumberWidth();
private int myAdditionalLineNumberAreaWidth;
int myLineNumberAreaWidth = getInitialLineNumberWidth();
int myAdditionalLineNumberAreaWidth;
@NotNull private List<FoldRegion> myActiveFoldRegions = Collections.emptyList();
private int myTextAnnotationGuttersSize;
private int myTextAnnotationExtraSize;
int myTextAnnotationGuttersSize;
int myTextAnnotationExtraSize;
final IntList myTextAnnotationGutterSizes = new IntArrayList();
final ArrayList<TextAnnotationGutterProvider> myTextAnnotationGutters = new ArrayList<>();
private boolean myGapAfterAnnotations;
boolean myGapAfterAnnotations;
private final Map<TextAnnotationGutterProvider, EditorGutterAction> myProviderToListener = new HashMap<>();
private String myLastGutterToolTip;
@NotNull private LineNumberConverter myLineNumberConverter = LineNumberConverter.DEFAULT;
@@ -176,6 +176,7 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
private short myForcedRightFreePaintersAreaWidth = -1;
private int myLastNonDumbModeIconAreaWidth;
boolean myDnDInProgress;
private final EditorGutterLayout myLayout = new EditorGutterLayout(this);
@Nullable private AccessibleGutterLine myAccessibleGutterLine;
private final AlphaAnimationContext myAlphaContext = new AlphaAnimationContext(composite -> {
if (isShowing()) repaint();
@@ -299,7 +300,7 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
@Override
public Dimension getPreferredSize() {
int w = getFoldingAreaOffset() + getFoldingAreaWidth();
int w = myLayout.getWidth();
Dimension size = new Dimension(w, myEditor.getPreferredHeight());
JBInsets.addTo(size, getInsets());
return size;
@@ -789,6 +790,10 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
return null;
}
boolean isShowGapAfterAnnotations() {
return isAnnotationsShown() && (myGapAfterAnnotations || myTextAnnotationExtraSize > 0);
}
@FunctionalInterface
interface RangeHighlighterProcessor {
void process(@NotNull RangeHighlighter highlighter);
@@ -1219,6 +1224,17 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
return startFoldRegion == null || !startFoldRegion.equals(endFoldRegion);
}
@Override
public boolean isInsideMarkerArea(@NotNull MouseEvent e) {
if (ExperimentalUI.isNewUI()) {
int x = e.getX();
int offset = getLineMarkerFreePaintersAreaOffset();
int width = myLayout.getAreaWidth(EditorGutterLayout.RIGHT_FREE_PAINTERS_AREA);
return offset < x && x <= offset + width;
}
return e.getX() > getLineMarkerFreePaintersAreaOffset();
}
@Nullable
Rectangle getLineRendererRectangle(RangeHighlighter highlighter) {
if (!isLineMarkerVisible(highlighter)) return null;
@@ -1587,10 +1603,10 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
}
private int getFoldingAreaOffset() {
return getLineMarkerAreaOffset() + getLineMarkerAreaWidth();
return myLayout.getFoldingAreaOffset();
}
private int getFoldingAreaWidth() {
int getFoldingAreaWidth() {
return isFoldingOutlineShown() ? getFoldingAnchorWidth() + JBUIScale.scale(2) :
isRealEditor() ? getFoldingAnchorWidth() : 0;
}
@@ -1622,7 +1638,7 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
!myEditor.isInPresentationMode();
}
private static int getGapBetweenAreas() {
static int getGapBetweenAreas() {
return GAP_BETWEEN_AREAS.get();
}
@@ -1672,38 +1688,16 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
@Nullable
EditorMouseEventArea getEditorMouseAreaByOffset(int offset) {
if (isLineNumbersShown() && offset < getLineNumberAreaOffset() + getLineNumberAreaWidth()) {
return EditorMouseEventArea.LINE_NUMBERS_AREA;
}
if (isAnnotationsShown() && offset < getAnnotationsAreaOffset() + getAnnotationsAreaWidth()) {
return EditorMouseEventArea.ANNOTATIONS_AREA;
}
if (isLineMarkersShown() && offset < getFoldingAreaOffset()) {
return EditorMouseEventArea.LINE_MARKERS_AREA;
}
if (isFoldingOutlineShown() && offset < getFoldingAreaOffset() + getFoldingAreaWidth()) {
return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
}
return null;
return myLayout.getEditorMouseAreaByOffset(offset);
}
int getLineNumberAreaOffset() {
if (getLineNumberAreaWidth() == 0 && getAnnotationsAreaWidthEx() == 0 && getLineMarkerAreaWidth() == 0) {
return getFoldingAreaWidth() == 0 ? 0 : 1;
}
if (getLineNumberAreaWidth() == 0 && getAnnotationsAreaWidthEx() > 0) {
return 0; // no gap if annotations area is the first visible
}
return getGapBetweenAreas();
return myLayout.getLineNumberAreaOffset();
}
@Override
public int getAnnotationsAreaOffset() {
return getLineNumberAreaOffset() + getAreaWidthWithGap(getLineNumberAreaWidth());
return myLayout.getAnnotationsAreaOffset();
}
@Override
@@ -1717,18 +1711,12 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
@Override
public int getLineMarkerAreaOffset() {
return getAnnotationsAreaOffset() +
(myGapAfterAnnotations || myTextAnnotationExtraSize > 0
? getAreaWidthWithGap(getAnnotationsAreaWidthEx())
: getAnnotationsAreaWidthEx());
return myLayout.getLineMarkerAreaOffset();
}
@Override
public int getIconAreaOffset() {
if (ExperimentalUI.isNewUI()) {
return getLineMarkerFreePaintersAreaOffset() + getLeftFreePaintersAreaWidth() + getGapBetweenAreas();
}
return getLineMarkerAreaOffset() + getLeftFreePaintersAreaWidth();
return myLayout.getIconAreaOffset();
}
private int getLeftFreePaintersAreaOffset() {
@@ -1737,25 +1725,23 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
@Override
public int getLineMarkerFreePaintersAreaOffset() {
if (ExperimentalUI.isNewUI()) {
return getAnnotationsAreaOffset() + getAnnotationsAreaWidth() + getGapBetweenAreas();
}
return getIconAreaOffset() + myIconsAreaWidth + getGapAfterIconsArea();
return myLayout.getLineMarkerFreePaintersAreaOffset();
}
private int getLeftFreePaintersAreaWidth() {
return myLeftFreePaintersAreaShown ? myForcedLeftFreePaintersAreaWidth < 0 ? FREE_PAINTERS_LEFT_AREA_WIDTH.get()
: myForcedLeftFreePaintersAreaWidth
: 0;
int getLeftFreePaintersAreaWidth() {
if (!myLeftFreePaintersAreaShown) return 0;
if (myForcedLeftFreePaintersAreaWidth >= 0) return myForcedLeftFreePaintersAreaWidth;
return FREE_PAINTERS_LEFT_AREA_WIDTH.get();
}
private int getRightFreePaintersAreaWidth() {
int getRightFreePaintersAreaWidth() {
int width = myRightFreePaintersAreaShown ? myForcedRightFreePaintersAreaWidth < 0 ? FREE_PAINTERS_RIGHT_AREA_WIDTH.get()
: myForcedRightFreePaintersAreaWidth
: 0;
if (ExperimentalUI.isNewEditorTabs()) {
if (width == 0) return 0;
return FREE_PAINTERS_RIGHT_AREA_WIDTH.get();
return Math.max(FREE_PAINTERS_RIGHT_AREA_WIDTH.get(), JBUI.getInt("Gutter.VcsChanges.width", 3));
}
return width;
}
@@ -1765,7 +1751,7 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements
return myIconsAreaWidth;
}
private int getGapAfterIconsArea() {
int getGapAfterIconsArea() {
return isRealEditor() && areIconsShown() ? getGapBetweenAreas() : 0;
}
@@ -0,0 +1,212 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.event.EditorMouseEventArea;
import com.intellij.ui.ExperimentalUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Supplier;
/**
* @author Konstantin Bulenkov
*/
public class EditorGutterLayout {
static final String GAP_BETWEEN_AREAS = "Gap between areas";
static final String LINE_NUMBERS_AREA = "Line numbers";
static final String ADDITIONAL_LINE_NUMBERS_AREA = "Additional line numbers";
static final String ANNOTATIONS_AREA = "Annotations";
static final String LEFT_FREE_PAINTERS_AREA = "Left free painters";
static final String ICONS_AREA = "Icons";
static final String GAP_AFTER_ICONS_AREA = "Gap after icons";
static final String RIGHT_FREE_PAINTERS_AREA = "Right free painters";
static final String FOLDING_AREA = "Free painters";
static final String VERTICAL_LINE_AREA = "Vertical line";
private final EditorGutterComponentImpl myEditorGutter;
private List<GutterArea> myExpLayout;
private List<GutterArea> myClassicLayout;
public EditorGutterLayout(EditorGutterComponentImpl editorGutter) {
myEditorGutter = editorGutter;
}
public int getWidth() {
return getLayout().stream().map(GutterArea::width).reduce(0, Integer::sum);
}
@Nullable
public EditorMouseEventArea getEditorMouseAreaByOffset(int offset) {
int off = 0;
for (GutterArea area : getLayout()) {
off += area.widthFunc.get();
if (off >= offset) {
return area.mouseEventAreaType;
}
}
return null;
}
static class GutterArea {
private final String id;
private final Supplier<Integer> widthFunc;
private EditorMouseEventArea mouseEventAreaType;
private Supplier<Boolean> showIfFunc;
GutterArea(String ID, Supplier<Integer> areaWidth) {
id = ID;
widthFunc = areaWidth;
switch (ID) {
case LINE_NUMBERS_AREA:
case ADDITIONAL_LINE_NUMBERS_AREA: mouseEventAreaType = EditorMouseEventArea.LINE_NUMBERS_AREA; break;
case ANNOTATIONS_AREA: mouseEventAreaType = EditorMouseEventArea.ANNOTATIONS_AREA; break;
case LEFT_FREE_PAINTERS_AREA:
case ICONS_AREA:
case RIGHT_FREE_PAINTERS_AREA:
case GAP_AFTER_ICONS_AREA: mouseEventAreaType = EditorMouseEventArea.LINE_MARKERS_AREA; break;
case FOLDING_AREA:
case VERTICAL_LINE_AREA: mouseEventAreaType = EditorMouseEventArea.FOLDING_OUTLINE_AREA; break;
}
}
int width() {
if (showIfFunc != null && !showIfFunc.get()) {
return 0;
}
return widthFunc.get();
}
GutterArea as(EditorMouseEventArea type) {
mouseEventAreaType = type;
return this;
}
GutterArea showIf(Supplier<Boolean> showIf) {
showIfFunc = showIf;
return this;
}
}
List<GutterArea> getLayout() {
if (ExperimentalUI.isNewUI()) {
return getExperimentalGutterLayout();
}
return getClassicGutterLayout();
}
private List<GutterArea> getClassicGutterLayout() {
if (myClassicLayout == null) {
myClassicLayout = createClassicLayout();
}
return myClassicLayout;
}
private List<GutterArea> createClassicLayout() {
return List.of(
areaGap()
.as(EditorMouseEventArea.LINE_NUMBERS_AREA)
.showIf(this::isLineNumbersShown),
area(LINE_NUMBERS_AREA, () -> myEditorGutter.myLineNumberAreaWidth).showIf(this::isLineNumbersShown),
area(ADDITIONAL_LINE_NUMBERS_AREA, () -> myEditorGutter.myAdditionalLineNumberAreaWidth),
areaGap()
.as(EditorMouseEventArea.LINE_NUMBERS_AREA)
.showIf(this::isLineNumbersShown),
area(ANNOTATIONS_AREA, () -> myEditorGutter.myTextAnnotationGuttersSize),
areaGap()
.as(EditorMouseEventArea.ANNOTATIONS_AREA)
.showIf(() -> myEditorGutter.isShowGapAfterAnnotations()),
area(LEFT_FREE_PAINTERS_AREA, myEditorGutter::getLeftFreePaintersAreaWidth),
area(ICONS_AREA, myEditorGutter::getIconsAreaWidth).showIf(() -> myEditorGutter.isLineMarkersShown()),
area(GAP_AFTER_ICONS_AREA, myEditorGutter::getGapAfterIconsArea),
area(RIGHT_FREE_PAINTERS_AREA, myEditorGutter::getRightFreePaintersAreaWidth),
area(FOLDING_AREA, myEditorGutter::getFoldingAreaWidth)
);
}
private boolean isLineNumbersShown() {
return myEditorGutter.isLineNumbersShown();
}
private List<GutterArea> createExperimentalLayout() {
return List.of(
areaGap()
.as(EditorMouseEventArea.ANNOTATIONS_AREA)
.showIf(() -> myEditorGutter.isShowGapAfterAnnotations()),
area(ANNOTATIONS_AREA, () -> myEditorGutter.myTextAnnotationGuttersSize),
area(LEFT_FREE_PAINTERS_AREA, myEditorGutter::getLeftFreePaintersAreaWidth),
area(RIGHT_FREE_PAINTERS_AREA, myEditorGutter::getRightFreePaintersAreaWidth),
area(LINE_NUMBERS_AREA, () -> myEditorGutter.myLineNumberAreaWidth).showIf(this::isLineNumbersShown),
area(ADDITIONAL_LINE_NUMBERS_AREA, () -> myEditorGutter.myAdditionalLineNumberAreaWidth),
area(ICONS_AREA, myEditorGutter::getIconsAreaWidth).showIf(() -> myEditorGutter.isLineMarkersShown()),
area(GAP_AFTER_ICONS_AREA, myEditorGutter::getGapAfterIconsArea),
area(FOLDING_AREA, myEditorGutter::getFoldingAreaWidth)
);
}
@NotNull
private GutterArea areaGap() {
return area(GAP_BETWEEN_AREAS, EditorGutterComponentImpl::getGapBetweenAreas);
}
private List<GutterArea> getExperimentalGutterLayout() {
if (myExpLayout == null) {
myExpLayout = createExperimentalLayout();
}
return myExpLayout;
}
private static GutterArea area(String id, Supplier<Integer> areaWidth) {
return new GutterArea(id, areaWidth);
}
public int getAnnotationsAreaOffset() {
return getOffset(ANNOTATIONS_AREA);
}
protected int getAreaWidth(String ID) {
for (GutterArea area : getLayout()) {
if (area.id == ID) {
return area.width();
}
}
return 0;
}
private int getOffset(String ID) {
int offset = 0;
for (GutterArea area : getLayout()) {
if (area.id == ID) return offset;
offset += area.width();
}
return -1;
}
// getEditorMouseAreaByOffset
public int getFoldingAreaOffset() {
return getOffset(FOLDING_AREA);
}
public int getIconAreaOffset() {
return getOffset(ICONS_AREA);
}
public int getLeftFreePaintersAreaOffset() {
return getOffset(LEFT_FREE_PAINTERS_AREA);
}
public int getLineMarkerAreaOffset() {
return getOffset(LEFT_FREE_PAINTERS_AREA);
}
public int getLineMarkerFreePaintersAreaOffset() {
return getOffset(RIGHT_FREE_PAINTERS_AREA);
}
public int getLineNumberAreaOffset() {
return getOffset(LINE_NUMBERS_AREA);
}
public int getVerticalLineX() {return getOffset(VERTICAL_LINE_AREA);}
}