All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pushingpixels.radiance.component.internal.theming.utils.CommandButtonBackgroundDelegate Maven / Gradle / Ivy

/*
 * Copyright (c) 2005-2021 Radiance 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 the copyright holder 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.radiance.component.internal.theming.utils;

import org.pushingpixels.radiance.component.api.common.JCommandButton;
import org.pushingpixels.radiance.component.api.common.JCommandButtonStrip;
import org.pushingpixels.radiance.component.api.common.model.CommandStripPresentationModel;
import org.pushingpixels.radiance.component.api.common.model.PopupButtonModel;
import org.pushingpixels.radiance.component.internal.theming.common.GlowingRadianceIcon;
import org.pushingpixels.radiance.component.internal.theming.common.ui.ActionPopupTransitionAwareUI;
import org.pushingpixels.radiance.common.api.RadianceCommonCortex;
import org.pushingpixels.radiance.theming.api.ComponentState;
import org.pushingpixels.radiance.theming.api.RadianceThemingSlices;
import org.pushingpixels.radiance.theming.api.RadianceThemingSlices.AnimationFacet;
import org.pushingpixels.radiance.theming.api.RadianceThemingSlices.ColorSchemeAssociationKind;
import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme;
import org.pushingpixels.radiance.theming.api.painter.border.RadianceBorderPainter;
import org.pushingpixels.radiance.theming.api.painter.fill.RadianceFillPainter;
import org.pushingpixels.radiance.theming.internal.AnimationConfigurationManager;
import org.pushingpixels.radiance.theming.internal.RadianceSynapse;
import org.pushingpixels.radiance.theming.internal.animation.StateTransitionTracker;
import org.pushingpixels.radiance.theming.internal.utils.*;
import org.pushingpixels.radiance.theming.internal.utils.icon.TransitionAware;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.Set;

/**
 * Delegate class for painting backgrounds of buttons in Radiance look and feel. This class
 * is for internal use only.
 *
 * @author Kirill Grouchnikov
 */
public class CommandButtonBackgroundDelegate {
    /**
     * Cache for background images.
     */
    private static LazyResettableHashMap imageCache = new LazyResettableHashMap<>(
            "CommandButtonBackgroundDelegate");

    /**
     * Retrieves the background for the specified button.
     *
     * @param commandButton          Button.
     * @param buttonModel            Button model.
     * @param fillPainter            Button fill painter.
     * @param borderPainter          Button border painter.
     * @param width                  Button width.
     * @param height                 Button height.
     * @param stateTransitionTracker State transition tracker.
     * @param ignoreSelections       If true, selection state will be ignored.
     * @return Button background.
     */
    @SuppressWarnings("incomplete-switch")
    public static BufferedImage getFullAlphaBackground(JCommandButton commandButton,
            ButtonModel buttonModel, RadianceFillPainter fillPainter,
            RadianceBorderPainter borderPainter, int width, int height,
            StateTransitionTracker stateTransitionTracker, boolean ignoreSelections) {
        double scale = RadianceCommonCortex.getScaleFactor(commandButton);
        StateTransitionTracker.ModelStateInfo modelStateInfo = (stateTransitionTracker == null)
                ? null : stateTransitionTracker.getModelStateInfo();
        Map activeStates =
                (modelStateInfo == null)
                        ? null
                        : (ignoreSelections ? modelStateInfo.getStateNoSelectionContributionMap()
                        : modelStateInfo.getStateContributionMap());
        ComponentState currState = (modelStateInfo == null)
                ? ComponentState.getState(buttonModel, commandButton)
                : (ignoreSelections ? modelStateInfo.getCurrModelStateNoSelection()
                : modelStateInfo.getCurrModelState());

        RadianceColorScheme baseFillScheme = RadianceColorSchemeUtilities
                .getColorScheme(commandButton, currState);
        RadianceColorScheme baseBorderScheme = RadianceColorSchemeUtilities
                .getColorScheme(commandButton, ColorSchemeAssociationKind.BORDER, currState);

        float radius = commandButton.getProjection().getPresentationModel().isMenu() ? 0 :
                RadianceSizeUtils.getClassicButtonCornerRadius(
                        RadianceSizeUtils.getComponentFontSize(commandButton));

        Set straightSides = RadianceCoreUtilities.getSides(commandButton,
                RadianceSynapse.BUTTON_STRAIGHT_SIDE);

        // special handling for location order
        JCommandButton.CommandButtonLocationOrderKind locationOrderKind = commandButton
                .getLocationOrderKind();
        int dx = 0;
        int dy = 0;
        int dw = 0;
        int dh = 0;
        boolean isVertical = false;
        if ((locationOrderKind != null)
                && (locationOrderKind != JCommandButton.CommandButtonLocationOrderKind.ONLY)) {
            Component parent = commandButton.getParent();
            if ((parent instanceof JCommandButtonStrip) &&
                    (((JCommandButtonStrip) parent).getProjection().getPresentationModel().getOrientation() ==
                            CommandStripPresentationModel.StripOrientation.VERTICAL)) {
                isVertical = true;
                switch (locationOrderKind) {
                    case FIRST:
                        dh = commandButton.getHeight() / 2;
                        break;
                    case MIDDLE:
                        dy = -commandButton.getHeight() / 2;
                        dh = commandButton.getHeight();
                        break;
                    case LAST:
                        dy = -commandButton.getHeight() / 2;
                        dh = commandButton.getHeight() / 2;
                }
            } else {
                boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
                if (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.MIDDLE) {
                    dx = -commandButton.getWidth() / 2;
                    dw = commandButton.getWidth();
                } else {
                    boolean curveOnLeft = (ltr
                            && (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.FIRST))
                            || (!ltr && (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.LAST));
                    if (curveOnLeft) {
                        dw = commandButton.getWidth() / 2;
                    } else {
                        dx = -commandButton.getWidth() / 2;
                        dw = commandButton.getWidth() / 2;
                    }
                }
            }
        }

        ImageHashMapKey baseKey = RadianceCoreUtilities.getScaleAwareHashKey(
                scale, currState, width, height,
                baseFillScheme.getDisplayName(), baseBorderScheme.getDisplayName(),
                fillPainter.getDisplayName(), borderPainter.getDisplayName(),
                commandButton.getClass().getName(), radius, straightSides,
                RadianceSizeUtils.getComponentFontSize(commandButton), locationOrderKind, dx, dy,
                dw, dh, isVertical);

        BufferedImage baseLayer = imageCache.get(baseKey);
        if (baseLayer == null) {
            baseLayer = getSingleLayer(commandButton, fillPainter, borderPainter, width, height,
                    baseFillScheme, baseBorderScheme, radius, straightSides, locationOrderKind, dx,
                    dy, dw, dh, isVertical);

            imageCache.put(baseKey, baseLayer);
        }

        if (currState.isDisabled() || (activeStates == null) || (activeStates.size() == 1)) {
            return baseLayer;
        }

        BufferedImage result = RadianceCoreUtilities.getBlankImage(scale, width, height);
        Graphics2D g2d = result.createGraphics();

        RadianceCommonCortex.drawImageWithScale(g2d, scale, baseLayer, 0, 0);

        for (Map.Entry activeEntry :
                activeStates.entrySet()) {
            ComponentState activeState = activeEntry.getKey();
            if (activeState == currState) {
                continue;
            }

            float contribution = activeEntry.getValue().getContribution();
            if (contribution == 0.0f) {
                continue;
            }

            RadianceColorScheme fillScheme = RadianceColorSchemeUtilities
                    .getColorScheme(commandButton, activeState);
            RadianceColorScheme borderScheme = RadianceColorSchemeUtilities
                    .getColorScheme(commandButton, ColorSchemeAssociationKind.BORDER, activeState);

            ImageHashMapKey key = RadianceCoreUtilities.getScaleAwareHashKey(
                    scale, activeState, width, height,
                    fillScheme.getDisplayName(), borderScheme.getDisplayName(),
                    fillPainter.getDisplayName(), borderPainter.getDisplayName(),
                    commandButton.getClass().getName(), radius, straightSides,
                    RadianceSizeUtils.getComponentFontSize(commandButton), locationOrderKind, dx,
                    dy, dw, dh, isVertical);

            BufferedImage layer = imageCache.get(key);
            if (layer == null) {
                layer = getSingleLayer(commandButton, fillPainter, borderPainter, width, height,
                        fillScheme, borderScheme, radius, straightSides, locationOrderKind, dx, dy,
                        dw, dh, isVertical);

                imageCache.put(key, layer);
            }

            g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
            RadianceCommonCortex.drawImageWithScale(g2d, scale, layer, 0, 0);
        }

        g2d.dispose();
        return result;
    }

    private static BufferedImage getSingleLayer(JCommandButton commandButton,
            RadianceFillPainter fillPainter, RadianceBorderPainter borderPainter, int width,
            int height, RadianceColorScheme fillScheme, RadianceColorScheme borderScheme,
            float radius, Set straightSides,
            JCommandButton.CommandButtonLocationOrderKind locationOrderKind, int dx, int dy,
            int dw, int dh, boolean isVertical) {
        double scale = RadianceCommonCortex.getScaleFactor(commandButton);
        float borderDelta = RadianceSizeUtils.getBorderStrokeWidth(commandButton) / 2.0f;

        Shape contour = RadianceOutlineUtilities.getBaseOutline(width + dw, height + dh, radius,
                straightSides, borderDelta);
        BufferedImage newBackground = RadianceCoreUtilities.getBlankImage(scale, width, height);
        Graphics2D finalGraphics = (Graphics2D) newBackground.getGraphics();
        finalGraphics.translate(dx, dy);
        fillPainter.paintContourBackground(finalGraphics, commandButton, width + dw, height + dh,
                contour, false, fillScheme, true);

        float borderThickness = RadianceSizeUtils.getBorderStrokeWidth(commandButton);
        Shape contourInner = RadianceOutlineUtilities.getBaseOutline(width + dw, height + dh,
                radius, straightSides, borderDelta + borderThickness);
        borderPainter.paintBorder(finalGraphics, commandButton, width + dw, height + dh, contour,
                contourInner, borderScheme);

        if (isVertical) {
            if ((locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.FIRST)
                    || (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.MIDDLE)) {
                // outer/inner line at the bottom
                float y = -dy + commandButton.getHeight() - borderDelta - borderThickness;
                float xs = borderDelta;
                float xe = commandButton.getWidth() - borderDelta;
                Shape upper = new Line2D.Double(xs + borderThickness, y, xe - borderThickness, y);
                y += borderThickness;
                Shape lower = new Line2D.Double(xs, y, xe, y);
                borderPainter.paintBorder(finalGraphics, commandButton, width + dw, height + dh,
                        lower, upper, borderScheme);
            }

            // special case for MIDDLE and LAST location order kinds
            if ((locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.MIDDLE)
                    || (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.LAST)) {
                // inner line at the top
                float y = -dy + borderDelta;
                float xs = borderDelta;
                float xe = commandButton.getWidth() - borderDelta;
                Shape upper = new Line2D.Double(xs + borderThickness, y, xe - borderThickness, y);
                borderPainter.paintBorder(finalGraphics, commandButton, width + dw, height + dh,
                        null, upper, borderScheme);
            }
        } else {
            // special case for leftmost (not FIRST!!!) and MIDDLE location
            // order kinds
            boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
            boolean leftmost = (ltr
                    && (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.FIRST))
                    || (!ltr && (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.LAST));
            if (leftmost || (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.MIDDLE)) {
                // outer / inner line at the right
                float x = -dx + commandButton.getWidth() - borderDelta - borderThickness;
                float ys = borderDelta;
                float ye = commandButton.getHeight() - borderDelta;
                Shape upper = new Line2D.Double(x, ys + borderThickness, x, ye - borderThickness);
                x += borderThickness;
                Shape lower = new Line2D.Double(x, ys, x, ye);
                borderPainter.paintBorder(finalGraphics, commandButton, width + dw, height + dh,
                        lower, upper, borderScheme);
            }

            // special case for MIDDLE and LAST location order kinds
            boolean rightmost = (ltr
                    && (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.LAST))
                    || (!ltr && (locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.FIRST));
            if ((locationOrderKind == JCommandButton.CommandButtonLocationOrderKind.MIDDLE) || rightmost) {
                // inner line at the left
                float x = -dx + borderDelta;
                float ys = borderDelta;
                float ye = commandButton.getHeight() - borderDelta;
                Shape upper = new Line2D.Double(x, ys + borderThickness, x, ye - borderThickness);
                borderPainter.paintBorder(finalGraphics, commandButton, width + dw, height + dh,
                        null, upper, borderScheme);
            }
        }
        return newBackground;
    }

    public static BufferedImage getCombinedCommandButtonBackground(
            JCommandButton commandButton, ButtonModel actionModel, Rectangle actionArea,
            PopupButtonModel popupModel, Rectangle popupArea) {
        double scale = RadianceCommonCortex.getScaleFactor(commandButton);
        ButtonModel backgroundModel = new DefaultButtonModel();
        backgroundModel.setEnabled(actionModel.isEnabled() && popupModel.isEnabled());

        RadianceFillPainter fillPainter = RadianceCoreUtilities.getFillPainter(commandButton);
        RadianceBorderPainter borderPainter = RadianceCoreUtilities
                .getBorderPainter(commandButton);

        // layer number one - background with the combined enabled
        // state of both models. Full opacity
        // System.out.println("Background layer");
        boolean ignoreSelections = commandButton.getProjection().getContentModel().isToggle()
                && commandButton.getProjection().getPresentationModel().isMenu();
        BufferedImage fullAlphaBackground = CommandButtonBackgroundDelegate.getFullAlphaBackground(
                commandButton, backgroundModel, fillPainter, borderPainter,
                commandButton.getWidth(), commandButton.getHeight(), null, ignoreSelections);

        BufferedImage layers = RadianceCoreUtilities.getBlankImage(scale,
                fullAlphaBackground.getWidth(), fullAlphaBackground.getHeight());
        Graphics2D combinedGraphics = layers.createGraphics();
        RadianceCommonCortex.drawImageWithScale(combinedGraphics, scale, fullAlphaBackground, 0, 0);

        ActionPopupTransitionAwareUI ui = (ActionPopupTransitionAwareUI) commandButton.getUI();

        if (actionModel.isEnabled() && popupModel.isEnabled()) {
            // layer number two - background with the combined rollover state
            // of both models. Opacity 60%.
            backgroundModel.setRollover(actionModel.isRollover() || popupModel.isRollover()
                    || popupModel.isPopupShowing());
            // System.out.println(actionModel.isRollover() + ":"
            // + popupModel.isRollover());
            combinedGraphics.setComposite(AlphaComposite.SrcOver.derive(0.6f));
            // System.out.println("Rollover layer");
            BufferedImage rolloverBackground = CommandButtonBackgroundDelegate
                    .getFullAlphaBackground(commandButton, backgroundModel, fillPainter,
                            borderPainter, commandButton.getWidth(), commandButton.getHeight(),
                            ui.getTransitionTracker(), ignoreSelections);
            RadianceCommonCortex.drawImageWithScale(combinedGraphics, scale, rolloverBackground, 0, 0);
        }

        if ((actionArea != null) && !actionArea.isEmpty()) {
            // layer number three - action area with its model. Opacity:
            // * 40% for enabled popup area of non-selected action
            // * 100% for enabled popup area of selected action
            // * 100% for disabled popup area
            Graphics2D graphicsAction = (Graphics2D) combinedGraphics.create();
            graphicsAction.clip(actionArea);
            float actionAlpha = actionModel.isSelected() ? 1.0f : 0.4f;
            if ((popupModel != null) && !popupModel.isEnabled())
                actionAlpha = 1.0f;
            if (!actionModel.isEnabled())
                actionAlpha = 0.0f;
            graphicsAction.setComposite(AlphaComposite.SrcOver.derive(actionAlpha));
            BufferedImage actionAreaBackground = CommandButtonBackgroundDelegate
                    .getFullAlphaBackground(commandButton, null, fillPainter, borderPainter,
                            commandButton.getWidth(), commandButton.getHeight(),
                            ui.getActionTransitionTracker(), ignoreSelections);
            RadianceCommonCortex.drawImageWithScale(graphicsAction, scale, actionAreaBackground, 0, 0);
            // graphicsAction.setColor(Color.red);
            // graphicsAction.fill(toFill);
            graphicsAction.dispose();
        }
        // combinedGraphics.setClip(currClip);
        if ((popupArea != null) && !popupArea.isEmpty()) {
            // layer number four - popup area with its model. Opacity 40%
            // for enabled action area, 100% for disabled action area
            Graphics2D graphicsPopup = (Graphics2D) combinedGraphics.create();
            // System.out.println(popupArea);
            graphicsPopup.clip(popupArea);
            // System.out.println(graphicsPopup.getClipBounds());
            float popupAlpha = 0.4f;
            if (!actionModel.isEnabled())
                popupAlpha = 1.0f;
            if ((popupModel != null) && !popupModel.isEnabled())
                popupAlpha = 0.0f;
            graphicsPopup.setComposite(AlphaComposite.SrcOver.derive(popupAlpha));
            // System.out.println(popupAlpha + ":"
            // + ComponentState.getState(popupModel, this.commandButton));
            BufferedImage popupAreaBackground = CommandButtonBackgroundDelegate
                    .getFullAlphaBackground(commandButton, null, fillPainter, borderPainter,
                            commandButton.getWidth(), commandButton.getHeight(),
                            ui.getPopupTransitionTracker(), ignoreSelections);
            RadianceCommonCortex.drawImageWithScale(graphicsPopup, scale, popupAreaBackground, 0, 0);
            // graphicsPopup.setColor(Color.blue);
            // graphicsPopup.fill(toFill);
            graphicsPopup.dispose();
        }
        combinedGraphics.dispose();
        // System.out.println(imageCache.size());
        return layers;
    }

    public static void paintCommandButtonIcon(Graphics2D g, Rectangle iconRect,
            JCommandButton commandButton, Icon regular, GlowingRadianceIcon glowingIcon,
            ButtonModel model, StateTransitionTracker stateTransitionTracker, Color textColor) {
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.translate(iconRect.x, iconRect.y);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BICUBIC);

        float alpha = stateTransitionTracker.getActiveStrength();
        if (alpha > 0 && glowingIcon != null && model.isEnabled()
                && AnimationConfigurationManager.getInstance()
                .isAnimationAllowed(AnimationFacet.ICON_GLOW, commandButton)
                && stateTransitionTracker.getIconGlowTracker().isPlaying()) {
            glowingIcon.paintIcon(commandButton, g2d, 0, 0);
            return;
        }

        if (regular.getClass().isAnnotationPresent(TransitionAware.class)) {
            regular.paintIcon(commandButton, g2d, 0, 0);
            return;
        }

        StateTransitionTracker.ModelStateInfo stateInfo = stateTransitionTracker.getModelStateInfo();
        ComponentState currentState = stateInfo.getCurrModelState();
        if (currentState.isDisabled()) {
            // No support yet for transitions between disabled and enabled / active
            // states
            Icon disabledIcon = RadianceCoreUtilities.getFilteredIcon(commandButton,
                    regular, currentState, textColor);
            disabledIcon.paintIcon(commandButton, g2d, 0, 0);
        } else {
            // Active states are painted on top of the icon that corresponds to the
            // enabled state
            Icon enabledIcon = RadianceCoreUtilities.getFilteredIcon(commandButton,
                    regular, ComponentState.ENABLED, textColor);
            enabledIcon.paintIcon(commandButton, g2d, 0, 0);
            if (stateTransitionTracker.getActiveStrength() > 0.0f) {
                for (Map.Entry entry :
                        stateInfo.getStateContributionMap().entrySet()) {
                    if (entry.getKey() == ComponentState.ENABLED) {
                        continue;
                    }
                    float contribution = entry.getValue().getContribution();
                    if (contribution > 0.0f) {
                        Icon activeIcon = RadianceCoreUtilities.getFilteredIcon(commandButton,
                                regular, entry.getKey(), textColor);
                        if (activeIcon != enabledIcon) {
                            g2d.setComposite(WidgetUtilities.getAlphaComposite(
                                    commandButton, contribution, g));
                            activeIcon.paintIcon(commandButton, g2d, 0, 0);
                        }
                    }
                }
            }
        }

        g2d.dispose();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy