2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.ui.paint;
18 import com.intellij.openapi.util.registry.Registry;
19 import com.intellij.util.JBHiDPIScaledImage;
20 import com.intellij.util.ui.JBUI;
21 import com.intellij.util.ui.RegionPainter;
22 import com.intellij.util.ui.UIUtil;
23 import com.intellij.util.ui.WavePainter;
26 import java.awt.font.LineMetrics;
27 import java.awt.geom.*;
28 import java.awt.image.BufferedImage;
29 import java.util.concurrent.ConcurrentHashMap;
32 * @author Sergey.Malenkov
34 public enum EffectPainter implements RegionPainter<Font> {
36 * @see com.intellij.openapi.editor.markup.EffectType#LINE_UNDERSCORE
40 * Draws a horizontal line under a text.
42 * @param g the {@code Graphics2D} object to render to
43 * @param x text position
44 * @param y text baseline
45 * @param width text width
46 * @param height available space under text
47 * @param font optional font to calculate line metrics
50 public void paint(Graphics2D g, int x, int y, int width, int height, Font font) {
51 if (!Registry.is("ide.text.effect.new")) {
52 g.drawLine(x, y + 1, x + width, y + 1);
55 paintUnderline(g, x, y, width, height, font, 1, this);
60 * @see com.intellij.openapi.editor.markup.EffectType#BOLD_LINE_UNDERSCORE
62 BOLD_LINE_UNDERSCORE {
64 * Draws a bold horizontal line under a text.
66 * @param g the {@code Graphics2D} object to render to
67 * @param x text position
68 * @param y text baseline
69 * @param width text width
70 * @param height available space under text
71 * @param font optional font to calculate line metrics
74 public void paint(Graphics2D g, int x, int y, int width, int height, Font font) {
75 if (!Registry.is("ide.text.effect.new")) {
76 int h = JBUI.scale(Registry.intValue("editor.bold.underline.height", 2));
77 g.fillRect(x, y, width, h);
80 paintUnderline(g, x, y, width, height, font, 2, this);
85 * @see com.intellij.openapi.editor.markup.EffectType#BOLD_DOTTED_LINE
87 BOLD_DOTTED_UNDERSCORE {
89 * Draws a bold horizontal line of dots under a text.
91 * @param g the {@code Graphics2D} object to render to
92 * @param x text position
93 * @param y text baseline
94 * @param width text width
95 * @param height available space under text
96 * @param font optional font to calculate line metrics
99 public void paint(Graphics2D g, int x, int y, int width, int height, Font font) {
100 paintUnderline(g, x, y, width, height, font, 2, this);
104 * @see com.intellij.openapi.editor.markup.EffectType#WAVE_UNDERSCORE
108 * Draws a horizontal wave under a text.
110 * @param g the {@code Graphics2D} object to render to
111 * @param x text position
112 * @param y text baseline
113 * @param width text width
114 * @param height available space under text
115 * @param font optional font to calculate line metrics
118 public void paint(Graphics2D g, int x, int y, int width, int height, Font font) {
119 if (!Registry.is("ide.text.effect.new")) {
120 WavePainter.forColor(g.getColor()).paint(g, x, x + width, y + height);
122 else if (Registry.is("ide.text.effect.new.metrics")) {
123 paintUnderline(g, x, y, width, height, font, 3, this);
125 else if (width > 0 && height > 0) {
126 Cached.WAVE_UNDERSCORE.paint(g, x, y, width, height, null);
131 * @see com.intellij.openapi.editor.markup.EffectType#STRIKEOUT
135 * Draws a horizontal line through a text.
137 * @param g the {@code Graphics2D} object to render to
138 * @param x text position
139 * @param y text baseline
140 * @param width text width
141 * @param height text height
142 * @param font optional font to calculate line metrics
145 public void paint(Graphics2D g, int x, int y, int width, int height, Font font) {
146 if (width > 0 && height > 0) {
147 if (!Registry.is("ide.text.effect.new.metrics")) {
148 drawLineCentered(g, x, y - height, width, height, 1, this);
151 if (font == null) font = g.getFont();
152 LineMetrics metrics = font.getLineMetrics("", g.getFontRenderContext());
153 int offset = (int)(0.5 - metrics.getStrikethroughOffset());
154 int thickness = Math.max(1, (int)(0.5 + metrics.getStrikethroughThickness()));
155 drawLine(g, x, y - offset, width, thickness, this);
161 private static int getMaxHeight(int height) {
162 return height > 7 && Registry.is("ide.text.effect.new.scale") ? height >> 1 : 3;
165 private static void paintUnderline(Graphics2D g, int x, int y, int width, int height, Font font, int thickness, EffectPainter painter) {
166 if (width > 0 && height > 0) {
167 if (Registry.is("ide.text.effect.new.metrics")) {
168 if (font == null) font = g.getFont();
169 LineMetrics metrics = font.getLineMetrics("", g.getFontRenderContext());
170 thickness = Math.max(thickness, (int)(0.5 + thickness * metrics.getUnderlineThickness()));
171 int offset = Math.min(height - thickness, Math.max(1, (int)(0.5 + metrics.getUnderlineOffset())));
173 offset = height > 3 ? 1 : 0;
174 thickness = height - offset;
176 drawLine(g, x, y + offset, width, thickness, painter);
180 int max = getMaxHeight(height);
183 if (thickness > 1 && height > 3) {
184 thickness = JBUI.scale(thickness);
187 drawLineCentered(g, x, y, width, height, thickness, painter);
192 private static void drawLineCentered(Graphics2D g, int x, int y, int width, int height, int thickness, EffectPainter painter) {
193 int offset = height - thickness;
195 y += offset - (offset >> 1);
198 drawLine(g, x, y, width, height, painter);
201 private static void drawLine(Graphics2D g, int x, int y, int width, int height, EffectPainter painter) {
202 if (painter == BOLD_DOTTED_UNDERSCORE) {
203 int dx = (x % height + height) % height;
205 int dw = (w % height + height) % height;
206 Cached.BOLD_DOTTED_UNDERSCORE.paint(g, x - dx, y, dw == 0 ? w : w - dw + height, height, null);
208 else if (painter == WAVE_UNDERSCORE) {
209 Cached.WAVE_UNDERSCORE.paint(g, x, y, width, height, null);
212 g.fillRect(x, y, width, height);
216 private enum Cached implements RegionPainter<Paint> {
217 BOLD_DOTTED_UNDERSCORE {
219 int getPeriod(int height) {
224 void paintImage(Graphics2D g, int width, int height, int period) {
225 Integer round = period <= 2 && !UIUtil.isRetina(g) ? null : period;
226 for (int dx = 0; dx < width; dx += period + period) {
227 RectanglePainter.FILL.paint(g, dx, 0, period, period, round);
232 private final BasicStroke THIN_STROKE = new BasicStroke(.7f);
235 int getPeriod(int height) {
236 return Math.max((Registry.is("ide.text.effect.new.metrics") ? height : getMaxHeight(height)) - 1, 1);
240 void paintImage(Graphics2D g, int width, int height, int period) {
242 double lower = height - 1;
243 double upper = lower - period;
244 if (Registry.is("ide.text.effect.new.metrics")) {
246 float fix = height / 3f;
247 g.setStroke(new BasicStroke(fix));
255 if (g.getClass().getName().equals("com.intellij.util.HiDPIScaledGraphics")) {
260 Path2D path = new Path2D.Double();
261 path.moveTo(dx, lower);
263 g.setStroke(THIN_STROKE);
265 path.lineTo(dx += period, upper);
266 path.lineTo(dx += period, lower);
270 double size = (double)period / 2;
271 double prev = dx - size / 2;
272 double center = (upper + lower) / 2;
274 path.quadTo(prev += size, lower, dx += size, center);
275 path.quadTo(prev += size, upper, dx += size, upper);
276 path.quadTo(prev += size, upper, dx += size, center);
277 path.quadTo(prev += size, lower, dx += size, lower);
280 path.lineTo((double)width, lower);
281 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
286 // we should not recalculate caches when IDEA is on Retina and non-Retina
287 private final ConcurrentHashMap<Long, BufferedImage> myNormalCache = new ConcurrentHashMap<>();
288 private final ConcurrentHashMap<Long, BufferedImage> myRetinaCache = new ConcurrentHashMap<>();
290 abstract int getPeriod(int height);
292 abstract void paintImage(Graphics2D g, int width, int height, int period);
294 void paintImage(Graphics2D g, Paint paint, int width, int height, int period) {
297 paintImage(g, width, height, period);
304 BufferedImage getImage(Graphics2D g, Color color, int height) {
305 Long key = color.getRGB() ^ ((long)height << 32);
306 ConcurrentHashMap<Long, BufferedImage> cache = UIUtil.isRetina(g) ? myRetinaCache : myNormalCache;
307 return cache.computeIfAbsent(key, k -> createImage(g, color, height));
310 BufferedImage createImage(Graphics2D g, Paint paint, int height) {
311 int period = getPeriod(height);
312 int width = period << (paint instanceof Color ? 8 : 1);
313 BufferedImage image = UIUtil.createImageForGraphics(g, width, height, BufferedImage.TYPE_INT_ARGB);
314 paintImage(image.createGraphics(), paint, width, height, period);
319 public void paint(Graphics2D g, int x, int y, int width, int height, Paint paint) {
320 if (paint == null) paint = g.getPaint();
321 g = (Graphics2D)g.create(x, y, width, height);
322 g.setComposite(AlphaComposite.SrcOver);
323 BufferedImage image = paint instanceof Color ? getImage(g, (Color)paint, height) : createImage(g, paint, height);
324 int period = image.getWidth(null);
325 if (image instanceof JBHiDPIScaledImage) period /= 2;
326 int offset = (x % period + period) % period; // normalize
327 for (int dx = -offset; dx < width; dx += period) {
328 UIUtil.drawImage(g, image, dx, 0, null);