mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Add EffectPainter2D and WavePainter2D versions
- also fix EffectPainterTest
This commit is contained in:
@@ -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<Font> {
|
||||
*/
|
||||
@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<Font> {
|
||||
*/
|
||||
@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<Font> {
|
||||
*/
|
||||
@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<Font> {
|
||||
*/
|
||||
@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<Font> {
|
||||
*/
|
||||
@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<Paint> {
|
||||
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<Long, BufferedImage> myNormalCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, BufferedImage> 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<Long, BufferedImage> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Font> {
|
||||
/**
|
||||
* @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<Paint> {
|
||||
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<Long, BufferedImage> myNormalCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, BufferedImage> 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<Long, BufferedImage> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* 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<Color, WavePainter> myPainters = new HashMap<Color, WavePainter>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* 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<Color, WavePainter2D> myPainters = new HashMap<Color, WavePainter2D>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user