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.flamingo.utils;
33
import java.awt.geom.Line2D;
34
import java.awt.image.BufferedImage;
40
import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
41
import org.pushingpixels.flamingo.api.common.JCommandButtonStrip;
42
import org.pushingpixels.flamingo.api.common.AbstractCommandButton.CommandButtonLocationOrderKind;
43
import org.pushingpixels.flamingo.api.common.JCommandButtonStrip.StripOrientation;
44
import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
45
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
46
import org.pushingpixels.lafwidget.LafWidgetUtilities;
47
import org.pushingpixels.substance.api.*;
48
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
49
import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
50
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
51
import org.pushingpixels.substance.flamingo.common.ui.ActionPopupTransitionAwareUI;
52
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
53
import org.pushingpixels.substance.internal.utils.*;
54
import org.pushingpixels.substance.internal.utils.icon.TransitionAware;
57
* Delegate class for painting backgrounds of buttons in <b>Substance </b> look
58
* and feel. This class is <b>for internal use only</b>.
60
* @author Kirill Grouchnikov
62
public class CommandButtonBackgroundDelegate {
64
* Cache for background images. Each time
65
* {@link #getFullAlphaBackground(org.pushingpixels.flamingo.api.common.AbstractCommandButton, javax.swing.ButtonModel, org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter, org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter, int, int, org.pushingpixels.substance.internal.animation.StateTransitionTracker, boolean)}
66
* is called, it checks <code>this</code> map to see if it already contains
67
* such background. If so, the background from the map is returned.
69
private static LazyResettableHashMap<BufferedImage> imageCache = new LazyResettableHashMap<BufferedImage>(
70
"Substance.Flamingo.CommandButtonBackgroundDelegate");
73
* Retrieves the background for the specified button.
75
* @param commandButton
78
* Button fill painter.
79
* @param borderPainter
80
* Button border painter.
85
* @return Button background.
87
public static BufferedImage getFullAlphaBackground(
88
AbstractCommandButton commandButton, ButtonModel buttonModel,
89
SubstanceFillPainter fillPainter,
90
SubstanceBorderPainter borderPainter, int width, int height,
91
StateTransitionTracker stateTransitionTracker,
92
boolean ignoreSelections) {
93
StateTransitionTracker.ModelStateInfo modelStateInfo = (stateTransitionTracker == null) ? null
94
: stateTransitionTracker.getModelStateInfo();
95
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = (modelStateInfo == null) ? null
96
: (ignoreSelections ? modelStateInfo
97
.getStateNoSelectionContributionMap() : modelStateInfo
98
.getStateContributionMap());
99
ComponentState currState = (modelStateInfo == null) ? ComponentState
100
.getState(buttonModel, commandButton)
101
: (ignoreSelections ? modelStateInfo
102
.getCurrModelStateNoSelection() : modelStateInfo
103
.getCurrModelState());
105
SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
106
.getColorScheme(commandButton, currState);
107
SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
108
.getColorScheme(commandButton,
109
ColorSchemeAssociationKind.BORDER, currState);
111
float radius = SubstanceSizeUtils
112
.getClassicButtonCornerRadius(SubstanceSizeUtils
113
.getComponentFontSize(commandButton));
115
Set<SubstanceConstants.Side> straightSides = SubstanceCoreUtilities
116
.getSides(commandButton,
117
SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY);
119
// special handling for location order
120
AbstractCommandButton.CommandButtonLocationOrderKind locationOrderKind = commandButton
121
.getLocationOrderKind();
126
boolean isVertical = false;
127
if ((locationOrderKind != null)
128
&& (locationOrderKind != AbstractCommandButton.CommandButtonLocationOrderKind.ONLY)) {
129
Component parent = commandButton.getParent();
130
if ((parent instanceof JCommandButtonStrip)
131
&& (((JCommandButtonStrip) parent).getOrientation() == StripOrientation.VERTICAL)) {
133
switch (locationOrderKind) {
135
dh = commandButton.getHeight() / 2;
138
dy = -commandButton.getHeight() / 2;
139
dh = commandButton.getHeight();
142
dy = -commandButton.getHeight() / 2;
143
dh = commandButton.getHeight() / 2;
146
boolean ltr = commandButton.getComponentOrientation()
148
if (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.MIDDLE) {
149
dx = -commandButton.getWidth() / 2;
150
dw = commandButton.getWidth();
152
boolean curveOnLeft = (ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST))
153
|| (!ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST));
155
dw = commandButton.getWidth() / 2;
157
dx = -commandButton.getWidth() / 2;
158
dw = commandButton.getWidth() / 2;
164
HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(currState,
165
width, height, baseFillScheme.getDisplayName(),
166
baseBorderScheme.getDisplayName(),
167
fillPainter.getDisplayName(), borderPainter.getDisplayName(),
168
commandButton.getClass().getName(), radius, straightSides,
169
SubstanceSizeUtils.getComponentFontSize(commandButton),
170
locationOrderKind, dx, dy, dw, dh, isVertical);
172
BufferedImage baseLayer = imageCache.get(baseKey);
173
if (baseLayer == null) {
174
baseLayer = getSingleLayer(commandButton, fillPainter,
175
borderPainter, width, height, baseFillScheme,
176
baseBorderScheme, radius, straightSides, locationOrderKind,
177
dx, dy, dw, dh, isVertical);
179
imageCache.put(baseKey, baseLayer);
182
if (currState.isDisabled() || (activeStates == null)
183
|| (activeStates.size() == 1)) {
187
BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
189
Graphics2D g2d = result.createGraphics();
191
g2d.drawImage(baseLayer, 0, 0, null);
193
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
195
ComponentState activeState = activeEntry.getKey();
196
if (activeState == currState)
199
float contribution = activeEntry.getValue().getContribution();
200
if (contribution == 0.0f)
203
SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
204
.getColorScheme(commandButton, activeState);
205
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
206
.getColorScheme(commandButton,
207
ColorSchemeAssociationKind.BORDER, activeState);
209
HashMapKey key = SubstanceCoreUtilities.getHashKey(activeState,
210
width, height, fillScheme.getDisplayName(), borderScheme
211
.getDisplayName(), fillPainter.getDisplayName(),
212
borderPainter.getDisplayName(), commandButton.getClass()
213
.getName(), radius, straightSides,
214
SubstanceSizeUtils.getComponentFontSize(commandButton),
215
locationOrderKind, dx, dy, dw, dh, isVertical);
217
BufferedImage layer = imageCache.get(key);
219
layer = getSingleLayer(commandButton, fillPainter,
220
borderPainter, width, height, fillScheme, borderScheme,
221
radius, straightSides, locationOrderKind, dx, dy, dw,
224
imageCache.put(key, layer);
227
g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
228
g2d.drawImage(layer, 0, 0, null);
235
private static BufferedImage getSingleLayer(
236
AbstractCommandButton commandButton,
237
SubstanceFillPainter fillPainter,
238
SubstanceBorderPainter borderPainter,
241
SubstanceColorScheme fillScheme,
242
SubstanceColorScheme borderScheme,
244
Set<SubstanceConstants.Side> straightSides,
245
AbstractCommandButton.CommandButtonLocationOrderKind locationOrderKind,
246
int dx, int dy, int dw, int dh, boolean isVertical) {
247
int borderDelta = (int) Math.floor(SubstanceSizeUtils
248
.getBorderStrokeWidth(SubstanceSizeUtils
249
.getComponentFontSize(commandButton)) / 2.0);
251
Shape contour = SubstanceOutlineUtilities.getBaseOutline(width + dw,
252
height + dh, radius, straightSides, borderDelta);
253
BufferedImage newBackground = SubstanceCoreUtilities.getBlankImage(
255
Graphics2D finalGraphics = (Graphics2D) newBackground.getGraphics();
256
finalGraphics.translate(dx, dy);
257
fillPainter.paintContourBackground(finalGraphics, commandButton, width
258
+ dw, height + dh, contour, false, fillScheme, true);
260
int borderThickness = (int) SubstanceSizeUtils
261
.getBorderStrokeWidth(SubstanceSizeUtils
262
.getComponentFontSize(commandButton));
263
Shape contourInner = SubstanceOutlineUtilities.getBaseOutline(width
264
+ dw, height + dh, radius, straightSides, borderDelta
266
borderPainter.paintBorder(finalGraphics, commandButton, width + dw,
267
height + dh, contour, contourInner, borderScheme);
270
if ((locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST)
271
|| (locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)) {
272
// outer/inner line at the bottom
273
int y = -dy + commandButton.getHeight() - borderDelta - 2
275
int xs = borderDelta;
276
int xe = commandButton.getWidth() - 2 * borderDelta;
277
Shape upper = new Line2D.Double(xs + borderThickness, y, xe - 2
278
* borderThickness, y);
279
y += borderThickness;
280
Shape lower = new Line2D.Double(xs, y, xe, y);
281
borderPainter.paintBorder(finalGraphics, commandButton, width
282
+ dw, height + dh, lower, upper, borderScheme);
285
// special case for MIDDLE and LAST location order kinds
286
if ((locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)
287
|| (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST)) {
288
// inner line at the top
289
int y = -dy + borderDelta;
290
int xs = borderDelta;
291
int xe = commandButton.getWidth() - 2 * borderDelta;
292
Shape upper = new Line2D.Double(xs + borderThickness, y, xe - 2
293
* borderThickness, y);
294
borderPainter.paintBorder(finalGraphics, commandButton, width
295
+ dw, height + dh, null, upper, borderScheme);
298
// special case for leftmost (not FIRST!!!) and MIDDLE location
301
boolean ltr = commandButton.getComponentOrientation()
303
boolean leftmost = (ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST))
304
|| (!ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST));
306
|| (locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)) {
307
// outer / inner line at the right
308
int x = -dx + commandButton.getWidth() - borderDelta - 2
310
int ys = borderDelta;
311
int ye = commandButton.getHeight() - 2 * borderDelta;
312
Shape upper = new Line2D.Double(x, ys + borderThickness, x, ye
313
- 2 * borderThickness);
314
x += borderThickness;
315
Shape lower = new Line2D.Double(x, ys, x, ye);
316
borderPainter.paintBorder(finalGraphics, commandButton, width
317
+ dw, height + dh, lower, upper, borderScheme);
320
// special case for MIDDLE and LAST location order kinds
321
boolean rightmost = (ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST))
322
|| (!ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST));
323
if ((locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)
325
// inner line at the left
326
int x = -dx + borderDelta;
327
int ys = borderDelta;
328
int ye = commandButton.getHeight() - 2 * borderDelta;
329
Shape upper = new Line2D.Double(x, ys + borderThickness, x, ye
330
- 2 * borderThickness);
331
borderPainter.paintBorder(finalGraphics, commandButton, width
332
+ dw, height + dh, null, upper, borderScheme);
335
return newBackground;
338
public static BufferedImage getCombinedCommandButtonBackground(
339
AbstractCommandButton commandButton, ButtonModel actionModel,
340
Rectangle actionArea, PopupButtonModel popupModel,
341
Rectangle popupArea) {
342
ButtonModel backgroundModel = new DefaultButtonModel();
343
backgroundModel.setEnabled(actionModel.isEnabled()
344
&& popupModel.isEnabled());
346
SubstanceFillPainter fillPainter = SubstanceCoreUtilities
347
.getFillPainter(commandButton);
348
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
349
.getBorderPainter(commandButton);
351
// layer number one - background with the combined enabled
352
// state of both models. Full opacity
353
// System.out.println("Background layer");
354
BufferedImage fullAlphaBackground = CommandButtonBackgroundDelegate
355
.getFullAlphaBackground(commandButton, backgroundModel,
356
fillPainter, borderPainter, commandButton.getWidth(),
357
commandButton.getHeight(), null, false);
359
BufferedImage layers = SubstanceCoreUtilities
360
.getBlankImage(fullAlphaBackground.getWidth(),
361
fullAlphaBackground.getHeight());
362
Graphics2D combinedGraphics = layers.createGraphics();
363
combinedGraphics.drawImage(fullAlphaBackground, 0, 0, null);
365
ActionPopupTransitionAwareUI ui = (ActionPopupTransitionAwareUI) commandButton
368
if (actionModel.isEnabled() && popupModel.isEnabled()) {
369
// layer number two - background with the combined rollover state
370
// of both models. Opacity 60%.
371
backgroundModel.setRollover(actionModel.isRollover()
372
|| popupModel.isRollover() || popupModel.isPopupShowing());
373
// System.out.println(actionModel.isRollover() + ":"
374
// + popupModel.isRollover());
375
combinedGraphics.setComposite(AlphaComposite.SrcOver.derive(0.6f));
376
// System.out.println("Rollover layer");
377
BufferedImage rolloverBackground = CommandButtonBackgroundDelegate
378
.getFullAlphaBackground(commandButton, backgroundModel,
379
fillPainter, borderPainter, commandButton
380
.getWidth(), commandButton.getHeight(), ui
381
.getTransitionTracker(), false);
382
combinedGraphics.drawImage(rolloverBackground, 0, 0, null);
385
// Shape currClip = combinedGraphics.getClip();
386
if ((actionArea != null) && !actionArea.isEmpty()) {
387
// layer number three - action area with its model. Opacity 40%
388
// for enabled popup area, 100% for disabled popup area
389
Graphics2D graphicsAction = (Graphics2D) combinedGraphics.create();
390
// System.out.println(actionArea);
391
graphicsAction.clip(actionArea);
392
// System.out.println(graphicsAction.getClipBounds());
393
float actionAlpha = 0.4f;
394
if ((popupModel != null) && !popupModel.isEnabled())
396
if (!actionModel.isEnabled())
398
graphicsAction.setComposite(AlphaComposite.SrcOver
399
.derive(actionAlpha));
400
BufferedImage actionAreaBackground = CommandButtonBackgroundDelegate
401
.getFullAlphaBackground(commandButton, null, fillPainter,
402
borderPainter, commandButton.getWidth(),
403
commandButton.getHeight(), ui
404
.getActionTransitionTracker(), false);
405
graphicsAction.drawImage(actionAreaBackground, 0, 0, null);
406
// graphicsAction.setColor(Color.red);
407
// graphicsAction.fill(toFill);
408
graphicsAction.dispose();
410
// combinedGraphics.setClip(currClip);
411
if ((popupArea != null) && !popupArea.isEmpty()) {
412
// layer number four - popup area with its model. Opacity 40%
413
// for enabled action area, 100% for disabled action area
414
Graphics2D graphicsPopup = (Graphics2D) combinedGraphics.create();
415
// System.out.println(popupArea);
416
graphicsPopup.clip(popupArea);
417
// System.out.println(graphicsPopup.getClipBounds());
418
float popupAlpha = 0.4f;
419
if (!actionModel.isEnabled())
421
if ((popupModel != null) && !popupModel.isEnabled())
423
graphicsPopup.setComposite(AlphaComposite.SrcOver
424
.derive(popupAlpha));
425
// System.out.println(popupAlpha + ":"
426
// + ComponentState.getState(popupModel, this.commandButton));
427
BufferedImage popupAreaBackground = CommandButtonBackgroundDelegate
428
.getFullAlphaBackground(commandButton, null, fillPainter,
429
borderPainter, commandButton.getWidth(),
430
commandButton.getHeight(), ui
431
.getPopupTransitionTracker(), false);
432
graphicsPopup.drawImage(popupAreaBackground, 0, 0, null);
433
// graphicsPopup.setColor(Color.blue);
434
// graphicsPopup.fill(toFill);
435
graphicsPopup.dispose();
437
combinedGraphics.dispose();
438
// System.out.println(imageCache.size());
443
* Returns the memory usage string.
445
* @return Memory usage string.
447
static String getMemoryUsage() {
448
StringBuffer sb = new StringBuffer();
449
sb.append("SubstanceBackgroundDelegate: \n");
450
sb.append("\t" + imageCache.size() + " regular");
451
// + pairwiseBackgrounds.size() + " pairwise");
452
return sb.toString();
455
public static void paintThemedCommandButtonIcon(Graphics2D g,
456
Rectangle iconRect, AbstractCommandButton commandButton,
457
Icon regular, ButtonModel model,
458
StateTransitionTracker stateTransitionTracker) {
459
Icon themed = SubstanceCoreUtilities.getThemedIcon(commandButton,
462
boolean useRegularVersion = model.isArmed()
464
|| model.isSelected()
465
|| regular.getClass()
466
.isAnnotationPresent(TransitionAware.class);
467
Graphics2D g2d = (Graphics2D) g.create();
468
if (useRegularVersion) {
469
regular.paintIcon(commandButton, g2d, iconRect.x, iconRect.y);
471
if (stateTransitionTracker != null) {
472
float alpha = stateTransitionTracker.getActiveStrength();
474
// paint the themed image full opaque on a separate image
475
BufferedImage themedImage = FlamingoUtilities
476
.getBlankImage(themed.getIconWidth(), themed
478
themed.paintIcon(commandButton, themedImage
479
.createGraphics(), 0, 0);
480
// and paint that image translucently
481
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
482
commandButton, 1.0f - alpha, g));
483
g2d.drawImage(themedImage, iconRect.x, iconRect.y, null);
487
// paint the regular image full opaque on a separate image
488
BufferedImage regularImage = FlamingoUtilities
489
.getBlankImage(regular.getIconWidth(), regular
491
regular.paintIcon(commandButton, regularImage
492
.createGraphics(), 0, 0);
493
// and paint that image translucently
494
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
495
commandButton, alpha, g));
496
g2d.drawImage(regularImage, iconRect.x, iconRect.y, null);
499
if (model.isRollover()) {
500
regular.paintIcon(commandButton, g2d, iconRect.x,
504
.paintIcon(commandButton, g2d, iconRect.x,