Skip to content

Commit

Permalink
Add animations on tonal buttons
Browse files Browse the repository at this point in the history
Background, border, text. For #400
  • Loading branch information
kirill-grouchnikov committed Dec 1, 2024
1 parent 7a3f872 commit f21f77d
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,14 @@ public final RadianceColorScheme getActiveColorScheme(
return this.colorSchemeBundleMap.get(RadianceThemingSlices.DecorationAreaType.NONE).getActiveColorScheme();
}

public final ContainerRenderColorTokens getActiveColorRenderTokens(
RadianceThemingSlices.DecorationAreaType decorationAreaType) {
if (this.tonalColorSchemeMap.containsKey(decorationAreaType)) {
return this.tonalColorSchemeMap.get(decorationAreaType).getPrimaryContainerTokens();
}
return this.tonalColorSchemeMap.get(RadianceThemingSlices.DecorationAreaType.NONE).getPrimaryContainerTokens();
}

/**
* Returns the main enabled color scheme for the specific decoration area
* type. Custom painting code that needs to consult the colors of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
*/
package org.pushingpixels.radiance.theming.internal;

import org.pushingpixels.radiance.theming.api.RadianceThemingCortex;
import org.pushingpixels.radiance.theming.api.RadianceThemingSlices;
import org.pushingpixels.radiance.animation.api.ease.Spline;
import org.pushingpixels.radiance.animation.api.swing.SwingComponentTimeline;
import org.pushingpixels.radiance.theming.api.RadianceThemingCortex;
import org.pushingpixels.radiance.theming.api.RadianceThemingSlices;

import java.awt.*;
import java.util.HashMap;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2005-2024 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.theming.internal.blade;

import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokens;
import org.pushingpixels.radiance.theming.api.palette.ContainerOutlineColorTokens;
import org.pushingpixels.radiance.theming.api.palette.ContainerRenderColorTokens;
import org.pushingpixels.radiance.theming.api.palette.OnContainerColorTokens;

import java.awt.*;

public class BladeContainerRenderColorTokens implements ContainerRenderColorTokens {
private ContainerColorTokens containerColorTokens;
private OnContainerColorTokens onContainerColorTokens;
private ContainerOutlineColorTokens containerOutlineColorTokens;

public Color containerLowest = Color.white;
public Color containerLow = Color.white;
public Color container = Color.white;
public Color containerHigh = Color.white;
public Color containerHighest = Color.white;
public Color onContainer = Color.white;
public Color onContainerVariant = Color.white;
public Color containerOutline = Color.white;
public Color containerOutlineVariant = Color.white;
public String combinedName = "";

public BladeContainerRenderColorTokens() {
this.containerColorTokens = new ContainerColorTokens() {
@Override
public Color getContainerLowest() {
return containerLowest;
}

@Override
public Color getContainerLow() {
return containerLow;
}

@Override
public Color getContainer() {
return container;
}

@Override
public Color getContainerHigh() {
return containerHigh;
}

@Override
public Color getContainerHighest() {
return containerHighest;
}
};
this.onContainerColorTokens = new OnContainerColorTokens() {
@Override
public Color getOnContainer() {
return onContainer;
}

@Override
public Color getOnContainerVariant() {
return onContainerVariant;
}
};
this.containerOutlineColorTokens = new ContainerOutlineColorTokens() {
@Override
public Color getContainerOutline() {
return containerOutline;
}

@Override
public Color getContainerOutlineVariant() {
return containerOutlineVariant;
}
};
}

@Override
public ContainerColorTokens getContainerColorTokens() {
return this.containerColorTokens;
}

@Override
public OnContainerColorTokens getOnContainerColorTokens() {
return this.onContainerColorTokens;
}

@Override
public ContainerOutlineColorTokens getContainerOutlineColorTokens() {
return this.containerOutlineColorTokens;
}

@Override
public int hashCode() {
if (this.combinedName.length() == 0) {
return super.hashCode();
}
return this.combinedName.hashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,95 @@ public static void populateColorScheme(
bladeColorScheme.displayName = nameBuilder.toString();
}

public static void populateColorTokens(
BladeContainerRenderColorTokens bladeRenderColorTokens,
Component component,
StateTransitionTracker.ModelStateInfo modelStateInfo,
ComponentState currState,
RadianceThemingSlices.ColorSchemeAssociationKind associationKind,
boolean treatEnabledAsActive) {
if (!SwingUtilities.isEventDispatchThread()) {
UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
"Color scheme population must be done on Event Dispatch Thread");
uiThreadingViolationError.printStackTrace(System.err);
throw uiThreadingViolationError;
}

StringBuilder nameBuilder = new StringBuilder();
ContainerRenderColorTokens currColorTokens = (treatEnabledAsActive && (currState == ComponentState.ENABLED))
? RadianceColorSchemeUtilities.getActiveRenderColorTokens(component, currState)
: RadianceColorSchemeUtilities.getRenderColorTokens(component, associationKind, currState);

Color containerLowest = currColorTokens.getContainerColorTokens().getContainerLowest();
Color containerLow = currColorTokens.getContainerColorTokens().getContainerLow();
Color container = currColorTokens.getContainerColorTokens().getContainer();
Color containerHigh = currColorTokens.getContainerColorTokens().getContainerHigh();
Color containerHighest = currColorTokens.getContainerColorTokens().getContainerHighest();
Color onContainer = currColorTokens.getOnContainerColorTokens().getOnContainer();
Color onContainerVariant = currColorTokens.getOnContainerColorTokens().getOnContainerVariant();
Color containerOutline = currColorTokens.getContainerOutlineColorTokens().getContainerOutline();
Color containerOutlineVariant = currColorTokens.getContainerOutlineColorTokens().getContainerOutlineVariant();

nameBuilder.append(currColorTokens.hashCode());

Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates =
(modelStateInfo == null) ? null : modelStateInfo.getStateContributionMap();

if (!currState.isDisabled() && (activeStates != null) && (activeStates.size() > 1)) {
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates.entrySet()) {
if (activeEntry.getKey() == currState) {
// Already accounted for the currently active state
continue;
}
float amount = activeEntry.getValue().getContribution();
if (amount == 0.0f) {
// Skip a zero-amount contribution
continue;
}
// Get the color scheme that matches the contribution state
ContainerRenderColorTokens contributionColorTokens = (treatEnabledAsActive && (activeEntry.getKey() == ComponentState.ENABLED))
? RadianceColorSchemeUtilities.getActiveRenderColorTokens(component, activeEntry.getKey())
: RadianceColorSchemeUtilities.getRenderColorTokens(component, associationKind, activeEntry.getKey());

// And interpolate the colors
containerLowest = RadianceColorUtilities.getInterpolatedColor(containerLowest,
contributionColorTokens.getContainerColorTokens().getContainerLowest(), 1.0f - amount);
containerLow = RadianceColorUtilities.getInterpolatedColor(containerLow,
contributionColorTokens.getContainerColorTokens().getContainerLow(), 1.0f - amount);
container = RadianceColorUtilities.getInterpolatedColor(container,
contributionColorTokens.getContainerColorTokens().getContainer(), 1.0f - amount);
containerHigh = RadianceColorUtilities.getInterpolatedColor(containerHigh,
contributionColorTokens.getContainerColorTokens().getContainerHigh(), 1.0f - amount);
containerHighest = RadianceColorUtilities.getInterpolatedColor(containerHighest,
contributionColorTokens.getContainerColorTokens().getContainerHighest(), 1.0f - amount);
onContainer = RadianceColorUtilities.getInterpolatedColor(onContainer,
contributionColorTokens.getOnContainerColorTokens().getOnContainer(), 1.0f - amount);
onContainerVariant = RadianceColorUtilities.getInterpolatedColor(onContainerVariant,
contributionColorTokens.getOnContainerColorTokens().getOnContainerVariant(), 1.0f - amount);
containerOutline = RadianceColorUtilities.getInterpolatedColor(containerOutline,
contributionColorTokens.getContainerOutlineColorTokens().getContainerOutline(), 1.0f - amount);
containerOutlineVariant = RadianceColorUtilities.getInterpolatedColor(containerOutlineVariant,
contributionColorTokens.getContainerOutlineColorTokens().getContainerOutlineVariant(), 1.0f - amount);

nameBuilder.append(", [").append(contributionColorTokens.hashCode()).append(":")
.append(amount).append("]");
}
}

// Update the mutable color tokens with the interpolated colors
bladeRenderColorTokens.containerLowest = containerLowest;
bladeRenderColorTokens.containerLow = containerLow;
bladeRenderColorTokens.container = container;
bladeRenderColorTokens.containerHigh = containerHigh;
bladeRenderColorTokens.containerHighest = containerHighest;
bladeRenderColorTokens.onContainer = onContainer;
bladeRenderColorTokens.onContainerVariant = onContainerVariant;
bladeRenderColorTokens.containerOutline = containerOutline;
bladeRenderColorTokens.containerOutlineVariant = containerOutlineVariant;

bladeRenderColorTokens.combinedName = nameBuilder.toString();
}

public interface ColorSchemeDelegate {
RadianceColorScheme getColorSchemeForCurrentState(ComponentState state);
RadianceColorScheme getColorSchemeForActiveState(ComponentState state);
Expand Down Expand Up @@ -250,13 +339,13 @@ public RadianceColorScheme getColorSchemeForActiveState(ComponentState state) {

@Override
public ContainerRenderColorTokens getRenderColorTokensForCurrentState(ComponentState state) {
return RadianceColorSchemeUtilities.getColorRenderTokens(component,
return RadianceColorSchemeUtilities.getRenderColorTokens(component,
colorSchemeAssociationKindDelegate.getColorSchemeAssociationKind(state), state);
}

@Override
public ContainerRenderColorTokens getRenderColorTokensForActiveState(ComponentState state) {
return RadianceColorSchemeUtilities.getColorRenderTokens(component,
return RadianceColorSchemeUtilities.getRenderColorTokens(component,
colorSchemeAssociationKindDelegate.getColorSchemeAssociationKind(state), state);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.pushingpixels.radiance.theming.internal.animation.StateTransitionTracker;
import org.pushingpixels.radiance.theming.internal.animation.TransitionAwareUI;
import org.pushingpixels.radiance.theming.internal.blade.BladeColorScheme;
import org.pushingpixels.radiance.theming.internal.blade.BladeContainerRenderColorTokens;
import org.pushingpixels.radiance.theming.internal.blade.BladeUtils;

import javax.swing.*;
Expand All @@ -62,6 +63,7 @@
public class ButtonBackgroundDelegate {
private BladeColorScheme mutableFillColorScheme = new BladeColorScheme();
private BladeColorScheme mutableBorderColorScheme = new BladeColorScheme();
private BladeContainerRenderColorTokens mutableRenderColorTokens = new BladeContainerRenderColorTokens();

private void drawBackground(
Graphics2D graphics, AbstractButton button,
Expand Down Expand Up @@ -99,12 +101,11 @@ private void drawBackground(

RadianceSkin skin = RadianceCoreUtilities.getSkin(button);
if (skin instanceof TonalSkin) {
ContainerRenderColorTokens buttonRenderColorTokens =
skin.getColorRenderTokens(button, currState);
BladeUtils.populateColorTokens(mutableRenderColorTokens, button, modelStateInfo,
currState, RadianceThemingSlices.ColorSchemeAssociationKind.FILL, false);

// TODO: TONAL - add animations
drawBackground(graphics, button, shaper, fillPainter, borderPainter, width, height,
buttonRenderColorTokens, openSides, isContentAreaFilled, isBorderPainted);
mutableRenderColorTokens, openSides, isContentAreaFilled, isBorderPainted);
} else {
// Populate fill and border color schemes based on the current transition state of
// the button.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,35 @@ public static RadianceColorScheme getColorScheme(Component component,
return getColorizedScheme(orig, nonColorized, !componentState.isDisabled());
}

public static ContainerRenderColorTokens getRenderColorTokens(Component component,
ComponentState componentState) {
Component orig = component;
RadianceSkin skin = RadianceCoreUtilities.getSkin(component);
// special case - if the component is marked as flat and
// it is in the default state, or it is a button
// that is never painting its background - get the color scheme of the
// parent
boolean isButtonThatIsNeverPainted = ((component instanceof AbstractButton)
&& RadianceCoreUtilities.isComponentNeverPainted((AbstractButton) component));
if (isButtonThatIsNeverPainted
|| (RadianceCoreUtilities.hasFlatAppearance(component, false)
&& (componentState == ComponentState.ENABLED))) {
// TODO: TONAL - verify that we don't need to use the old logic.
// TODO: TONAL - colorization
return skin.getBackgroundRenderColorTokens( DecorationPainterUtils.getDecorationType(component));
// component = component.getParent();
}

if (skin == null) {
RadianceCoreUtilities.traceRadianceApiUsage(component,
"Radiance delegate used when Radiance is not the current LAF");
}
ContainerRenderColorTokens nonColorized = skin.getColorRenderTokens(component, componentState);
// TODO: TONAL - colorization
return nonColorized;
// return getColorizedScheme(orig, nonColorized, !componentState.isDisabled());
}

/**
* Returns the color scheme of the component.
*
Expand Down Expand Up @@ -215,7 +244,7 @@ public static RadianceColorScheme getColorScheme(Component component,
* @param componentState Component state.
* @return Component color scheme.
*/
public static ContainerRenderColorTokens getColorRenderTokens(Component component,
public static ContainerRenderColorTokens getRenderColorTokens(Component component,
RadianceThemingSlices.ColorSchemeAssociationKind associationKind,
ComponentState componentState) {
RadianceSkin skin = RadianceCoreUtilities.getSkin(component);
Expand Down Expand Up @@ -291,6 +320,25 @@ public static RadianceColorScheme getActiveColorScheme(Component component,
return getColorizedScheme(component, nonColorized, !componentState.isDisabled());
}

public static ContainerRenderColorTokens getActiveRenderColorTokens(Component component,
ComponentState componentState) {
// special case - if the component is marked as flat and
// it is in the enabled state, get the color scheme of the parent.
// However, flat toolbars should be ignored, since they are
// the "top" level decoration area.
if (!(component instanceof JToolBar)
&& RadianceCoreUtilities.hasFlatAppearance(component, false)
&& (componentState == ComponentState.ENABLED)) {
component = component.getParent();
}

ContainerRenderColorTokens nonColorized = RadianceCoreUtilities.getSkin(component)
.getActiveColorRenderTokens(RadianceThemingCortex.ComponentOrParentChainScope.getDecorationType(component));
// TODO: TONAL - colorization
return nonColorized;
// return getColorizedScheme(component, nonColorized, !componentState.isDisabled());
}

/**
* Returns the alpha channel of the highlight color scheme of the component.
*
Expand Down
Loading

0 comments on commit f21f77d

Please sign in to comment.