From 070aba6377250295de14104ecf52b85bd6155b25 Mon Sep 17 00:00:00 2001 From: Sergey Malenkov Date: Wed, 9 Dec 2015 17:37:36 +0300 Subject: [PATCH] IDEA-148164 Add translucent thumb over error stripes in the project view tree --- .../util/ui/ButtonlessScrollBarUI.java | 16 +++- .../com/intellij/util/ui/RegionPainter.java | 71 +++++++++++++-- .../com/intellij/ui/stripe/ErrorStripe.java | 11 +-- .../ui/stripe/ErrorStripePainter.java | 22 +++-- .../ui/stripe/ExtraErrorStripePainter.java | 10 +- .../ui/stripe/TranslucencyThumbPainter.java | 91 +++++++++++++++++++ .../src/com/intellij/ui/stripe/Updater.java | 19 ++-- .../util/resources/misc/registry.properties | 2 +- 8 files changed, 198 insertions(+), 44 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/ui/stripe/TranslucencyThumbPainter.java diff --git a/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java index e168c002550f..bb80dc4bdf97 100644 --- a/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java +++ b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java @@ -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 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 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); + } } } diff --git a/platform/platform-api/src/com/intellij/util/ui/RegionPainter.java b/platform/platform-api/src/com/intellij/util/ui/RegionPainter.java index 14d6169dbe21..296f60ac8db2 100644 --- a/platform/platform-api/src/com/intellij/util/ui/RegionPainter.java +++ b/platform/platform-api/src/com/intellij/util/ui/RegionPainter.java @@ -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. + *

+ * 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 { + /** + * 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 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> 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> 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 { 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); diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripe.java b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripe.java index d3e9525c256b..8700e9416187 100644 --- a/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripe.java +++ b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripe.java @@ -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 { +public final class ErrorStripe implements Comparable { private final Color myColor; private final int myLayer; @@ -44,12 +41,6 @@ public final class ErrorStripe implements RegionPainter, Comparable 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(); diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripePainter.java b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripePainter.java index 144e8eec4ec3..21b12b3ba120 100644 --- a/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripePainter.java +++ b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripePainter.java @@ -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 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); } } } diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/ExtraErrorStripePainter.java b/platform/platform-impl/src/com/intellij/ui/stripe/ExtraErrorStripePainter.java index d73da2157043..2fc8f9fc3fd8 100644 --- a/platform/platform-impl/src/com/intellij/ui/stripe/ExtraErrorStripePainter.java +++ b/platform/platform-impl/src/com/intellij/ui/stripe/ExtraErrorStripePainter.java @@ -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); } } diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/TranslucencyThumbPainter.java b/platform/platform-impl/src/com/intellij/ui/stripe/TranslucencyThumbPainter.java new file mode 100644 index 000000000000..e649dc5de5e9 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/stripe/TranslucencyThumbPainter.java @@ -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 { + 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); + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/Updater.java b/platform/platform-impl/src/com/intellij/ui/stripe/Updater.java index b1d4c584c194..64f9915aab0d 100644 --- a/platform/platform-impl/src/com/intellij/ui/stripe/Updater.java +++ b/platform/platform-impl/src/com/intellij/ui/stripe/Updater.java @@ -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 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() { @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 implements Dis @Override public void run() { update(myPainter); - if (myPainter.isModified()) { - myScrollBar.repaint(); - } + myScrollBar.repaint(); } }); } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 73db72fa3f9e..7a81f3be41e6 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -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.