/*
* 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.utils;
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractButton;
import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.contrib.intellij.UIUtil;
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
import org.pushingpixels.substance.api.ComponentState;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceConstants;
import org.pushingpixels.substance.api.SubstanceConstants.Side;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
import org.pushingpixels.substance.api.shaper.RectangularButtonShaper;
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
/**
* Delegate class for painting backgrounds of buttons in <b>Substance </b> look
* and feel. This class is <b>for internal use only</b>.
*
* @author Kirill Grouchnikov
*/
public class PairwiseButtonBackgroundDelegate {
/**
* Cache for background images for pairwise backgrounds. Each time
* {@link #getPairwiseBackground(AbstractButton, int, int, Side)} is called,
* it checks <code>this</code> map to see if it already contains such
* background. If so, the background from the map is returned.
*/
private static LazyResettableHashMap<BufferedImage> pairwiseBackgrounds =
new LazyResettableHashMap<BufferedImage>("PairwiseButtonBackgroundDelegate");
/**
* Paints background image for the specified button in button pair (such as
* scrollbar arrows, for example).
*
* @param g
* Graphics context.
* @param button
* Button.
* @param painter
* Gradient painter.
* @param width
* Button width.
* @param height
* Button height.
* @param side
* Button orientation.
* @param toIgnoreOpenSides
* If <code>true</code>, the open side setting (controlled by the
* {@link SubstanceLookAndFeel#BUTTON_OPEN_SIDE_PROPERTY} is
* ignored.
*/
public static void updatePairwiseBackground(Graphics g,
AbstractButton button, int width, int height,
boolean toIgnoreOpenSides) {
if (SubstanceCoreUtilities.isButtonNeverPainted(button))
return;
SubstanceButtonShaper shaper = SubstanceCoreUtilities.getButtonShaper(button);
TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button.getUI();
StateTransitionTracker stateTransitionTracker = transitionAwareUI.getTransitionTracker();
StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
.getModelStateInfo();
ComponentState currState = modelStateInfo.getCurrModelState();
SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
.getColorScheme(button, currState);
SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
.getColorScheme(button, ColorSchemeAssociationKind.BORDER,
currState);
SubstanceFillPainter fillPainter = SubstanceCoreUtilities.isSpinnerButton(button)
? MatteFillPainter.INSTANCE
: SubstanceImageCreator.SimplisticSoftBorderReverseFillPainter.INSTANCE;
Set<Side> openSides = toIgnoreOpenSides ? EnumSet.noneOf(Side.class)
: SubstanceCoreUtilities.getSides(button,
SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY);
boolean needsRotation = (openSides != null) &&
(openSides.contains(Side.BOTTOM) || openSides.contains(Side.TOP));
BufferedImage baseLayer = getPairwiseFullAlphaBackground(button,
fillPainter, shaper, width, height, baseFillScheme,
baseBorderScheme, toIgnoreOpenSides, needsRotation);
BufferedImage fullOpacity = null;
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates =
modelStateInfo.getStateContributionMap();
int scaleFactor = UIUtil.getScaleFactor();
if (currState.isDisabled() || (activeStates.size() == 1)) {
fullOpacity = baseLayer;
} else {
fullOpacity = SubstanceCoreUtilities.getBlankImage(baseLayer
.getWidth(), baseLayer.getHeight());
Graphics2D g2fullOpacity = fullOpacity.createGraphics();
// draw the base layer
g2fullOpacity.drawImage(baseLayer, 0, 0, baseLayer.getWidth() / scaleFactor,
baseLayer.getHeight() / scaleFactor, null);
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry :
activeStates.entrySet()) {
ComponentState activeState = activeEntry.getKey();
if (activeState == currState)
continue;
float contribution = activeEntry.getValue().getContribution();
if (contribution == 0.0f)
continue;
SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
.getColorScheme(button, activeState);
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
.getColorScheme(button,
ColorSchemeAssociationKind.BORDER, activeState);
BufferedImage layer = getPairwiseFullAlphaBackground(button,
fillPainter, shaper, width, height, fillScheme,
borderScheme, toIgnoreOpenSides, needsRotation);
g2fullOpacity.setComposite(AlphaComposite.SrcOver.derive(contribution));
g2fullOpacity.drawImage(layer, 0, 0, layer.getWidth() / scaleFactor,
layer.getHeight() / scaleFactor, null);
}
g2fullOpacity.dispose();
}
boolean isFlat = SubstanceCoreUtilities.hasFlatAppearance(button);
boolean isSpecial = isFlat || !button.isEnabled();
float extraAlpha = 1.0f;
if (isSpecial) {
if (isFlat) {
// Special handling of flat buttons
extraAlpha = 0.0f;
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry :
activeStates.entrySet()) {
ComponentState activeState = activeEntry.getKey();
if (activeState.isDisabled())
continue;
if (activeState == ComponentState.ENABLED)
continue;
extraAlpha += activeEntry.getValue().getContribution();
}
} else {
if (!button.isEnabled()) {
extraAlpha = SubstanceColorSchemeUtilities.getAlpha(button,
currState);
}
}
}
if (extraAlpha > 0.0f) {
Graphics2D graphics = (Graphics2D) g.create();
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(button, extraAlpha, g));
graphics.drawImage(fullOpacity, 0, 0, fullOpacity.getWidth() / scaleFactor,
fullOpacity.getHeight() / scaleFactor, null);
graphics.dispose();
}
}
/**
* Retrieves background image for the specified button in button pair (such
* as scrollbar arrows, for example).
*
* @param button
* Button.
* @param kind
* Color scheme kind.
* @param fillPainter
* Gradient painter.
* @param width
* Button width.
* @param height
* Button height.
* @param side
* Button orientation.
* @param cyclePos
* Cycle position.
* @param colorScheme
* The fill color scheme.
* @param borderScheme
* The border color scheme.
* @param graphicsComposite
* Composite to apply before painting the button.
* @param toIgnoreOpenSides
* If <code>true</code>, the open side setting (controlled by the
* {@link SubstanceLookAndFeel#BUTTON_OPEN_SIDE_PROPERTY} is
* ignored.
* @return Button background image.
*/
private static BufferedImage getPairwiseFullAlphaBackground(
AbstractButton button, SubstanceFillPainter fillPainter,
SubstanceButtonShaper shaper, int width, int height,
SubstanceColorScheme colorScheme,
SubstanceColorScheme borderScheme, boolean toIgnoreOpenSides,
boolean needsRotation) {
if (SubstanceCoreUtilities.isButtonNeverPainted(button))
return null;
Set<Side> openSides = toIgnoreOpenSides ? EnumSet.noneOf(Side.class)
: SubstanceCoreUtilities.getSides(button,
SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY);
Set<Side> straightSides = SubstanceCoreUtilities.getSides(button,
SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY);
boolean isBorderPainted = button.isBorderPainted();
boolean isContentAreaFilled = button.isContentAreaFilled();
float radius = 0.0f;
if (SubstanceCoreUtilities.isSpinnerButton(button)
&& shaper instanceof RectangularButtonShaper) {
radius = ((RectangularButtonShaper) shaper).getCornerRadius(button, 0.0f);
}
HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height, straightSides,
openSides, colorScheme.getDisplayName(), borderScheme.getDisplayName(),
button.getClass().getName(), fillPainter.getDisplayName(), shaper.getDisplayName(),
isBorderPainted, isContentAreaFilled, radius);
// System.out.println("\tKey " + key);
BufferedImage finalBackground = pairwiseBackgrounds.get(key);
if (finalBackground == null) {
// System.out.println("\tNot found");
int deltaLeft = (openSides != null)
&& openSides.contains(Side.LEFT) ? 3 : 0;
int deltaRight = (openSides != null)
&& openSides.contains(Side.RIGHT) ? 3 : 0;
int deltaTop = (openSides != null) && openSides.contains(Side.TOP) ? 3 : 0;
int deltaBottom = (openSides != null) && openSides.contains(Side.BOTTOM) ? 3 : 0;
GeneralPath contour = null;
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
.getBorderPainter(button);
float borderDelta = SubstanceSizeUtils.getBorderStrokeWidth() / 2.0f;
finalBackground = SubstanceCoreUtilities.getBlankImage(width, height);
Graphics2D finalGraphics = (Graphics2D) finalBackground.getGraphics();
finalGraphics.translate(-deltaLeft, -deltaTop);
if (needsRotation) {
// rotate by 90% for better visuals
contour = SubstanceOutlineUtilities.getBaseOutline(height
+ deltaTop + deltaBottom, width + deltaLeft
+ deltaRight, radius, null, borderDelta);
int translateY = finalBackground.getHeight();
if (SubstanceCoreUtilities.isScrollButton(button)) {
if ((openSides != null) && openSides.contains(SubstanceConstants.Side.BOTTOM))
translateY += 4;
}
AffineTransform at = AffineTransform.getTranslateInstance(0, translateY);
at.rotate(-Math.PI / 2);
int scaleFactor = UIUtil.getScaleFactor();
finalGraphics.scale(1, 1);
finalGraphics.setTransform(at);
finalGraphics.scale(scaleFactor, scaleFactor);
if (isContentAreaFilled) {
fillPainter.paintContourBackground(finalGraphics,
button, height + deltaTop + deltaBottom,
width + deltaLeft + deltaRight, contour,
false, colorScheme, true);
}
if (isBorderPainted) {
borderPainter.paintBorder(finalGraphics, button,
height + deltaTop + deltaBottom,
width + deltaLeft + deltaRight,
contour, null, borderScheme);
}
} else {
contour = SubstanceOutlineUtilities.getBaseOutline(
width + deltaLeft + deltaRight,
height + deltaTop + deltaBottom,
radius, straightSides, borderDelta);
if (SubstanceCoreUtilities.isScrollButton(button)) {
if ((openSides != null) && openSides.contains(SubstanceConstants.Side.LEFT))
finalGraphics.translate(1, 0);
}
if (isContentAreaFilled) {
fillPainter.paintContourBackground(finalGraphics, button,
width + deltaLeft + deltaRight,
height + deltaTop + deltaBottom,
contour, false, colorScheme, true);
}
if (isBorderPainted) {
borderPainter.paintBorder(finalGraphics, button,
width + deltaLeft + deltaRight,
height + deltaTop + deltaBottom,
contour, null, borderScheme);
}
}
pairwiseBackgrounds.put(key, finalBackground);
}
return finalBackground;
}
}