/* * Copyright (c) 2005-2016 Substance Kirill Grouchnikov. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of Substance Kirill Grouchnikov nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.pushingpixels.substance.internal.painter; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.LinearGradientPaint; import java.awt.geom.Line2D; import java.awt.image.BufferedImage; import java.util.Collection; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.JSeparator; import javax.swing.SwingConstants; import org.pushingpixels.lafwidget.contrib.intellij.UIUtil; import org.pushingpixels.substance.api.ColorSchemeAssociationKind; import org.pushingpixels.substance.api.ComponentState; import org.pushingpixels.substance.api.DecorationAreaType; import org.pushingpixels.substance.api.SubstanceColorScheme; import org.pushingpixels.substance.api.SubstanceLookAndFeel; import org.pushingpixels.substance.api.SubstanceSkin; import org.pushingpixels.substance.internal.utils.HashMapKey; import org.pushingpixels.substance.internal.utils.LazyResettableHashMap; import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities; import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities; import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities; import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils; /** * Contains utility methods related to painting separators. This class is for * internal use only. * * @author Kirill Grouchnikov */ public class SeparatorPainterUtils { /** * Cached images of separators. */ private static LazyResettableHashMap<BufferedImage> cached = new LazyResettableHashMap<BufferedImage>( "SeparatorPainterUtils"); /** * Paints a separator. * * @param c * Component. * @param graphics * Graphics context. * @param width * Separator width. * @param height * Separator height. * @param orientation * Separator orientation. */ public static void paintSeparator(Component c, Graphics graphics, int width, int height, int orientation) { paintSeparator(c, graphics, width, height, orientation, true, 10); } /** * Paints a separator. * * @param c * Component. * @param graphics * Graphics context. * @param scheme * Color scheme. * @param width * Separator width. * @param height * Separator height. * @param orientation * Separator orientation. * @param hasShadow * If <code>true</code>, the separator painting will have shadow. * @param maxGradLength * Specifies the maximum pixel length of "ramp" portions of the * separator. The ramp portions are located on separator ends and * allow providing a faded appearance on those ends. */ public static void paintSeparator(Component c, Graphics graphics, int width, int height, int orientation, boolean hasShadow, int maxGradLength) { paintSeparator(c, graphics, width, height, orientation, hasShadow, maxGradLength, maxGradLength, false); } /** * Paints a separator. * * @param c * Component. * @param g * Graphics context. * @param width * Separator width. * @param height * Separator height. * @param orientation * Separator orientation. * @param hasShadow * If <code>true</code>, the separator painting will have shadow. * @param maxGradLengthStart * Specifies the maximum pixel length of the starting "ramp" * portion of the separator. The starting ramp portion is located * on top / left separator end and allows providing a faded * appearance on that end. * @param maxGradLengthEnd * Specifies the maximum pixel length of the ending "ramp" * portion of the separator. The ending ramp portion is located * on bottom / right separator end and allows providing a faded * appearance on that end. * @param toEnforceAlphaColors * If <code>true</code>, the fade sequences will always use alpha * colors. This may affect the performance. */ public static void paintSeparator(Component c, Graphics g, int width, int height, int orientation, boolean hasShadow, int maxGradLengthStart, int maxGradLengthEnd, boolean toEnforceAlphaColors) { SubstanceColorScheme compScheme = null; Component parent = c.getParent(); boolean isParentAPopup = (parent instanceof JPopupMenu) || ((parent instanceof JComponent) && ((JComponent) parent).getClientProperty( DecorationPainterUtils.POPUP_INVOKER_LINK) != null); if (isParentAPopup) { // For separators in popups, first see if we have a color // scheme explicitly registered for the SEPARATOR association kind. compScheme = SubstanceColorSchemeUtilities.getDirectColorScheme(c, ColorSchemeAssociationKind.SEPARATOR, ComponentState.ENABLED); if (compScheme == null) { // Then get a background color scheme associated with the // decoration type // of that separator compScheme = SubstanceCoreUtilities.getSkin(c) .getBackgroundColorScheme(SubstanceLookAndFeel.getDecorationType(c)); } } if (compScheme == null) { // And finally, get the separator's color scheme via the regular // route that includes fall back in case there is no explicitly registered // color scheme for the SEPARATOR association kind. compScheme = SubstanceColorSchemeUtilities.getColorScheme(c, ColorSchemeAssociationKind.SEPARATOR, ComponentState.ENABLED); } paintSeparator(c, g, compScheme, width, height, orientation, hasShadow, maxGradLengthStart, maxGradLengthEnd, toEnforceAlphaColors); } /** * Paints a separator. * * @param c * Component. * @param g * Graphics context. * @param scheme * Color scheme. * @param width * Separator width. * @param height * Separator height. * @param orientation * Separator orientation. * @param hasShadow * If <code>true</code>, the separator painting will have shadow. * @param maxGradLengthStart * Specifies the maximum pixel length of the starting "ramp" * portion of the separator. The starting ramp portion is located * on top / left separator end and allows providing a faded * appearance on that end. * @param maxGradLengthEnd * Specifies the maximum pixel length of the ending "ramp" * portion of the separator. The ending ramp portion is located * on bottom / right separator end and allows providing a faded * appearance on that end. * @param toEnforceAlphaColors * If <code>true</code>, the fade sequences will always use alpha * colors. This may affect the performance. */ public static void paintSeparator(Component c, Graphics g, SubstanceColorScheme scheme, int width, int height, int orientation, boolean hasShadow, int maxGradLengthStart, int maxGradLengthEnd, boolean toEnforceAlphaColors) { DecorationAreaType decorationAreaType = SubstanceLookAndFeel.getDecorationType(c); SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c); // use alpha colors when the control is in a painted decoration area // (where skin can use different background colors) or in a decoration // area that has overlays. boolean toUseAlphaColors = ((decorationAreaType == null) || (decorationAreaType == DecorationAreaType.NONE)) ? false : skin.isRegisteredAsDecorationArea(decorationAreaType) || (skin.getOverlayPainters(decorationAreaType).size() > 0); toUseAlphaColors = toUseAlphaColors || toEnforceAlphaColors; Color backgrFill = SubstanceColorUtilities.getBackgroundFillColor(c); int fontSize = SubstanceSizeUtils.getComponentFontSize(c); float borderStrokeWidth = SubstanceSizeUtils.getBorderStrokeWidth(); if ((orientation == JSeparator.HORIZONTAL) && (height == 0)) { height = (int) Math.ceil(2.0 * borderStrokeWidth); } if ((orientation == JSeparator.VERTICAL) && (width == 0)) { width = (int) Math.ceil(2.0 * borderStrokeWidth); } if ((width == 0) || (height == 0)) return; HashMapKey key = SubstanceCoreUtilities.getHashKey(fontSize, scheme.getDisplayName(), width, height, orientation, hasShadow, maxGradLengthStart, maxGradLengthEnd, toUseAlphaColors, backgrFill.getRGB()); BufferedImage singleLine = cached.get(key); if (singleLine == null) { singleLine = SubstanceCoreUtilities.getBlankImage(width, height); Graphics2D graphics = singleLine.createGraphics(); Color foreLight = getSeparatorLightColor(scheme); Color foreDark = getSeparatorDarkColor(scheme); Color back = getSeparatorShadowColor(scheme); Color foreLight12 = toUseAlphaColors ? SubstanceColorUtilities.getAlphaColor(foreLight, 32) : SubstanceColorUtilities.getInterpolatedColor(foreLight, backgrFill, 0.12f); Color foreDark95 = toUseAlphaColors ? SubstanceColorUtilities.getAlphaColor(foreDark, 240) : SubstanceColorUtilities.getInterpolatedColor(foreDark, backgrFill, 0.95f); Color back12 = toUseAlphaColors ? SubstanceColorUtilities.getAlphaColor(back, 32) : SubstanceColorUtilities.getInterpolatedColor(back, backgrFill, 0.12f); Color back95 = toUseAlphaColors ? SubstanceColorUtilities.getAlphaColor(back, 240) : SubstanceColorUtilities.getInterpolatedColor(back, backgrFill, 0.95f); graphics.setStroke(new BasicStroke(borderStrokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); if (orientation == JSeparator.VERTICAL) { int gradStart = Math.min(maxGradLengthStart, height / 2); int gradEnd = Math.min(maxGradLengthEnd, height / 2); float regularX = Math.max(0, width / 2.0f - borderStrokeWidth); graphics.setPaint(new GradientPaint(0, 0, foreLight12, 0, gradStart, foreDark95)); graphics.draw(new Line2D.Float(regularX, 0, regularX, gradStart)); graphics.setColor(foreDark95); graphics.draw(new Line2D.Float(regularX, gradStart, regularX, height - gradEnd)); graphics.setPaint( new GradientPaint(0, height - gradEnd, foreDark95, 0, height, foreLight12)); graphics.draw(new Line2D.Float(regularX, height - gradEnd, regularX, height)); if (hasShadow) { float shadowX = regularX + borderStrokeWidth; graphics.setPaint(new GradientPaint(0, 0, back12, 0, gradStart, back95)); graphics.draw(new Line2D.Float(shadowX, 0, shadowX, gradStart)); graphics.setColor(back95); graphics.draw(new Line2D.Float(shadowX, gradStart, shadowX, height - gradEnd)); graphics.setPaint( new GradientPaint(0, height - gradEnd, back95, 0, height, back12)); graphics.draw(new Line2D.Float(shadowX, height - gradEnd, shadowX, height)); } } else { // HORIZONTAL int gradStart = Math.min(maxGradLengthStart, width / 2); int gradEnd = Math.min(maxGradLengthEnd, width / 2); graphics.translate(0, Math.max(0, height / 2 - 1)); float regularY = Math.max(0, height / 2.0f - borderStrokeWidth); graphics.setPaint(new GradientPaint(0, 0, foreLight12, gradStart, 0, foreDark95)); graphics.draw(new Line2D.Float(0, regularY, gradStart, regularY)); graphics.setColor(foreDark95); graphics.draw(new Line2D.Float(gradStart, regularY, width - gradEnd, regularY)); graphics.setPaint( new GradientPaint(width - gradEnd, 0, foreDark95, width, 0, foreLight12)); graphics.draw(new Line2D.Float(width - gradEnd, regularY, width, regularY)); if (hasShadow) { float shadowY = regularY + borderStrokeWidth; graphics.setPaint(new GradientPaint(0, 0, back12, gradStart, 0, back95)); graphics.draw(new Line2D.Float(0, shadowY, gradStart, shadowY)); graphics.setColor(back95); graphics.draw(new Line2D.Float(gradStart, shadowY, width - gradEnd, shadowY)); graphics.setPaint( new GradientPaint(width - gradEnd, 0, back95, width, 0, back12)); graphics.draw(new Line2D.Float(width - gradEnd, shadowY, width, shadowY)); } } graphics.dispose(); cached.put(key, singleLine); } Graphics2D g2d = (Graphics2D) g.create(); int scaleFactor = UIUtil.getScaleFactor(); g2d.drawImage(singleLine, 0, 0, singleLine.getWidth() / scaleFactor, singleLine.getHeight() / scaleFactor, null); g2d.dispose(); } public static Color getSeparatorShadowColor(SubstanceColorScheme scheme) { return scheme.isDark() ? scheme.getDarkColor() : scheme.getUltraLightColor(); } public static Color getSeparatorDarkColor(SubstanceColorScheme scheme) { return scheme.isDark() ? scheme.getExtraLightColor() : SubstanceColorUtilities.getInterpolatedColor(scheme.getMidColor(), scheme.getDarkColor(), 0.4f); } public static Color getSeparatorLightColor(SubstanceColorScheme scheme) { return scheme.isDark() ? scheme.getLightColor() : SubstanceColorUtilities.getInterpolatedColor(scheme.getLightColor(), scheme.getDarkColor(), 0.8f); } /** * Paints vertical separator lines. * * @param g * Graphics context. * @param c * Component. * @param scheme * Color scheme for painting the vertical separator lines. * @param y * The top Y coordinate of the lines. * @param x * The X coordinates of the lines. * @param height * The height of the lines. * @param fadeStartFraction * The start fraction of the fade out sequence. */ public static void paintVerticalLines(Graphics g, Component c, SubstanceColorScheme scheme, int y, Collection<Integer> x, int height, float fadeStartFraction) { int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c); Color backgrFill = SubstanceColorUtilities.getBackgroundFillColor(c); HashMapKey key = SubstanceCoreUtilities.getHashKey(componentFontSize, scheme.getDisplayName(), 0, height, SwingConstants.VERTICAL, true, 0.0, fadeStartFraction, backgrFill.getRGB()); float borderStrokeWidth = SubstanceSizeUtils.getBorderStrokeWidth(); BufferedImage singleLine = cached.get(key); if (singleLine == null) { singleLine = SubstanceCoreUtilities .getBlankImage(Math.max(2, (int) Math.ceil(2.0 * borderStrokeWidth)), height); Graphics2D graphics = singleLine.createGraphics(); Color foreLight = getSeparatorLightColor(scheme); Color foreDark = getSeparatorDarkColor(scheme); Color back = getSeparatorShadowColor(scheme); graphics.setStroke(new BasicStroke(borderStrokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); Color foreLight12 = SubstanceColorUtilities.getInterpolatedColor(foreLight, backgrFill, 0.12f); Color foreDark95 = SubstanceColorUtilities.getInterpolatedColor(foreDark, backgrFill, 0.95f); Color back12 = SubstanceColorUtilities.getInterpolatedColor(back, backgrFill, 0.12f); Color back95 = SubstanceColorUtilities.getInterpolatedColor(back, backgrFill, 0.95f); LinearGradientPaint forePaint = new LinearGradientPaint(0, 0, 0, height, new float[] { 0.0f, fadeStartFraction, 1.0f }, new Color[] { foreDark95, foreDark95, foreLight12 }); graphics.setPaint(forePaint); graphics.translate(borderStrokeWidth / 2, 0); graphics.draw( new Line2D.Float(borderStrokeWidth / 2, 0, borderStrokeWidth / 2, height)); LinearGradientPaint backPaint = new LinearGradientPaint(0, 0, 0, height, new float[] { 0.0f, fadeStartFraction, 1.0f }, new Color[] { back95, back95, back12 }); graphics.setPaint(backPaint); graphics.draw(new Line2D.Float(3 * borderStrokeWidth / 2, 0, 3 * borderStrokeWidth / 2, height)); graphics.dispose(); cached.put(key, singleLine); } Graphics2D g2d = (Graphics2D) g.create(); int scaleFactor = UIUtil.getScaleFactor(); for (int lineX : x) { g2d.drawImage(singleLine, lineX, y, singleLine.getWidth() / scaleFactor, singleLine.getHeight() / scaleFactor, null); } g2d.dispose(); } /** * Paints horizontal separator lines. * * @param g * Graphics context. * @param c * Component. * @param scheme * Color scheme for painting the horizontal separator lines. * @param x * The left X coordinate of the lines. * @param y * The Y coordinates of the lines. * @param width * The width of the lines. * @param fadeStartFraction * The start fraction of the fade out sequence. * @param isLtr * If <code>true</code>, the lines are left-to-right and the fade * out is on the right side. Otherwise, the fade out is on the * left side. */ public static void paintHorizontalLines(Graphics g, Component c, SubstanceColorScheme scheme, int x, Collection<Integer> y, int width, float fadeStartFraction, boolean isLtr) { int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c); Color backgrFill = SubstanceColorUtilities.getBackgroundFillColor(c); HashMapKey key = SubstanceCoreUtilities.getHashKey(componentFontSize, scheme.getDisplayName(), width, 0, SwingConstants.VERTICAL, true, 0.0, fadeStartFraction, isLtr, backgrFill.getRGB()); float borderStrokeWidth = SubstanceSizeUtils.getBorderStrokeWidth(); BufferedImage singleLine = cached.get(key); if (singleLine == null) { singleLine = SubstanceCoreUtilities.getBlankImage(width, Math.max(2, (int) Math.ceil(2.0 * borderStrokeWidth))); Graphics2D graphics = singleLine.createGraphics(); Color foreLight = getSeparatorLightColor(scheme); Color foreDark = getSeparatorDarkColor(scheme); Color back = getSeparatorShadowColor(scheme); graphics.setStroke(new BasicStroke(borderStrokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); Color foreLight12 = SubstanceColorUtilities.getInterpolatedColor(foreLight, backgrFill, 0.12f); Color foreDark95 = SubstanceColorUtilities.getInterpolatedColor(foreDark, backgrFill, 0.95f); Color back12 = SubstanceColorUtilities.getInterpolatedColor(back, backgrFill, 0.12f); Color back95 = SubstanceColorUtilities.getInterpolatedColor(back, backgrFill, 0.95f); LinearGradientPaint forePaint = new LinearGradientPaint(0, 0, width, 0, new float[] { 0.0f, fadeStartFraction, 1.0f }, new Color[] { isLtr ? foreDark95 : foreLight12, foreDark95, isLtr ? foreLight12 : foreDark95 }); graphics.setPaint(forePaint); graphics.draw(new Line2D.Float(0, borderStrokeWidth / 2, width, borderStrokeWidth / 2)); LinearGradientPaint backPaint = new LinearGradientPaint(0, 9, width, 0, new float[] { 0.0f, fadeStartFraction, 1.0f }, new Color[] { isLtr ? back95 : back12, back95, isLtr ? back12 : back95 }); graphics.setPaint(backPaint); graphics.draw(new Line2D.Float(0, 3 * borderStrokeWidth / 2, width, 3 * borderStrokeWidth / 2)); graphics.dispose(); cached.put(key, singleLine); } Graphics2D g2d = (Graphics2D) g.create(); int scaleFactor = UIUtil.getScaleFactor(); for (int lineY : y) { g2d.drawImage(singleLine, x, lineY, singleLine.getWidth() / scaleFactor, singleLine.getHeight() / scaleFactor, null); } g2d.dispose(); } }