From 6f804df909738a742d8a2af0e2d62fbfcbe7fb65 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Wed, 14 Mar 2018 18:31:54 +0300 Subject: [PATCH] Add EffectPainter2D and WavePainter2D versions - also fix EffectPainterTest --- .../com/intellij/ui/paint/EffectPainter.java | 224 +----------- .../intellij/ui/paint/EffectPainter2D.java | 329 ++++++++++++++++++ .../src/com/intellij/util/ui/WavePainter.java | 66 +--- .../com/intellij/util/ui/WavePainter2D.java | 85 +++++ 4 files changed, 427 insertions(+), 277 deletions(-) create mode 100644 platform/platform-api/src/com/intellij/ui/paint/EffectPainter2D.java create mode 100644 platform/util/src/com/intellij/util/ui/WavePainter2D.java diff --git a/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java b/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java index 87e5f54cf7f9..a180006d1869 100644 --- a/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java +++ b/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2018 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. @@ -15,18 +15,9 @@ */ package com.intellij.ui.paint; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.util.JBHiDPIScaledImage; -import com.intellij.util.ui.JBUI; import com.intellij.util.ui.RegionPainter; -import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.WavePainter; import java.awt.*; -import java.awt.font.LineMetrics; -import java.awt.geom.*; -import java.awt.image.BufferedImage; -import java.util.concurrent.ConcurrentHashMap; /** * @author Sergey.Malenkov @@ -48,12 +39,7 @@ public enum EffectPainter implements RegionPainter { */ @Override public void paint(Graphics2D g, int x, int y, int width, int height, Font font) { - if (!Registry.is("ide.text.effect.new")) { - g.drawLine(x, y + 1, x + width, y + 1); - } - else { - paintUnderline(g, x, y, width, height, font, 1, this); - } + EffectPainter2D.LINE_UNDERSCORE.paint(g, x, y, width, height, font); } }, /** @@ -72,13 +58,7 @@ public enum EffectPainter implements RegionPainter { */ @Override public void paint(Graphics2D g, int x, int y, int width, int height, Font font) { - if (!Registry.is("ide.text.effect.new")) { - int h = JBUI.scale(Registry.intValue("editor.bold.underline.height", 2)); - g.fillRect(x, y, width, h); - } - else { - paintUnderline(g, x, y, width, height, font, 2, this); - } + EffectPainter2D.BOLD_LINE_UNDERSCORE.paint(g, x, y, width, height, font); } }, /** @@ -97,7 +77,7 @@ public enum EffectPainter implements RegionPainter { */ @Override public void paint(Graphics2D g, int x, int y, int width, int height, Font font) { - paintUnderline(g, x, y, width, height, font, 2, this); + EffectPainter2D.BOLD_DOTTED_UNDERSCORE.paint(g, x, y, width, height, font); } }, /** @@ -116,15 +96,7 @@ public enum EffectPainter implements RegionPainter { */ @Override public void paint(Graphics2D g, int x, int y, int width, int height, Font font) { - if (!Registry.is("ide.text.effect.new")) { - WavePainter.forColor(g.getColor()).paint(g, x, x + width, y + height); - } - else if (Registry.is("ide.text.effect.new.metrics")) { - paintUnderline(g, x, y, width, height, font, 3, this); - } - else if (width > 0 && height > 0) { - Cached.WAVE_UNDERSCORE.paint(g, x, y, width, height, null); - } + EffectPainter2D.WAVE_UNDERSCORE.paint(g, x, y, width, height, font); } }, /** @@ -143,191 +115,7 @@ public enum EffectPainter implements RegionPainter { */ @Override public void paint(Graphics2D g, int x, int y, int width, int height, Font font) { - if (width > 0 && height > 0) { - if (!Registry.is("ide.text.effect.new.metrics")) { - drawLineCentered(g, x, y - height, width, height, 1, this); - } - else { - if (font == null) font = g.getFont(); - LineMetrics metrics = font.getLineMetrics("", g.getFontRenderContext()); - int offset = (int)(0.5 - metrics.getStrikethroughOffset()); - int thickness = Math.max(1, (int)(0.5 + metrics.getStrikethroughThickness())); - drawLine(g, x, y - offset, width, thickness, this); - } - } - } - }; - - private static int getMaxHeight(int height) { - return height > 7 && Registry.is("ide.text.effect.new.scale") ? height >> 1 : 3; - } - - private static void paintUnderline(Graphics2D g, int x, int y, int width, int height, Font font, int thickness, EffectPainter painter) { - if (width > 0 && height > 0) { - if (Registry.is("ide.text.effect.new.metrics")) { - if (font == null) font = g.getFont(); - LineMetrics metrics = font.getLineMetrics("", g.getFontRenderContext()); - thickness = Math.max(thickness, (int)(0.5 + thickness * metrics.getUnderlineThickness())); - int offset = Math.min(height - thickness, Math.max(1, (int)(0.5 + metrics.getUnderlineOffset()))); - if (offset < 1) { - offset = height > 3 ? 1 : 0; - thickness = height - offset; - } - drawLine(g, x, y + offset, width, thickness, painter); - } - else { - if (height > 3) { - int max = getMaxHeight(height); - y += height - max; - height = max; - if (thickness > 1 && height > 3) { - thickness = JBUI.scale(thickness); - } - } - drawLineCentered(g, x, y, width, height, thickness, painter); - } - } - } - - private static void drawLineCentered(Graphics2D g, int x, int y, int width, int height, int thickness, EffectPainter painter) { - int offset = height - thickness; - if (offset > 0) { - y += offset - (offset >> 1); - height = thickness; - } - drawLine(g, x, y, width, height, painter); - } - - private static void drawLine(Graphics2D g, int x, int y, int width, int height, EffectPainter painter) { - if (painter == BOLD_DOTTED_UNDERSCORE) { - int dx = (x % height + height) % height; - int w = width + dx; - int dw = (w % height + height) % height; - Cached.BOLD_DOTTED_UNDERSCORE.paint(g, x - dx, y, dw == 0 ? w : w - dw + height, height, null); - } - else if (painter == WAVE_UNDERSCORE) { - Cached.WAVE_UNDERSCORE.paint(g, x, y, width, height, null); - } - else { - g.fillRect(x, y, width, height); - } - } - - private enum Cached implements RegionPainter { - BOLD_DOTTED_UNDERSCORE { - @Override - int getPeriod(int height) { - return height; - } - - @Override - void paintImage(Graphics2D g, int width, int height, int period) { - Integer round = period <= 2 && !UIUtil.isJreHiDPI(g) ? null : period; - for (int dx = 0; dx < width; dx += period + period) { - RectanglePainter.FILL.paint(g, dx, 0, period, period, round); - } - } - }, - WAVE_UNDERSCORE { - private final BasicStroke THIN_STROKE = new BasicStroke(.7f); - - @Override - int getPeriod(int height) { - return Math.max((Registry.is("ide.text.effect.new.metrics") ? height : getMaxHeight(height)) - 1, 1); - } - - @Override - void paintImage(Graphics2D g, int width, int height, int period) { - double dx = 0; - double lower = height - 1; - double upper = lower - period; - if (Registry.is("ide.text.effect.new.metrics")) { - if (height > 3) { - float fix = height / 3f; - g.setStroke(new BasicStroke(fix)); - if (fix > 1) { - fix = (fix - 1) / 2; - lower -= fix; - upper += fix; - } - } - height += 2; - if (g.getClass().getName().equals("com.intellij.util.HiDPIScaledGraphics")) { - lower += .5; - upper += .5; - } - } - Path2D path = new Path2D.Double(); - path.moveTo(dx, lower); - if (height < 6) { - g.setStroke(THIN_STROKE); - while (dx < width) { - path.lineTo(dx += period, upper); - path.lineTo(dx += period, lower); - } - } - else { - double size = (double)period / 2; - double prev = dx - size / 2; - double center = (upper + lower) / 2; - while (dx < width) { - path.quadTo(prev += size, lower, dx += size, center); - path.quadTo(prev += size, upper, dx += size, upper); - path.quadTo(prev += size, upper, dx += size, center); - path.quadTo(prev += size, lower, dx += size, lower); - } - } - path.lineTo((double)width, lower); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.draw(path); - } - }; - - // we should not recalculate caches when IDEA is on Retina and non-Retina - private final ConcurrentHashMap myNormalCache = new ConcurrentHashMap<>(); - private final ConcurrentHashMap myRetinaCache = new ConcurrentHashMap<>(); - - abstract int getPeriod(int height); - - abstract void paintImage(Graphics2D g, int width, int height, int period); - - void paintImage(Graphics2D g, Paint paint, int width, int height, int period) { - try { - g.setPaint(paint); - paintImage(g, width, height, period); - } - finally { - g.dispose(); - } - } - - BufferedImage getImage(Graphics2D g, Color color, int height) { - Long key = color.getRGB() ^ ((long)height << 32); - ConcurrentHashMap cache = UIUtil.isRetina(g) ? myRetinaCache : myNormalCache; - return cache.computeIfAbsent(key, k -> createImage(g, color, height)); - } - - BufferedImage createImage(Graphics2D g, Paint paint, int height) { - int period = getPeriod(height); - int width = period << (paint instanceof Color ? 8 : 1); - BufferedImage image = UIUtil.createImage(g, width, height, BufferedImage.TYPE_INT_ARGB); - paintImage(image.createGraphics(), paint, width, height, period); - return image; - } - - @Override - public void paint(Graphics2D g, int x, int y, int width, int height, Paint paint) { - if (paint == null) paint = g.getPaint(); - g = (Graphics2D)g.create(x, y, width, height); - g.setComposite(AlphaComposite.SrcOver); - BufferedImage image = paint instanceof Color ? getImage(g, (Color)paint, height) : createImage(g, paint, height); - int period = image.getWidth(null); - if (image instanceof JBHiDPIScaledImage) period /= 2; - int offset = (x % period + period) % period; // normalize - for (int dx = -offset; dx < width; dx += period) { - UIUtil.drawImage(g, image, dx, 0, null); - } - g.dispose(); + EffectPainter2D.STRIKE_THROUGH.paint(g, x, y, width, height, font); } } } diff --git a/platform/platform-api/src/com/intellij/ui/paint/EffectPainter2D.java b/platform/platform-api/src/com/intellij/ui/paint/EffectPainter2D.java new file mode 100644 index 000000000000..0f5b1e9e2943 --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/paint/EffectPainter2D.java @@ -0,0 +1,329 @@ +// Copyright 2000-2018 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. +package com.intellij.ui.paint; + +import com.intellij.openapi.util.registry.Registry; +import com.intellij.util.JBHiDPIScaledImage; +import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.WavePainter2D; + +import java.awt.*; +import java.awt.font.LineMetrics; +import java.awt.geom.*; +import java.awt.image.BufferedImage; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Sergey.Malenkov + * @author tav + */ +public enum EffectPainter2D implements RegionPainter2D { + /** + * @see com.intellij.openapi.editor.markup.EffectType#LINE_UNDERSCORE + */ + LINE_UNDERSCORE { + /** + * Draws a horizontal line under a text. + * + * @param g the {@code Graphics2D} object to render to + * @param x text position + * @param y text baseline + * @param width text width + * @param height available space under text + * @param font optional font to calculate line metrics + */ + @Override + public void paint(Graphics2D g, double x, double y, double width, double height, Font font) { + if (!Registry.is("ide.text.effect.new")) { + LinePainter2D.paint(g, x, y + 1, x + width, y + 1); + } + else { + paintUnderline(g, x, y, width, height, font, 1, this); + } + } + }, + /** + * @see com.intellij.openapi.editor.markup.EffectType#BOLD_LINE_UNDERSCORE + */ + BOLD_LINE_UNDERSCORE { + /** + * Draws a bold horizontal line under a text. + * + * @param g the {@code Graphics2D} object to render to + * @param x text position + * @param y text baseline + * @param width text width + * @param height available space under text + * @param font optional font to calculate line metrics + */ + @Override + public void paint(Graphics2D g, double x, double y, double width, double height, Font font) { + if (!Registry.is("ide.text.effect.new")) { + int h = JBUI.scale(Registry.intValue("editor.bold.underline.height", 2)); + RectanglePainter2D.FILL.paint(g, x, y, width, h); + } + else { + paintUnderline(g, x, y, width, height, font, 2, this); + } + } + }, + /** + * @see com.intellij.openapi.editor.markup.EffectType#BOLD_DOTTED_LINE + */ + BOLD_DOTTED_UNDERSCORE { + /** + * Draws a bold horizontal line of dots under a text. + * + * @param g the {@code Graphics2D} object to render to + * @param x text position + * @param y text baseline + * @param width text width + * @param height available space under text + * @param font optional font to calculate line metrics + */ + @Override + public void paint(Graphics2D g, double x, double y, double width, double height, Font font) { + paintUnderline(g, x, y, width, height, font, 2, this); + } + }, + /** + * @see com.intellij.openapi.editor.markup.EffectType#WAVE_UNDERSCORE + */ + WAVE_UNDERSCORE { + /** + * Draws a horizontal wave under a text. + * + * @param g the {@code Graphics2D} object to render to + * @param x text position + * @param y text baseline + * @param width text width + * @param height available space under text + * @param font optional font to calculate line metrics + */ + @Override + public void paint(Graphics2D g, double x, double y, double width, double height, Font font) { + if (!Registry.is("ide.text.effect.new")) { + WavePainter2D.forColor(g.getColor()).paint(g, x, x + width, y + height); + } + else if (Registry.is("ide.text.effect.new.metrics")) { + paintUnderline(g, x, y, width, height, font, 3, this); + } + else if (width > 0 && height > 0) { + Cached.WAVE_UNDERSCORE.paint(g, x, y, width, height, null); + } + } + }, + /** + * @see com.intellij.openapi.editor.markup.EffectType#STRIKEOUT + */ + STRIKE_THROUGH { + /** + * Draws a horizontal line through a text. + * + * @param g the {@code Graphics2D} object to render to + * @param x text position + * @param y text baseline + * @param width text width + * @param height text height + * @param font optional font to calculate line metrics + */ + @Override + public void paint(Graphics2D g, double x, double y, double width, double height, Font font) { + if (width > 0 && height > 0) { + if (!Registry.is("ide.text.effect.new.metrics")) { + drawLineCentered(g, x, y - height, width, height, 1, this); + } + else { + if (font == null) font = g.getFont(); + LineMetrics metrics = font.getLineMetrics("", g.getFontRenderContext()); + double offset = 0.5 - metrics.getStrikethroughOffset(); + double thickness = Math.max(1, 0.5 + metrics.getStrikethroughThickness()); + drawLine(g, x, y - offset, width, thickness, this); + } + } + } + }; + + private static int getMaxHeight(double height) { + return height > 7 && Registry.is("ide.text.effect.new.scale") ? (int)height >> 1 : 3; + } + + private static void paintUnderline(Graphics2D g, double x, double y, double width, double height, Font font, double thickness, EffectPainter2D painter) { + if (width > 0 && height > 0) { + if (Registry.is("ide.text.effect.new.metrics")) { + if (font == null) font = g.getFont(); + LineMetrics metrics = font.getLineMetrics("", g.getFontRenderContext()); + thickness = Math.max(thickness, 0.5 + thickness * metrics.getUnderlineThickness()); + double offset = Math.min(height - thickness, Math.max(1, 0.5 + metrics.getUnderlineOffset())); + if (offset < 1) { + offset = height > 3 ? 1 : 0; + thickness = height - offset; + } + drawLine(g, x, y + offset, width, thickness, painter); + } + else { + if (height > 3) { + int max = getMaxHeight((int)height); + y += height - max; + height = max; + if (thickness > 1 && height > 3) { + thickness = JBUI.scale((float)thickness); + } + } + drawLineCentered(g, x, y, width, height, thickness, painter); + } + } + } + + private static void drawLineCentered(Graphics2D g, double x, double y, double width, double height, double thickness, EffectPainter2D painter) { + int offset = (int)(height - thickness); + if (offset > 0) { + y += offset - (offset >> 1); + height = thickness; + } + drawLine(g, x, y, width, height, painter); + } + + private static void drawLine(Graphics2D g, double x, double y, double width, double height, EffectPainter2D painter) { + if (painter == BOLD_DOTTED_UNDERSCORE) { + double dx = (x % height + height) % height; + double w = width + dx; + double dw = (w % height + height) % height; + Cached.BOLD_DOTTED_UNDERSCORE.paint(g, x - dx, y, dw == 0 ? w : w - dw + height, height, null); + } + else if (painter == WAVE_UNDERSCORE) { + Cached.WAVE_UNDERSCORE.paint(g, x, y, width, height, null); + } + else { + RectanglePainter2D.FILL.paint(g, x, y, width, height); + } + } + + private enum Cached implements RegionPainter2D { + BOLD_DOTTED_UNDERSCORE { + @Override + double getPeriod(double height) { + return height; + } + + @Override + void paintImage(Graphics2D g, double width, double height, double period) { + Double round = period <= 2 && !UIUtil.isJreHiDPI(g) ? null : period; + for (int dx = 0; dx < width; dx += period + period) { + RectanglePainter2D.FILL.paint(g, dx, 0, period, period, round, LinePainter2D.StrokeType.INSIDE, 1, RenderingHints.VALUE_ANTIALIAS_DEFAULT); + } + } + }, + WAVE_UNDERSCORE { + private final BasicStroke THIN_STROKE = new BasicStroke(.7f); + + @Override + double getPeriod(double height) { + return Math.max((Registry.is("ide.text.effect.new.metrics") ? height : getMaxHeight(height)) - 1, 1); + } + + @Override + void paintImage(Graphics2D g, double width, double height, double period) { + double dx = 0; + double lower = height - 1; + double upper = lower - period; + if (Registry.is("ide.text.effect.new.metrics")) { + if (height > 3) { + double fix = height / 3; + g.setStroke(new BasicStroke((float)fix)); + if (fix > 1) { + fix = (fix - 1) / 2; + lower -= fix; + upper += fix; + } + } + height += 2; + if (g.getClass().getName().equals("com.intellij.util.HiDPIScaledGraphics")) { + lower += .5; + upper += .5; + } + } + Path2D path = new Path2D.Double(); + path.moveTo(dx, lower); + if (height < 6) { + g.setStroke(THIN_STROKE); + while (dx < width) { + path.lineTo(dx += period, upper); + path.lineTo(dx += period, lower); + } + } + else { + double size = period / 2; + double prev = dx - size / 2; + double center = (upper + lower) / 2; + while (dx < width) { + path.quadTo(prev += size, lower, dx += size, center); + path.quadTo(prev += size, upper, dx += size, upper); + path.quadTo(prev += size, upper, dx += size, center); + path.quadTo(prev += size, lower, dx += size, lower); + } + } + path.lineTo(width, lower); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.draw(path); + } + }; + + // we should not recalculate caches when IDEA is on Retina and non-Retina + private final ConcurrentHashMap myNormalCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap myHiDPICache = new ConcurrentHashMap<>(); + + abstract double getPeriod(double height); + + abstract void paintImage(Graphics2D g, double width, double height, double period); + + void paintImage(Graphics2D g, Paint paint, double width, double height, double period) { + try { + g.setPaint(paint); + paintImage(g, width, height, period); + } + finally { + g.dispose(); + } + } + + BufferedImage getImage(Graphics2D g, Color color, double height) { + Long key = color.getRGB() ^ ((long)height << 32); + ConcurrentHashMap cache = UIUtil.isJreHiDPI(g) ? myHiDPICache : myNormalCache; + return cache.computeIfAbsent(key, k -> createImage(g, color, height)); + } + + BufferedImage createImage(Graphics2D g, Paint paint, double height) { + double period = getPeriod(height); + int width = (int)period << (paint instanceof Color ? 8 : 1); + BufferedImage image; + if (UIUtil.isJreHiDPI(g)) { + image = new JBHiDPIScaledImage(g, width, height, BufferedImage.TYPE_INT_ARGB); + } else { + image = UIUtil.createImage(g, width, (int)height, BufferedImage.TYPE_INT_ARGB); + } + paintImage(image.createGraphics(), paint, width, height, period); + return image; + } + + @Override + public void paint(Graphics2D g, double x, double y, double width, double height, Paint paint) { + if (paint == null) paint = g.getPaint(); + g = (Graphics2D)g.create(); + g.translate(x, y); + g.clip(new Rectangle2D.Double(0, 0, width, height)); + + g.setComposite(AlphaComposite.SrcOver); + BufferedImage image = paint instanceof Color ? getImage(g, (Color)paint, height) : createImage(g, paint, height); + int period = image.getWidth(null); + if (image instanceof JBHiDPIScaledImage) period /= 2; + double offset = (x % period + period) % period; // normalize + g.translate(-offset, 0); + for (double dx = -offset; dx < width; dx += period) { + UIUtil.drawImage(g, image, 0, 0, null); + g.translate(period, 0); + } + g.dispose(); + } + } +} diff --git a/platform/util/src/com/intellij/util/ui/WavePainter.java b/platform/util/src/com/intellij/util/ui/WavePainter.java index 143fe51cc69c..f8cc321d24b5 100644 --- a/platform/util/src/com/intellij/util/ui/WavePainter.java +++ b/platform/util/src/com/intellij/util/ui/WavePainter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2018 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. @@ -16,75 +16,23 @@ package com.intellij.util.ui; import java.awt.*; -import java.awt.geom.GeneralPath; -import java.awt.image.BufferedImage; -import java.util.HashMap; -import java.util.Map; /** * Draws a 'wavy' line of 1-pixel amplitude. Instances are cached for performance reasons. *

* This class is not thread-safe, it's supposed to be used in EDT only. + * + * @see WavePainter2D */ -public class WavePainter { - private static final float STROKE_WIDTH = 0.7f; - - private static final Map myPainters = new HashMap(); - private static final int PATTERN_WIDTH = 4000; - - private final BufferedImage myImage; - - private WavePainter(Color color) { - myImage = UIUtil.createImage(PATTERN_WIDTH, 3, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = myImage.createGraphics(); - try { - GraphicsUtil.setupAAPainting(g); - g.setStroke(new BasicStroke(STROKE_WIDTH)); - g.setColor(color); - double height = 1; - double cycle = 4 * height; - final double wavedAt = 3 - STROKE_WIDTH - height; - GeneralPath wavePath = new GeneralPath(); - wavePath.moveTo(0, wavedAt - Math.cos(0 * 2 * Math.PI / cycle) * height); - for (int x = 0; x < PATTERN_WIDTH; x++) { - wavePath.lineTo(x, wavedAt - Math.cos(x * 2 * Math.PI / cycle) * height); - } - g.draw(wavePath); - } - finally { - g.dispose(); - } - } +public abstract class WavePainter { + protected WavePainter() {} /** * Paints a wave in given coordinate range. {@code y} defines the lower boundary of painted wave. */ - public void paint(Graphics2D g, int xStart, int xEnd, int y) { - Shape oldClip = g.getClip(); - final Rectangle rectangle = new Rectangle(xStart, y - 3, xEnd - xStart, 3); - final Rectangle waveClip = oldClip != null ? oldClip.getBounds().intersection(rectangle) : rectangle; - if (waveClip.isEmpty()) return; - - Composite oldComposite = g.getComposite(); - try { - g.setComposite(AlphaComposite.SrcOver); - g.setClip(waveClip); - xStart -= xStart % 4; - UIUtil.drawImage(g, myImage, xStart, y - 3, null); - } finally { - g.setComposite(oldComposite); - g.setClip(oldClip); - } - } + public abstract void paint(Graphics2D g, int xStart, int xEnd, int y); public static WavePainter forColor(Color color) { - WavePainter painter = myPainters.get(color); - if (painter == null) { - painter = new WavePainter(color); - // creating a new Color instance, as the one passed as parameter can be mutable (JBColor) and shouldn't be used as a map key - //noinspection UseJBColor - myPainters.put(new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()), painter); - } - return painter; + return WavePainter2D.forColor(color); } } diff --git a/platform/util/src/com/intellij/util/ui/WavePainter2D.java b/platform/util/src/com/intellij/util/ui/WavePainter2D.java new file mode 100644 index 000000000000..a30c58eff834 --- /dev/null +++ b/platform/util/src/com/intellij/util/ui/WavePainter2D.java @@ -0,0 +1,85 @@ +// Copyright 2000-2018 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. +package com.intellij.util.ui; + +import java.awt.*; +import java.awt.geom.GeneralPath; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; + +/** + * Draws a 'wavy' line of 1-pixel amplitude. Instances are cached for performance reasons. + *

+ * This class is not thread-safe, it's supposed to be used in EDT only. + */ +public class WavePainter2D extends WavePainter { + private static final float STROKE_WIDTH = 0.7f; + + private static final Map myPainters = new HashMap(); + private static final int PATTERN_WIDTH = 4000; + + private final BufferedImage myImage; + + private WavePainter2D(Color color) { + myImage = UIUtil.createImage(PATTERN_WIDTH, 3, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = myImage.createGraphics(); + try { + GraphicsUtil.setupAAPainting(g); + g.setStroke(new BasicStroke(STROKE_WIDTH)); + g.setColor(color); + double height = 1; + double cycle = 4 * height; + final double wavedAt = 3 - STROKE_WIDTH - height; + GeneralPath wavePath = new GeneralPath(); + wavePath.moveTo(0, wavedAt - Math.cos(0 * 2 * Math.PI / cycle) * height); + for (int x = 0; x < PATTERN_WIDTH; x++) { + wavePath.lineTo(x, wavedAt - Math.cos(x * 2 * Math.PI / cycle) * height); + } + g.draw(wavePath); + } + finally { + g.dispose(); + } + } + + /** + * Paints a wave in given coordinate range. {@code y} defines the lower boundary of painted wave. + */ + @Override + public void paint(Graphics2D g, int xStart, int xEnd, int y) { + paint(g, (double)xStart, (double)xEnd, (double)y); + } + + /** + * Paints a wave in given coordinate range. {@code y} defines the lower boundary of painted wave. + */ + public void paint(Graphics2D g, double xStart, double xEnd, double y) { + Shape clip = g.getClip(); + final Rectangle2D rectangle = new Rectangle2D.Double(xStart, y - 3, xEnd - xStart, 3); + final Rectangle2D waveClip = clip != null ? clip.getBounds2D().createIntersection(rectangle) : rectangle; + if (waveClip.isEmpty()) return; + + Graphics2D g2d = (Graphics2D)g.create(); + try { + g2d.setComposite(AlphaComposite.SrcOver); + g2d.setClip(waveClip); + xStart -= xStart % 4; + g2d.translate(xStart, y - 3); + UIUtil.drawImage(g2d, myImage, 0, 0, null); + } finally { + g2d.dispose(); + } + } + + public static WavePainter2D forColor(Color color) { + WavePainter2D painter = myPainters.get(color); + if (painter == null) { + painter = new WavePainter2D(color); + // creating a new Color instance, as the one passed as parameter can be mutable (JBColor) and shouldn't be used as a map key + //noinspection UseJBColor + myPainters.put(new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()), painter); + } + return painter; + } +}