IDEA-148164 Add translucent thumb over error stripes in the project view tree

This commit is contained in:
Sergey Malenkov
2015-12-09 17:44:59 +03:00
parent b9db8496a3
commit 070aba6377
8 changed files with 198 additions and 44 deletions
@@ -637,9 +637,9 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI {
if (alwaysShowTrack() || myMouseOverScrollbarExpandLevel > 0) {
doPaintTrack(g, c, trackBounds);
}
RegionPainter painter = UIUtil.getClientProperty(c, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK);
RegionPainter<Object> painter = UIUtil.getClientProperty(c, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK);
if (painter != null) {
painter.paint(g, trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height);
painter.paint((Graphics2D)g, trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height, null);
}
}
@@ -698,9 +698,15 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI {
paintMacThumb(g, thumbBounds);
}
else {
g.translate(thumbBounds.x, thumbBounds.y);
paintMaxiThumb((Graphics2D)g, thumbBounds);
g.translate(-thumbBounds.x, -thumbBounds.y);
RegionPainter<Integer> painter = UIUtil.getClientProperty(scrollbar, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_MAXI_THUMB);
if (painter != null) {
painter.paint((Graphics2D)g, thumbBounds.x, thumbBounds.y, thumbBounds.width, thumbBounds.height, myThumbFadeColorShift);
}
else {
g.translate(thumbBounds.x, thumbBounds.y);
paintMaxiThumb((Graphics2D)g, thumbBounds);
g.translate(-thumbBounds.x, -thumbBounds.y);
}
}
}
@@ -16,35 +16,92 @@
package com.intellij.util.ui;
import com.intellij.openapi.util.Key;
import com.intellij.util.ui.UIUtil;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
/**
* The {@code RegionPainter} interface defines exactly one method, {@code paint}.
* It is used in situations where the developer can change the painting routine
* of a component without having to resort to subclassing the component.
* It is also generically useful when doing any form of painting delegation.
* <p>
* Painters are simply encapsulations of Java2D code and make it fairly trivial
* to reuse existing painters or to combine them together.
* Implementations of this interface are also trivial to write,
* such that if you can't find a painter that does what you need,
* you can write one with minimal effort.
*
* @author Sergey.Malenkov
* @see com.sun.java.swing.Painter
*/
public interface RegionPainter {
void paint(Graphics g, int x, int y, int width, int height);
public interface RegionPainter<T> {
/**
* Renders to the given {@link Graphics2D} object.
*
* @param g the {@code Graphics2D} object to render to
* @param x X position of the area to paint
* @param y Y position of the area to paint
* @param width width of the area to paint
* @param height height of the area to paint
* @param object an optional configuration parameter
*/
void paint(Graphics2D g, int x, int y, int width, int height, T object);
Key<RegionPainter> BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK = Key.create("BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK");
/**
* This key is used in the {@link ButtonlessScrollBarUI}
* to paint error stripes over the scrollbar track.
*
* @see ButtonlessScrollBarUI
* @see UIUtil#putClientProperty
*/
Key<RegionPainter<Object>> BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK = Key.create("BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK");
class Image implements RegionPainter {
/**
* This key is used in the {@link ButtonlessScrollBarUI}
* to paint the scrollbar maxi thumb.
*
* @see ButtonlessScrollBarUI
* @see UIUtil#putClientProperty
*/
Key<RegionPainter<Integer>> BUTTONLESS_SCROLL_BAR_UI_MAXI_THUMB = Key.create("BUTTONLESS_SCROLL_BAR_UI_MAXI_THUMB");
/**
* This class provides a caching functionality for a region painter.
*/
class Image implements RegionPainter<Object> {
private BufferedImage myImage;
/**
* This method is called for the cached image before {@code drawImage}.
* It should be overridden if the image must be updated without creating a new image.
*
* @param image the cached image to update
*/
protected void updateImage(BufferedImage image) {
}
/**
* This method is called if the cached image is invalidated or it's size is changed.
* It must be overridden to paint on newly created image.
*
* @param width width of the new image
* @param height height of the new image
* @return new {@link BufferedImage} object
*/
protected BufferedImage createImage(int width, int height) {
return UIUtil.createImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
/**
* This method is called to invalidate the cached image.
*/
protected void invalidate() {
myImage = null;
}
@Override
public void paint(Graphics g, int x, int y, int width, int height) {
public void paint(Graphics2D g, int x, int y, int width, int height, Object object) {
if (width > 0 && width > 0) {
if (myImage == null || width != myImage.getWidth() || height != myImage.getHeight()) {
myImage = createImage(width, height);
@@ -15,15 +15,12 @@
*/
package com.intellij.ui.stripe;
import com.intellij.util.ui.RegionPainter;
import java.awt.Color;
import java.awt.Graphics;
/**
* @author Sergey.Malenkov
*/
public final class ErrorStripe implements RegionPainter, Comparable<ErrorStripe> {
public final class ErrorStripe implements Comparable<ErrorStripe> {
private final Color myColor;
private final int myLayer;
@@ -44,12 +41,6 @@ public final class ErrorStripe implements RegionPainter, Comparable<ErrorStripe>
return myLayer;
}
@Override
public void paint(Graphics g, int x, int y, int width, int height) {
g.setColor(myColor);
g.fillRect(x, y, width, height);
}
@Override
public int hashCode() {
return myLayer + myColor.hashCode();
@@ -19,7 +19,6 @@ import com.intellij.util.ui.RegionPainter;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Iterator;
@@ -232,12 +231,12 @@ public class ErrorStripePainter extends RegionPainter.Image {
}
@Override
public void paint(Graphics g, int x, int y, int width, int height) {
public void paint(Graphics2D g, int x, int y, int width, int height, Object object) {
myImageY = y;
super.paint(g, x, y, width, height);
super.paint(g, x, y, width, height, object);
}
private abstract static class Value implements RegionPainter {
private abstract static class Value {
boolean myModified;
abstract boolean set(ErrorStripe stripe);
@@ -246,6 +245,8 @@ public class ErrorStripePainter extends RegionPainter.Image {
abstract ErrorStripe get();
abstract void paint(Graphics2D g, int x, int y, int width, int height);
void paint(Graphics2D g, int x, int y, int width, int height, boolean force) {
if (force || myModified) {
if (!force) {
@@ -284,12 +285,13 @@ public class ErrorStripePainter extends RegionPainter.Image {
}
@Override
public void paint(Graphics g, int x, int y, int width, int height) {
void paint(Graphics2D g, int x, int y, int width, int height) {
myModified = false;
if (myStripe != null) {
int thickness = myAlignment != null ? myMin + myGap : height;
y += getOffset(height, thickness);
myStripe.paint(g, x, y, width, thickness - myGap);
g.setColor(myStripe.getColor());
g.fillRect(x, y, width, thickness - myGap);
}
}
}
@@ -323,7 +325,7 @@ public class ErrorStripePainter extends RegionPainter.Image {
}
@Override
public void paint(Graphics g, int x, int y, int width, int height) {
void paint(Graphics2D g, int x, int y, int width, int height) {
myModified = false;
if (mySet != null) {
Iterator<ErrorStripe> iterator = mySet.iterator();
@@ -333,13 +335,15 @@ public class ErrorStripePainter extends RegionPainter.Image {
int count = Math.min(height / thickness, mySet.size());
y += getOffset(height, thickness * count);
do {
iterator.next().paint(g, x, y, width, thickness - myGap);
g.setColor(iterator.next().getColor());
g.fillRect(x, y, width, thickness - myGap);
y += thickness;
}
while (--count > 0 && iterator.hasNext());
}
else {
iterator.next().paint(g, x, y, width, thickness - myGap);
g.setColor(iterator.next().getColor());
g.fillRect(x, y, width, thickness - myGap);
}
}
}
@@ -15,7 +15,7 @@
*/
package com.intellij.ui.stripe;
import java.awt.Graphics;
import java.awt.Graphics2D;
/**
* @author Sergey.Malenkov
@@ -38,7 +38,7 @@ public class ExtraErrorStripePainter extends ErrorStripePainter {
}
public int getGroupGap() {
return myGroupGap;
return myGroupGap != null ? myGroupGap : getMinimalThickness();
}
public void setGroupGap(int gap) {
@@ -71,7 +71,7 @@ public class ExtraErrorStripePainter extends ErrorStripePainter {
}
@Override
public void paint(Graphics g, int x, int y, int width, int height) {
public void paint(Graphics2D g, int x, int y, int width, int height, Object object) {
int min = getMinimalThickness();
int gap = myGroupGap == null ? min : myGroupGap;
int pos = x;
@@ -81,7 +81,7 @@ public class ExtraErrorStripePainter extends ErrorStripePainter {
else {
x += gap + min;
}
myPainter.paint(g, pos, y, min, height);
super.paint(g, x, y, width - gap - min, height);
myPainter.paint(g, pos, y, min, height, object);
super.paint(g, x, y, width - gap - min, height, object);
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2000-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ui.stripe;
import com.intellij.openapi.Disposable;
import com.intellij.ui.Gray;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.RegionPainter;
import com.intellij.util.ui.UIUtil;
import javax.swing.JScrollBar;
import java.awt.*;
/**
* @author Sergey.Malenkov
*/
final class TranslucencyThumbPainter implements Disposable, RegionPainter<Integer> {
private static final BasicStroke BASIC_STROKE = new BasicStroke();
private final ErrorStripePainter myPainter;
private final JScrollBar myScrollBar;
TranslucencyThumbPainter(ErrorStripePainter painter, JScrollBar bar) {
myPainter = painter;
myScrollBar = bar;
UIUtil.putClientProperty(myScrollBar, BUTTONLESS_SCROLL_BAR_UI_MAXI_THUMB, this);
}
@Override
public void dispose() {
UIUtil.putClientProperty(myScrollBar, BUTTONLESS_SCROLL_BAR_UI_MAXI_THUMB, null);
}
@Override
public void paint(Graphics2D g, int x, int y, int width, int height, Integer object) {
g = (Graphics2D)g.create(x, y, width, height);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
g.setStroke(BASIC_STROKE);
boolean dark = UIUtil.isUnderDarcula();
int offset = object != null ? object + 11 : 11;
Color start = getColor(dark ? 95 + offset : 251 - offset);
Color stop = getColor(dark ? 80 + offset : 215 - offset);
Paint paint;
if (Adjustable.VERTICAL == myScrollBar.getOrientation()) {
x = 2;
y = 1;
width -= 3;
height -= 2;
if (myPainter instanceof ExtraErrorStripePainter) {
ExtraErrorStripePainter extra = (ExtraErrorStripePainter)myPainter;
int gap = extra.getMinimalThickness() - 1;
x += gap;
width -= gap;
}
paint = UIUtil.getGradientPaint(x, 0, start, x + width, 0, stop);
}
else {
x = 1;
y = 2;
width -= 2;
height -= 3;
paint = UIUtil.getGradientPaint(0, y, start, 0, y + height, stop);
}
int arc = JBUI.scale(3);
g.setPaint(paint);
g.fillRoundRect(x + 1, y + 1, width - 2, height - 2, arc, arc);
g.setColor(getColor(dark ? 85 : 201));
g.drawRoundRect(x, y, width - 1, height - 1, arc, arc);
g.dispose();
}
private static Color getColor(int gray) {
return Gray.get(gray < 0 ? 0 : gray > 255 ? 255 : gray);
}
}
@@ -20,6 +20,7 @@ import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.actionSystem.ShortcutSet;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.RegionPainter;
@@ -31,7 +32,7 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
@@ -60,15 +61,21 @@ public abstract class Updater<Painter extends ErrorStripePainter> implements Dis
myScrollBar.addMouseListener(myMouseAdapter);
myScrollBar.addMouseMotionListener(myMouseAdapter);
myQueue = new MergingUpdateQueue("ErrorStripeUpdater", 100, true, myScrollBar, this);
UIUtil.putClientProperty(myScrollBar, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK, new RegionPainter() {
UIUtil.putClientProperty(myScrollBar, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK, new RegionPainter<Object>() {
@Override
public void paint(Graphics g, int x, int y, int width, int height) {
public void paint(Graphics2D g, int x, int y, int width, int height, Object object) {
DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
myPainter.setMinimalThickness(settings == null ? 2 : Math.min(settings.ERROR_STRIPE_MARK_MIN_HEIGHT, JBUI.scale(4)));
myPainter.setErrorStripeGap(Registry.intValue("error.stripe.gap", 0));
myPainter.paint(g, x, y, width, height);
if (myPainter instanceof ExtraErrorStripePainter) {
ExtraErrorStripePainter extra = (ExtraErrorStripePainter)myPainter;
extra.setGroupSwap(!myScrollBar.getComponentOrientation().isLeftToRight());
}
myPainter.paint(g, x, y, width, height, object);
}
});
Disposer.register(this, new TranslucencyThumbPainter(myPainter, myScrollBar));
Disposer.register(this, new TranslucencyThumbPainter(null, pane.getHorizontalScrollBar()));
}
@Override
@@ -141,9 +148,7 @@ public abstract class Updater<Painter extends ErrorStripePainter> implements Dis
@Override
public void run() {
update(myPainter);
if (myPainter.isModified()) {
myScrollBar.repaint();
}
myScrollBar.repaint();
}
});
}
@@ -606,7 +606,7 @@ editor.config.stop.at.project.root.description=Stops searching for .editorconfig
editor.bold.underline.height=2
editor.bold.underline.height.description=Underline height for EffectType.BOLD_LINE_UNDERSCORE
error.stripe.enabled=false
error.stripe.enabled=true
error.stripe.gap=1
error.stripe.gap.description=Minimal space between error stripe.