From e823f72e10ab94f980accf1a6735c224db0b13c2 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Tue, 11 Jan 2022 14:24:47 +0100 Subject: [PATCH] introduce EditorGutterLayout for managing component on the gutter GitOrigin-RevId: 84415e7cab2e9084ef33c917e2c96d7fedc50c37 --- .../diff/LineStatusMarkerDrawUtil.java | 11 +- .../editor/ex/EditorGutterComponentEx.java | 7 +- .../impl/EditorGutterComponentImpl.java | 94 ++++---- .../editor/impl/EditorGutterLayout.java | 212 ++++++++++++++++++ 4 files changed, 264 insertions(+), 60 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterLayout.java diff --git a/platform/diff-impl/src/com/intellij/openapi/diff/LineStatusMarkerDrawUtil.java b/platform/diff-impl/src/com/intellij/openapi/diff/LineStatusMarkerDrawUtil.java index 10c7c83dbee9..624bfce01a1e 100644 --- a/platform/diff-impl/src/com/intellij/openapi/diff/LineStatusMarkerDrawUtil.java +++ b/platform/diff-impl/src/com/intellij/openapi/diff/LineStatusMarkerDrawUtil.java @@ -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; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorGutterComponentEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorGutterComponentEx.java index 611489037999..d16fecd2bea2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorGutterComponentEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorGutterComponentEx.java @@ -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); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java index abb83e0cb359..30820ed63759 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java @@ -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 myActiveFoldRegions = Collections.emptyList(); - private int myTextAnnotationGuttersSize; - private int myTextAnnotationExtraSize; + int myTextAnnotationGuttersSize; + int myTextAnnotationExtraSize; final IntList myTextAnnotationGutterSizes = new IntArrayList(); final ArrayList myTextAnnotationGutters = new ArrayList<>(); - private boolean myGapAfterAnnotations; + boolean myGapAfterAnnotations; private final Map 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; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterLayout.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterLayout.java new file mode 100644 index 000000000000..3d5dfa2bafb8 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterLayout.java @@ -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 myExpLayout; + private List 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 widthFunc; + private EditorMouseEventArea mouseEventAreaType; + private Supplier showIfFunc; + + GutterArea(String ID, Supplier 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 showIf) { + showIfFunc = showIf; + return this; + } + } + + List getLayout() { + if (ExperimentalUI.isNewUI()) { + return getExperimentalGutterLayout(); + } + return getClassicGutterLayout(); + } + + private List getClassicGutterLayout() { + if (myClassicLayout == null) { + myClassicLayout = createClassicLayout(); + } + return myClassicLayout; + } + + private List 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 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 getExperimentalGutterLayout() { + if (myExpLayout == null) { + myExpLayout = createExperimentalLayout(); + } + return myExpLayout; + } + + private static GutterArea area(String id, Supplier 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);} +}