| package com.intellij.vcs.log.ui.render; |
| |
| import com.intellij.ui.JBColor; |
| import com.intellij.vcs.log.VcsRef; |
| import com.intellij.vcs.log.ui.VcsLogColorManager; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.awt.*; |
| import java.awt.font.FontRenderContext; |
| import java.awt.geom.RoundRectangle2D; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import static com.intellij.vcs.log.printer.idea.PrintParameters.HEIGHT_CELL; |
| |
| /** |
| * @author erokhins |
| */ |
| public class RefPainter { |
| private static final int RECTANGLE_X_PADDING = 4; |
| private static final int RECTANGLE_Y_PADDING = 3; |
| private static final int REF_PADDING = 13; |
| private static final int LABEL_PADDING = 3; |
| |
| private static final int FLAG_WIDTH = 8; |
| private static final int FLAG_PADDING = 8; |
| private static final int FLAG_TOP_INDENT = 2; |
| |
| private static final int ROUND_RADIUS = 5; |
| |
| public static final Font DEFAULT_FONT = new Font("Arial", Font.PLAIN, 12); |
| private static final Color DEFAULT_FONT_COLOR = JBColor.BLACK; |
| |
| @NotNull private final VcsLogColorManager myColorManager; |
| private final boolean myDrawMultiRepoIndicator; |
| |
| public RefPainter(@NotNull VcsLogColorManager manager, boolean drawMultiRepoIndicator) { |
| myColorManager = manager; |
| myDrawMultiRepoIndicator = drawMultiRepoIndicator; |
| } |
| |
| private double paddingStr(@NotNull String str, @NotNull FontRenderContext renderContext) { |
| return getStringWidth(str, renderContext) + REF_PADDING + flagWidth(); |
| } |
| |
| private static double getStringWidth(@NotNull String str, @NotNull FontRenderContext renderContext) { |
| return DEFAULT_FONT.getStringBounds(str, renderContext).getWidth(); |
| } |
| |
| private int flagWidth() { |
| return myColorManager.isMultipleRoots() && myDrawMultiRepoIndicator ? FLAG_WIDTH + FLAG_PADDING : 0; |
| } |
| |
| private static void drawText(@NotNull Graphics2D g2, @NotNull String str, int padding) { |
| FontMetrics metrics = g2.getFontMetrics(); |
| g2.setColor(DEFAULT_FONT_COLOR); |
| int x = padding + REF_PADDING / 2; |
| int y = HEIGHT_CELL / 2 + (metrics.getAscent() - metrics.getDescent()) / 2; |
| g2.drawString(str, x, y); |
| } |
| |
| private int draw(@NotNull Graphics2D g2, @NotNull VcsRef ref, int padding) { |
| Rectangle rectangle = drawLabel(g2, ref.getName(), padding, ref.getType().getBackgroundColor(), |
| myColorManager.getRootColor(ref.getRoot())); |
| return rectangle.x; |
| } |
| |
| private void drawRootIndicator(@NotNull Graphics2D g2, int x, int y, int height, @NotNull Color rootIndicatorColor) { |
| g2.setColor(rootIndicatorColor); |
| int x0 = x + FLAG_PADDING; |
| int xMid = x0 + FLAG_WIDTH / 2; |
| int xRight = x0 + FLAG_WIDTH; |
| |
| int y0 = y - FLAG_TOP_INDENT; |
| int yMid = y0 + 2 * height / 3 - 2; |
| int yBottom = y0 + height - 4; |
| |
| // something like a knight flag |
| Polygon polygon = new Polygon(new int[]{ x0, xRight, xRight, xMid, x0 }, |
| new int[]{ y0, y0, yMid, yBottom, yMid }, 5); |
| g2.fillPolygon(polygon); |
| g2.setColor(myColorManager.getReferenceBorderColor()); |
| g2.drawPolygon(polygon); |
| } |
| |
| public int padding(@NotNull Collection<String> refs, FontMetrics fontMetrics) { |
| int p = 0; |
| for (String ref : refs) { |
| XAndWidth xAndWidth = getXAndWidth(ref, 0, fontMetrics); |
| p += xAndWidth.x + xAndWidth.width + LABEL_PADDING; |
| } |
| if (p > 0) { // additional padding after all references looks better |
| p += LABEL_PADDING; |
| } |
| return p; |
| } |
| |
| public Map<Integer, VcsRef> draw(@NotNull Graphics2D g2, @NotNull Collection<VcsRef> refs, int startPadding, int maxWidth) { |
| float currentPadding = startPadding; |
| setupGraphics(g2); |
| FontRenderContext renderContext = g2.getFontRenderContext(); |
| Map<Integer, VcsRef> positions = new HashMap<Integer, VcsRef>(); |
| for (VcsRef ref : refs) { |
| int x = draw(g2, ref, (int)currentPadding); |
| positions.put(x, ref); |
| currentPadding += paddingStr(ref.getName(), renderContext); |
| if (maxWidth > 0 && x >= maxWidth) { |
| break; |
| } |
| } |
| return positions; |
| } |
| |
| private static void setupGraphics(Graphics2D g2) { |
| g2.setFont(DEFAULT_FONT); |
| g2.setStroke(new BasicStroke(1.5f)); |
| g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| } |
| |
| public void drawLabels(@NotNull Graphics2D g2, @NotNull Map<String, Color> labels, int startPadding) { |
| int padding = startPadding; |
| for (Map.Entry<String, Color> entry : labels.entrySet()) { |
| Rectangle rectangle = drawLabel(g2, entry.getKey(), padding, entry.getValue(), null); |
| padding += rectangle.width + LABEL_PADDING; |
| } |
| } |
| |
| private XAndWidth getXAndWidth(String label, int paddingX, FontMetrics metrics) { |
| int x = paddingX + REF_PADDING / 2 - RECTANGLE_X_PADDING; |
| int width = metrics.stringWidth(label) + 2 * RECTANGLE_X_PADDING + flagWidth(); |
| return new XAndWidth(x, width); |
| } |
| |
| public Rectangle drawLabel(@NotNull Graphics2D g2, @NotNull String label, int paddingX, @NotNull Color bgColor, |
| @Nullable Color rootIndicatorColor) { |
| setupGraphics(g2); |
| FontMetrics metrics = g2.getFontMetrics(); |
| XAndWidth xAndWidth = getXAndWidth(label, paddingX, metrics); |
| int x = xAndWidth.x; |
| int width = xAndWidth.width; |
| int y = RECTANGLE_Y_PADDING; |
| int height = HEIGHT_CELL - 2 * RECTANGLE_Y_PADDING; |
| RoundRectangle2D rectangle2D = new RoundRectangle2D.Double(x, y, width, height, ROUND_RADIUS, ROUND_RADIUS); |
| |
| g2.setColor(bgColor); |
| g2.fill(rectangle2D); |
| |
| g2.setColor(myColorManager.getReferenceBorderColor()); |
| g2.draw(rectangle2D); |
| |
| g2.setColor(JBColor.BLACK); |
| drawText(g2, label, paddingX + flagWidth()); |
| |
| if (rootIndicatorColor != null && myColorManager.isMultipleRoots() && myDrawMultiRepoIndicator) { |
| drawRootIndicator(g2, paddingX, y, height, rootIndicatorColor); |
| } |
| |
| return new Rectangle(x, y, width, height); |
| } |
| |
| private static class XAndWidth { |
| private final int x; |
| private final int width; |
| |
| private XAndWidth(int x, int width) { |
| this.x = x; |
| this.width = width; |
| } |
| } |
| |
| } |