diff: do not paint over annotations in gutter
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / util / DiffLineSeparatorRenderer.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.diff.util;
17
18 import com.intellij.openapi.editor.Editor;
19 import com.intellij.openapi.editor.colors.ColorKey;
20 import com.intellij.openapi.editor.colors.EditorColors;
21 import com.intellij.openapi.editor.colors.EditorColorsManager;
22 import com.intellij.openapi.editor.colors.EditorColorsScheme;
23 import com.intellij.openapi.editor.ex.EditorEx;
24 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
25 import com.intellij.openapi.editor.markup.LineMarkerRenderer;
26 import com.intellij.openapi.editor.markup.LineSeparatorRenderer;
27 import com.intellij.openapi.ui.GraphicsConfig;
28 import com.intellij.openapi.util.BooleanGetter;
29 import com.intellij.ui.ColorUtil;
30 import com.intellij.ui.Gray;
31 import com.intellij.util.ui.GraphicsUtil;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34
35 import java.awt.*;
36 import java.awt.geom.AffineTransform;
37 import java.util.Arrays;
38
39 public class DiffLineSeparatorRenderer implements LineMarkerRenderer, LineSeparatorRenderer {
40   @NotNull private final Editor myEditor;
41   @NotNull private final BooleanGetter myCondition;
42
43   public DiffLineSeparatorRenderer(@NotNull Editor editor) {
44     this(editor, BooleanGetter.TRUE);
45   }
46
47   public DiffLineSeparatorRenderer(@NotNull Editor editor, @NotNull BooleanGetter condition) {
48     myEditor = editor;
49     myCondition = condition;
50   }
51
52   public static void drawConnectorLine(@NotNull Graphics2D g,
53                                        int x1, int x2,
54                                        int start1, int end1,
55                                        int start2, int end2) {
56     drawConnectorLine(g, x1, x2, start1, start2, end1 - end2, null);
57   }
58
59   /*
60    * Divider
61    */
62   public static void drawConnectorLine(@NotNull Graphics2D g,
63                                        int x1, int x2,
64                                        int y1, int y2,
65                                        int lineHeight,
66                                        @Nullable EditorColorsScheme scheme) {
67     int step = getStepSize(lineHeight);
68     int height = getHeight(lineHeight);
69     int dx = getDeltaX(lineHeight);
70     int dy = getDeltaY(lineHeight);
71
72     int start1 = y1 + (lineHeight - height - step) / 2 + step / 2;
73     int start2 = y2 + (lineHeight - height - step) / 2 + step / 2;
74     int end1 = start1 + height - 1;
75     int end2 = start2 + height - 1;
76
77     int[] xPoints;
78     int[] yPoints;
79
80     if (Math.abs(x2 - x1) < Math.abs(y2 - y1)) {
81       if (y2 < y1) {
82         xPoints = new int[]{x1, x2 - dx, x2, x2, x1 + dx, x1};
83         yPoints = new int[]{start1, start2 + dy, start2, end2, end1 - dy, end1};
84       }
85       else {
86         xPoints = new int[]{x1, x1 + dx, x2, x2, x2 - dx, x1};
87         yPoints = new int[]{start1, start1 + dy, start2, end2, end2 - dy, end1};
88       }
89     }
90     else {
91       xPoints = new int[]{x1, x2, x2, x1};
92       yPoints = new int[]{start1, start2, end2, end1};
93     }
94
95     paintConnectorLine(g, xPoints, yPoints, lineHeight, scheme);
96   }
97
98   /*
99    * Gutter
100    */
101   @Override
102   public void paint(Editor editor, Graphics g, Rectangle r) {
103     if (!myCondition.get()) return;
104
105     int y = r.y;
106     int lineHeight = myEditor.getLineHeight();
107
108     EditorGutterComponentEx gutter = ((EditorEx)editor).getGutterComponentEx();
109     int annotationsOffset = gutter.getAnnotationsAreaOffset();
110     int annotationsWidth = gutter.getAnnotationsAreaWidth();
111     if (annotationsWidth != 0) {
112       g.setColor(editor.getColorsScheme().getColor(EditorColors.GUTTER_BACKGROUND));
113       g.fillRect(annotationsOffset, y, annotationsWidth, lineHeight);
114     }
115
116     draw(g, 0, y, lineHeight, myEditor.getColorsScheme());
117   }
118
119   /*
120    * Editor
121    */
122   @Override
123   public void drawLine(Graphics g, int x1, int x2, int y) {
124     if (!myCondition.get()) return;
125
126     y++; // we want y to be line's top position
127
128     final int gutterWidth = ((EditorEx)myEditor).getGutterComponentEx().getWidth();
129     int lineHeight = myEditor.getLineHeight();
130     int interval = getStepSize(lineHeight) * 2;
131
132     int shiftX = -interval; // skip zero index painting
133     if (DiffUtil.isMirrored(myEditor)) {
134       int contentWidth = ((EditorEx)myEditor).getScrollPane().getViewport().getWidth();
135       shiftX += contentWidth % interval - interval;
136       shiftX += gutterWidth % interval - interval;
137     }
138     else {
139       shiftX += -gutterWidth % interval - interval;
140     }
141
142     draw(g, shiftX, y, lineHeight, myEditor.getColorsScheme());
143   }
144
145   private static void draw(@NotNull Graphics g,
146                            int shiftX,
147                            int shiftY,
148                            int lineHeight,
149                            @Nullable EditorColorsScheme scheme) {
150     int step = getStepSize(lineHeight);
151     int height = getHeight(lineHeight);
152
153     Rectangle clip = g.getClipBounds();
154     if (clip.width <= 0) return;
155     int count = (clip.width / step + 3);
156     int shift = (clip.x - shiftX) / step;
157
158     int[] xPoints = new int[count];
159     int[] yPoints = new int[count];
160
161     shiftY += (lineHeight - height - step) / 2;
162
163     for (int index = 0; index < count; index++) {
164       int absIndex = index + shift;
165
166       int xPos = absIndex * step + shiftX;
167       int yPos;
168
169       if (absIndex == 0) {
170         yPos = step / 2 + shiftY;
171       }
172       else if (absIndex % 2 == 0) {
173         yPos = shiftY;
174       }
175       else {
176         yPos = step + shiftY;
177       }
178
179       xPoints[index] = xPos;
180       yPoints[index] = yPos;
181     }
182
183     GraphicsConfig config = GraphicsUtil.disableAAPainting(g);
184     try {
185       paintLine(g, xPoints, yPoints, lineHeight, scheme);
186     }
187     finally {
188       config.restore();
189     }
190   }
191
192   private static void paintLine(@NotNull Graphics g,
193                                 @NotNull int[] xPoints, @NotNull int[] yPoints,
194                                 int lineHeight,
195                                 @Nullable EditorColorsScheme scheme) {
196     int height = getHeight(lineHeight);
197     if (scheme == null) scheme = EditorColorsManager.getInstance().getGlobalScheme();
198
199     Graphics2D gg = ((Graphics2D)g);
200     AffineTransform oldTransform = gg.getTransform();
201
202     for (int i = 0; i < height; i++) {
203       Color color = getTopBorderColor(i, lineHeight, scheme);
204       if (color == null) color = getBottomBorderColor(i, lineHeight, scheme);
205       if (color == null) color = getBackgroundColor(scheme);
206
207       gg.setColor(color);
208       gg.drawPolyline(xPoints, yPoints, xPoints.length);
209       gg.translate(0, 1);
210     }
211     gg.setTransform(oldTransform);
212   }
213
214   private static void paintConnectorLine(@NotNull Graphics g,
215                                          @NotNull int[] xPoints, @NotNull int[] yPoints,
216                                          int lineHeight,
217                                          @Nullable EditorColorsScheme scheme) {
218     // TODO: shadow looks bad with big lineHeight and slope
219     int height = getHeight(lineHeight);
220     if (scheme == null) scheme = EditorColorsManager.getInstance().getGlobalScheme();
221
222     Graphics2D gg = ((Graphics2D)g);
223     AffineTransform oldTransform = gg.getTransform();
224
225     // background
226     gg.setColor(getBackgroundColor(scheme));
227     gg.fillPolygon(xPoints, yPoints, xPoints.length);
228
229     if (scheme.getColor(TOP_BORDER) != null) {
230       for (int i = 0; i < height; i++) {
231         Color color = getTopBorderColor(i, lineHeight, scheme);
232         if (color == null) break;
233
234         gg.setColor(color);
235         gg.drawPolyline(xPoints, yPoints, xPoints.length / 2);
236         gg.translate(0, 1);
237       }
238       gg.setTransform(oldTransform);
239     }
240
241     if (scheme.getColor(BOTTOM_BORDER) != null) {
242       int[] xBottomPoints = Arrays.copyOfRange(xPoints, xPoints.length / 2, xPoints.length);
243       int[] yBottomPoints = Arrays.copyOfRange(yPoints, xPoints.length / 2, xPoints.length);
244
245       for (int i = height - 1; i >= 0; i--) {
246         Color color = getBottomBorderColor(i, lineHeight, scheme);
247         if (color == null) break;
248
249         gg.setColor(color);
250         gg.drawPolyline(xBottomPoints, yBottomPoints, xPoints.length / 2);
251         gg.translate(0, -1);
252       }
253       gg.setTransform(oldTransform);
254     }
255   }
256
257   //
258   // Parameters
259   //
260
261   private static final ColorKey BACKGROUND = ColorKey.createColorKey("DIFF_SEPARATORS_BACKGROUND");
262   private static final ColorKey TOP_BORDER = ColorKey.createColorKey("DIFF_SEPARATORS_TOP_BORDER");
263   private static final ColorKey BOTTOM_BORDER = ColorKey.createColorKey("DIFF_SEPARATORS_BOTTOM_BORDER");
264
265   private static int getStepSize(int lineHeight) {
266     return Math.max(lineHeight / 3, 1);
267   }
268
269   private static int getHeight(int lineHeight) {
270     return Math.max(lineHeight / 2, 1);
271   }
272
273   private static int getDeltaX(int lineHeight) {
274     return Math.max(lineHeight / 4, 1);
275   }
276
277   private static int getDeltaY(int lineHeight) {
278     return Math.max(lineHeight / 6, 1);
279   }
280
281   @NotNull
282   private static Color getBackgroundColor(@NotNull EditorColorsScheme scheme) {
283     Color color = scheme.getColor(BACKGROUND);
284     return color != null ? color : Gray._128;
285   }
286
287   @Nullable
288   private static Color getTopBorderColor(int i, int lineHeight, @NotNull EditorColorsScheme scheme) {
289     int border = Math.max(lineHeight / 4, 1);
290     double ratio = (double)i / border;
291     if (ratio > 1) return null;
292
293     Color top = scheme.getColor(TOP_BORDER);
294     if (top == null) return null;
295
296     Color background = getBackgroundColor(scheme);
297     return ColorUtil.mix(top, background, ratio);
298   }
299
300   @Nullable
301   private static Color getBottomBorderColor(int i, int lineHeight, @NotNull EditorColorsScheme scheme) {
302     int height = getHeight(lineHeight);
303     int border = Math.max(lineHeight / 12, 1);
304
305     int index = (height - i - 1);
306     double ratio = (double)index / border;
307     if (ratio > 1) return null;
308
309     Color bottom = scheme.getColor(BOTTOM_BORDER);
310     if (bottom == null) return null;
311
312     Color background = getBackgroundColor(scheme);
313     return ColorUtil.mix(bottom, background, ratio);
314   }
315 }