2
* Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Substance Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.substance.internal.utils;
32
import java.awt.AlphaComposite;
33
import java.awt.Graphics;
34
import java.awt.Graphics2D;
35
import java.awt.Insets;
36
import java.awt.Shape;
37
import java.awt.image.BufferedImage;
41
import javax.swing.AbstractButton;
42
import javax.swing.ButtonModel;
43
import javax.swing.JButton;
45
import org.pushingpixels.lafwidget.LafWidgetUtilities;
46
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
47
import org.pushingpixels.substance.api.ComponentState;
48
import org.pushingpixels.substance.api.SubstanceColorScheme;
49
import org.pushingpixels.substance.api.SubstanceConstants;
50
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
51
import org.pushingpixels.substance.api.SubstanceConstants.Side;
52
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
53
import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
54
import org.pushingpixels.substance.api.shaper.RectangularButtonShaper;
55
import org.pushingpixels.substance.api.shaper.StandardButtonShaper;
56
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
57
import org.pushingpixels.substance.internal.animation.ModificationAwareUI;
58
import org.pushingpixels.substance.internal.animation.RootPaneDefaultButtonTracker;
59
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
60
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
61
import org.pushingpixels.trident.Timeline;
62
import org.pushingpixels.trident.Timeline.TimelineState;
65
* Delegate class for painting backgrounds of buttons in <b>Substance </b> look
66
* and feel. This class is <b>for internal use only</b>.
68
* @author Kirill Grouchnikov
70
public class ButtonBackgroundDelegate {
72
* Cache for background images. Each time
73
* {@link #getFullAlphaBackground(javax.swing.AbstractButton, javax.swing.ButtonModel, org.pushingpixels.substance.api.shaper.SubstanceButtonShaper, org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter, org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter, int, int)}
74
* is called, it checks <code>this</code> map to see if it already contains
75
* such background. If so, the background from the map is returned.
77
private static LazyResettableHashMap<BufferedImage> regularBackgrounds = new LazyResettableHashMap<BufferedImage>(
78
"ButtonBackgroundDelegate");
81
* Retrieves the background for the specified button.
90
* Button fill painter.
91
* @param borderPainter
92
* Button border painter.
97
* @return Button background.
99
public static BufferedImage getFullAlphaBackground(AbstractButton button,
100
ButtonModel model, SubstanceButtonShaper shaper,
101
SubstanceFillPainter fillPainter,
102
SubstanceBorderPainter borderPainter, int width, int height) {
103
TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
105
StateTransitionTracker.ModelStateInfo modelStateInfo = transitionAwareUI
106
.getTransitionTracker().getModelStateInfo();
108
ComponentState currState = modelStateInfo.getCurrModelState();
110
// ComponentState prevState = stateTransitionModel.getPrevModelState();
112
// System.out.println(button.getText() + ": " + prevState.name() +
114
// + state.name() + " at "
115
// + stateTransitionModel.getTransitionPosition());
117
// compute cycle count (for animation)
118
float cyclePos = 0.0f;// currState.getCyclePosition();
119
// boolean isPulsating = false;
120
if (button instanceof JButton) {
121
JButton jb = (JButton) button;
122
if (RootPaneDefaultButtonTracker.isPulsating(jb)
123
&& (currState != ComponentState.PRESSED_SELECTED)
124
&& (currState != ComponentState.PRESSED_UNSELECTED)) {
125
// isPulsating = true;
126
cyclePos = RootPaneDefaultButtonTracker.getTimelinePosition(jb);
130
// compute the straight sides
131
Set<SubstanceConstants.Side> straightSides = SubstanceCoreUtilities
132
.getSides(button, SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY);
134
boolean isRoundButton = StandardButtonShaper.isRoundButton(button);
136
if (shaper instanceof RectangularButtonShaper) {
137
radius = ((RectangularButtonShaper) shaper).getCornerRadius(button,
141
Set<Side> openSides = SubstanceCoreUtilities.getSides(button,
142
SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY);
143
// String openKey = "";
144
// for (Side oSide : openSides) {
145
// openKey += oSide.name() + "-";
147
// String extraModelKey = "";
148
// for (String modelKey : extraModelKeys) {
149
// extraModelKey += (modelKey + "-");
151
boolean isContentAreaFilled = button.isContentAreaFilled();
152
boolean isBorderPainted = button.isBorderPainted();
154
// compute color scheme
155
SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
156
.getColorScheme(button, ColorSchemeAssociationKind.BORDER,
159
// see if need to use attention-drawing animation
160
// boolean isWindowModified = false;
161
if (button.getUI() instanceof ModificationAwareUI) {
162
ModificationAwareUI modificationAwareUI = (ModificationAwareUI) button
164
Timeline modificationTimeline = modificationAwareUI
165
.getModificationTimeline();
166
if (modificationTimeline != null) {
167
if (modificationTimeline.getState() != TimelineState.IDLE) {
168
// isWindowModified = true;
169
SubstanceColorScheme colorScheme2 = SubstanceColorSchemeUtilities.YELLOW;
170
SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities.ORANGE;
171
cyclePos = modificationTimeline.getTimelinePosition();
173
HashMapKey key1 = SubstanceCoreUtilities.getHashKey(width,
174
height, colorScheme.getDisplayName(),
175
baseBorderScheme.getDisplayName(), shaper
176
.getDisplayName(), fillPainter
177
.getDisplayName(), borderPainter
178
.getDisplayName(), straightSides,
179
openSides, button.getClass().getName(),
180
isRoundButton, radius, isContentAreaFilled,
181
isBorderPainted, SubstanceSizeUtils
182
.getComponentFontSize(button));
183
BufferedImage layer1 = regularBackgrounds.get(key1);
184
if (layer1 == null) {
185
layer1 = createBackgroundImage(button, shaper,
186
fillPainter, borderPainter, width, height,
187
colorScheme, baseBorderScheme, openSides,
188
isContentAreaFilled, isBorderPainted);
190
regularBackgrounds.put(key1, layer1);
192
HashMapKey key2 = SubstanceCoreUtilities.getHashKey(width,
193
height, colorScheme2.getDisplayName(),
194
baseBorderScheme.getDisplayName(), shaper
195
.getDisplayName(), fillPainter
196
.getDisplayName(), borderPainter
197
.getDisplayName(), straightSides,
198
openSides, button.getClass().getName(),
199
isRoundButton, radius, isContentAreaFilled,
200
isBorderPainted, SubstanceSizeUtils
201
.getComponentFontSize(button));
202
BufferedImage layer2 = regularBackgrounds.get(key2);
203
if (layer2 == null) {
204
layer2 = createBackgroundImage(button, shaper,
205
fillPainter, borderPainter, width, height,
206
colorScheme2, baseBorderScheme, openSides,
207
isContentAreaFilled, isBorderPainted);
209
regularBackgrounds.put(key2, layer2);
212
BufferedImage result = SubstanceCoreUtilities
213
.getBlankImage(width, height);
214
Graphics2D g2d = result.createGraphics();
216
g2d.drawImage(layer1, 0, 0, null);
217
if (cyclePos > 0.0f) {
218
g2d.setComposite(AlphaComposite.SrcOver
220
g2d.drawImage(layer2, 0, 0, null);
228
// see if need to use transition animation. Important - don't do it
229
// on pulsating buttons (such as default or close buttons
230
// of modified frames).
232
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
233
.getStateContributionMap();
235
SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
236
.getColorScheme(button, currState);
237
HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(width, height,
238
baseFillScheme.getDisplayName(), baseBorderScheme
239
.getDisplayName(), shaper.getDisplayName(), fillPainter
240
.getDisplayName(), borderPainter.getDisplayName(),
241
straightSides, openSides, button.getClass().getName(),
242
isRoundButton, (int) (1000 * radius), isContentAreaFilled,
243
isBorderPainted, SubstanceSizeUtils
244
.getComponentFontSize(button));
245
BufferedImage layerBase = regularBackgrounds.get(keyBase);
246
if (layerBase == null) {
247
layerBase = createBackgroundImage(button, shaper, fillPainter,
248
borderPainter, width, height, baseFillScheme,
249
baseBorderScheme, openSides, isContentAreaFilled,
251
regularBackgrounds.put(keyBase, layerBase);
253
if (currState.isDisabled() || (activeStates.size() == 1)) {
257
BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
259
Graphics2D g2d = result.createGraphics();
260
// draw the base layer
261
g2d.drawImage(layerBase, 0, 0, null);
262
// System.out.println("\nPainting base state " + currState);
264
// draw the other active layers
265
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
267
ComponentState activeState = activeEntry.getKey();
268
// System.out.println("Painting state " + activeState + "[curr is "
269
// + currState + "] with " + activeEntry.getValue());
270
if (activeState == currState)
273
float stateContribution = activeEntry.getValue().getContribution();
274
if (stateContribution > 0.0f) {
275
g2d.setComposite(AlphaComposite.SrcOver
276
.derive(stateContribution));
278
SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
279
.getColorScheme(button, activeState);
280
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
281
.getColorScheme(button,
282
ColorSchemeAssociationKind.BORDER, activeState);
283
HashMapKey key = SubstanceCoreUtilities.getHashKey(width,
284
height, fillScheme.getDisplayName(), borderScheme
285
.getDisplayName(), shaper.getDisplayName(),
286
fillPainter.getDisplayName(), borderPainter
287
.getDisplayName(), straightSides, openSides,
288
button.getClass().getName(), isRoundButton,
289
(int) (1000 * radius), isContentAreaFilled,
290
isBorderPainted, SubstanceSizeUtils
291
.getComponentFontSize(button));
292
BufferedImage layer = regularBackgrounds.get(key);
294
layer = createBackgroundImage(button, shaper, fillPainter,
295
borderPainter, width, height, fillScheme,
296
borderScheme, openSides, isContentAreaFilled,
298
regularBackgrounds.put(key, layer);
300
g2d.drawImage(layer, 0, 0, null);
307
private static BufferedImage createBackgroundImage(AbstractButton button,
308
SubstanceButtonShaper shaper, SubstanceFillPainter fillPainter,
309
SubstanceBorderPainter borderPainter, int width, int height,
310
SubstanceColorScheme colorScheme,
311
SubstanceColorScheme borderScheme, Set<Side> openSides,
312
boolean isContentAreaFilled, boolean isBorderPainted) {
313
int openDelta = (int) (Math.ceil(3.0 * SubstanceSizeUtils
314
.getBorderStrokeWidth(SubstanceSizeUtils
315
.getComponentFontSize(button))));
316
int deltaLeft = ((openSides != null) && openSides.contains(Side.LEFT)) ? openDelta
318
int deltaRight = ((openSides != null) && openSides.contains(Side.RIGHT)) ? openDelta
320
int deltaTop = ((openSides != null) && openSides.contains(Side.TOP)) ? openDelta
322
int deltaBottom = ((openSides != null) && openSides
323
.contains(Side.BOTTOM)) ? openDelta : 0;
325
// System.err.println(key);
326
int borderDelta = (int) Math.floor(SubstanceSizeUtils
327
.getBorderStrokeWidth(SubstanceSizeUtils
328
.getComponentFontSize(button)) / 2.0);
329
Shape contour = shaper.getButtonOutline(button, new Insets(borderDelta,
330
borderDelta, borderDelta, borderDelta), width + deltaLeft
331
+ deltaRight, height + deltaTop + deltaBottom, false);
333
BufferedImage newBackground = SubstanceCoreUtilities.getBlankImage(
335
Graphics2D finalGraphics = (Graphics2D) newBackground.getGraphics();
336
finalGraphics.translate(-deltaLeft, -deltaTop);
337
if (isContentAreaFilled) {
338
fillPainter.paintContourBackground(finalGraphics, button, width
339
+ deltaLeft + deltaRight, height + deltaTop + deltaBottom,
340
contour, false, colorScheme, true);
343
if (isBorderPainted) {
344
int borderThickness = (int) SubstanceSizeUtils
345
.getBorderStrokeWidth(SubstanceSizeUtils
346
.getComponentFontSize(button));
347
Shape contourInner = borderPainter.isPaintingInnerContour() ? shaper
348
.getButtonOutline(button, new Insets(borderDelta
349
+ borderThickness, borderDelta + borderThickness,
350
borderDelta + borderThickness, borderDelta
351
+ borderThickness), width + deltaLeft
352
+ deltaRight, height + deltaTop + deltaBottom, true)
354
borderPainter.paintBorder(finalGraphics, button, width + deltaLeft
355
+ deltaRight, height + deltaTop + deltaBottom, contour,
356
contourInner, borderScheme);
358
return newBackground;
362
* Simple constructor.
364
public ButtonBackgroundDelegate() {
369
* Updates background of the specified button.
376
public void updateBackground(Graphics g, AbstractButton button) {
377
// failsafe for LAF change
378
if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
381
if (SubstanceCoreUtilities.isButtonNeverPainted(button))
384
int width = button.getWidth();
385
int height = button.getHeight();
387
if (SubstanceCoreUtilities.isScrollButton(button)
388
|| SubstanceCoreUtilities.isSpinnerButton(button)) {
389
Sideable sideable = (Sideable) button;
390
PairwiseButtonBackgroundDelegate.updatePairwiseBackground(g,
391
button, width, height, sideable.getSide(), false);
395
SubstanceFillPainter fillPainter = SubstanceCoreUtilities
396
.getFillPainter(button);
397
SubstanceButtonShaper shaper = SubstanceCoreUtilities
398
.getButtonShaper(button);
399
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
400
.getBorderPainter(button);
402
BufferedImage bgImage = getFullAlphaBackground(button, button
403
.getModel(), shaper, fillPainter, borderPainter, width, height);
405
TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
407
StateTransitionTracker stateTransitionTracker = transitionAwareUI
408
.getTransitionTracker();
409
StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
410
.getModelStateInfo();
411
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
412
.getStateContributionMap();
414
// Two special cases here:
415
// 1. Button has flat appearance.
416
// 2. Button is disabled.
417
// For both cases, we need to set custom translucency.
418
boolean isFlat = SubstanceCoreUtilities.hasFlatAppearance(button);
419
boolean isSpecial = isFlat || !button.isEnabled();
420
float extraAlpha = 1.0f;
423
// Special handling of flat buttons
425
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
427
ComponentState activeState = activeEntry.getKey();
428
if (activeState.isDisabled())
430
if (activeState == ComponentState.ENABLED)
432
extraAlpha += activeEntry.getValue().getContribution();
435
if (!button.isEnabled()) {
436
extraAlpha = SubstanceColorSchemeUtilities.getAlpha(button,
437
modelStateInfo.getCurrModelState());
441
if (extraAlpha > 0.0f) {
442
Graphics2D graphics = (Graphics2D) g.create();
443
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(button,
445
graphics.drawImage(bgImage, 0, y, null);
451
* Checks whether the specified button has round corners.
455
* @return <code>true</code> if the specified button has round corners,
456
* <code>false</code> otherwise.
458
public static boolean isRoundButton(AbstractButton button) {
459
return (!SubstanceCoreUtilities.isComboBoxButton(button))
460
&& (!SubstanceCoreUtilities.isScrollButton(button))
461
&& SubstanceCoreUtilities.hasText(button);
465
* Returns <code>true</code> if the specified <i>x,y </i> location is
466
* contained within the look and feel's defined shape of the specified
467
* component. <code>x</code> and <code>y</code> are defined to be relative
468
* to the coordinate system of the specified component.
471
* the component where the <i>x,y </i> location is being queried;
473
* the <i>x </i> coordinate of the point
475
* the <i>y </i> coordinate of the point
476
* @return <code>true</code> if the specified <i>x,y </i> location is
477
* contained within the look and feel's defined shape of the
478
* specified component, <code>false</code> otherwise.
480
public static boolean contains(AbstractButton button, int x, int y) {
481
// failsafe for LAF change
482
if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
485
SubstanceButtonShaper shaper = SubstanceCoreUtilities
486
.getButtonShaper(button);
489
Shape contour = shaper.getButtonOutline(button, null,
490
button.getWidth(), button.getHeight(), false);
491
return contour.contains(x, y);
495
* Returns the memory usage string.
497
* @return Memory usage string.
499
static String getMemoryUsage() {
500
StringBuffer sb = new StringBuffer();
501
sb.append("SubstanceBackgroundDelegate: \n");
502
sb.append("\t" + regularBackgrounds.size() + " regular");
503
// + pairwiseBackgrounds.size() + " pairwise");
504
return sb.toString();