diff --git a/demos/theming-demo/src/main/java/org/pushingpixels/radiance/demo/theming/main/ControlStatesExtended.java b/demos/theming-demo/src/main/java/org/pushingpixels/radiance/demo/theming/main/ControlStatesExtended.java index ef2090d05..2351c78a7 100644 --- a/demos/theming-demo/src/main/java/org/pushingpixels/radiance/demo/theming/main/ControlStatesExtended.java +++ b/demos/theming-demo/src/main/java/org/pushingpixels/radiance/demo/theming/main/ControlStatesExtended.java @@ -31,11 +31,8 @@ import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.factories.Paddings; -import org.pushingpixels.radiance.theming.api.ComponentState; import org.pushingpixels.radiance.theming.api.RadianceThemingCortex; import org.pushingpixels.radiance.theming.api.RadianceThemingSlices; -import org.pushingpixels.radiance.theming.api.RadianceThemingSlices.ColorSchemeAssociationKind; -import org.pushingpixels.radiance.theming.api.RadianceThemingSlices.DecorationAreaType; import org.pushingpixels.radiance.theming.extras.api.skinpack.OfficeSilver2007Skin; import javax.swing.*; @@ -44,10 +41,7 @@ public class ControlStatesExtended extends JFrame { public ControlStatesExtended() { super("States"); - setIconImage(RadianceLogo.getLogoImage(this, - RadianceThemingCortex.ComponentScope.getCurrentSkin(this.getRootPane()) - .getColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE, - ColorSchemeAssociationKind.FILL, ComponentState.ENABLED))); + RadianceLogo.tonalConfigureOn(this); FormBuilder builder = FormBuilder.create(). columns("right:pref, 4dlu, fill:pref:grow"). diff --git a/theming-extras/build.gradle b/theming-extras/build.gradle index 2e45db843..679ca9ff5 100644 --- a/theming-extras/build.gradle +++ b/theming-extras/build.gradle @@ -34,6 +34,7 @@ dependencies { implementation project(':common') implementation project(':animation') implementation project(':theming') + implementation libs.ephemeral.chroma } ext.designation = "core" diff --git a/theming-extras/src/main/java/org/pushingpixels/radiance/theming/extras/api/skinpack/OfficeSilver2007Skin.java b/theming-extras/src/main/java/org/pushingpixels/radiance/theming/extras/api/skinpack/OfficeSilver2007Skin.java index 347ec98c1..db1e41013 100644 --- a/theming-extras/src/main/java/org/pushingpixels/radiance/theming/extras/api/skinpack/OfficeSilver2007Skin.java +++ b/theming-extras/src/main/java/org/pushingpixels/radiance/theming/extras/api/skinpack/OfficeSilver2007Skin.java @@ -29,22 +29,24 @@ */ package org.pushingpixels.radiance.theming.extras.api.skinpack; -import org.pushingpixels.radiance.theming.api.ComponentState; -import org.pushingpixels.radiance.theming.api.RadianceColorSchemeBundle; -import org.pushingpixels.radiance.theming.api.RadianceSkin; +import org.pushingpixels.ephemeral.chroma.hct.Hct; +import org.pushingpixels.radiance.theming.api.*; import org.pushingpixels.radiance.theming.api.RadianceThemingSlices.ColorSchemeAssociationKind; import org.pushingpixels.radiance.theming.api.RadianceThemingSlices.DecorationAreaType; -import org.pushingpixels.radiance.theming.api.colorscheme.ColorSchemeSingleColorQuery; import org.pushingpixels.radiance.theming.api.colorscheme.ColorTransform; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; import org.pushingpixels.radiance.theming.api.painter.border.CompositeBorderPainter; -import org.pushingpixels.radiance.theming.api.painter.border.DelegateFractionBasedBorderPainter; -import org.pushingpixels.radiance.theming.api.painter.border.FractionBasedBorderPainter; +import org.pushingpixels.radiance.theming.api.painter.border.DelegateFractionBasedTonalBorderPainter; +import org.pushingpixels.radiance.theming.api.painter.border.FractionBasedTonalBorderPainter; import org.pushingpixels.radiance.theming.api.painter.border.RadianceBorderPainter; -import org.pushingpixels.radiance.theming.api.painter.decoration.FractionBasedDecorationPainter; -import org.pushingpixels.radiance.theming.api.painter.fill.ClassicFillPainter; -import org.pushingpixels.radiance.theming.api.painter.fill.FractionBasedFillPainter; -import org.pushingpixels.radiance.theming.api.painter.overlay.BottomLineOverlayPainter; +import org.pushingpixels.radiance.theming.api.painter.decoration.FractionBasedTonalDecorationPainter; +import org.pushingpixels.radiance.theming.api.painter.fill.ClassicTonalFillPainter; +import org.pushingpixels.radiance.theming.api.painter.fill.FractionBasedTonalFillPainter; +import org.pushingpixels.radiance.theming.api.painter.overlay.BottomLineTonalOverlayPainter; +import org.pushingpixels.radiance.theming.api.palette.ColorSchemeUtils; +import org.pushingpixels.radiance.theming.api.palette.RadianceColorScheme2; +import org.pushingpixels.radiance.theming.api.palette.TonalSkin; import org.pushingpixels.radiance.theming.api.shaper.ClassicButtonShaper; /** @@ -53,7 +55,7 @@ * * @author Kirill Grouchnikov */ -public class OfficeSilver2007Skin extends RadianceSkin { +public class OfficeSilver2007Skin extends RadianceSkin implements TonalSkin { /** * Display name for this skin. */ @@ -216,47 +218,97 @@ public OfficeSilver2007Skin() { DecorationAreaType.PRIMARY_TITLE_PANE, DecorationAreaType.SECONDARY_TITLE_PANE); - this.addOverlayPainter(new BottomLineOverlayPainter( - ColorSchemeSingleColorQuery.composite(ColorSchemeSingleColorQuery.FOREGROUND, - ColorTransform.alpha(72))), - DecorationAreaType.PRIMARY_TITLE_PANE, - DecorationAreaType.SECONDARY_TITLE_PANE); + RadianceColorScheme2 officeSilverColorScheme = + ColorSchemeUtils.getLightTonalFidelityColorScheme(Hct.fromInt(0xFFC6CACF), + Hct.fromInt(0xFFE6EAEE), Hct.fromInt(0xFFF2F5F5)); + RadianceColorSchemeBundle2 officeSilverDefaultBundle = + new RadianceColorSchemeBundle2(officeSilverColorScheme); + + RadianceColorScheme2 rolloverScheme2 = ColorSchemeUtils.getLightTonalFidelityColorScheme( + Hct.fromInt(0xFFFFD111), Hct.fromInt(0xFFE6EAEE), Hct.fromInt(0xFFF2F5F5)); + RadianceColorScheme2 selectedScheme2 = ColorSchemeUtils.getLightTonalFidelityColorScheme( + Hct.fromInt(0xFFFFB340), Hct.fromInt(0xFFE6EAEE), Hct.fromInt(0xFFF2F5F5)); + RadianceColorScheme2 rolloverSelectedScheme2 = ColorSchemeUtils.getLightTonalFidelityColorScheme( + Hct.fromInt(0xFFFFA400), Hct.fromInt(0xFFE6EAEE), Hct.fromInt(0xFFF2F5F5)); + RadianceColorScheme2 pressedScheme2 = ColorSchemeUtils.getLightTonalFidelityColorScheme( + Hct.fromInt(0xFFFF8C18), Hct.fromInt(0xFFE6EAEE), Hct.fromInt(0xFFF2F5F5)); + RadianceColorScheme2 pressedSelectedScheme2 = ColorSchemeUtils.getLightTonalFidelityColorScheme( + Hct.fromInt(0xFFFF991C), Hct.fromInt(0xFFE6EAEE), Hct.fromInt(0xFFF2F5F5));; + + // register state-specific color schemes on rollovers, presses and selections + officeSilverDefaultBundle.registerColorScheme(rolloverScheme2, + ComponentState.ROLLOVER_UNSELECTED); + officeSilverDefaultBundle.registerColorScheme(rolloverSelectedScheme2, + ComponentState.ROLLOVER_SELECTED); + officeSilverDefaultBundle.registerColorScheme(selectedScheme2, + ComponentState.SELECTED); + officeSilverDefaultBundle.registerColorScheme(pressedScheme2, + ComponentState.PRESSED_UNSELECTED); + officeSilverDefaultBundle.registerColorScheme(pressedSelectedScheme2, + ComponentState.PRESSED_SELECTED); + + // register state-specific highlight color schemes on rollover and selections + officeSilverDefaultBundle.registerColorScheme(rolloverScheme2, + RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT, + ComponentState.ROLLOVER_UNSELECTED); + officeSilverDefaultBundle.registerColorScheme(selectedScheme2, + RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT, + ComponentState.SELECTED, ComponentState.ARMED, ComponentState.ROLLOVER_ARMED); + officeSilverDefaultBundle.registerColorScheme(rolloverSelectedScheme2, + RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT, + ComponentState.ROLLOVER_SELECTED); + + // marks + RadianceColorScheme2 markEnabledScheme2 = ColorSchemeUtils.getLightTonalFidelityColorScheme( + Hct.fromInt(0xFFFDD07C), Hct.fromInt(0xFFF1F3F3), Hct.fromInt(0xFFF2F5F5)); + officeSilverDefaultBundle.registerColorScheme(markEnabledScheme2, + RadianceThemingSlices.ContainerColorTokensAssociationKind.MARK, ComponentState.ENABLED); + + this.registerDecorationAreaSchemeBundle(officeSilverDefaultBundle, + RadianceThemingSlices.DecorationAreaType.NONE); + + this.addOverlayPainter(new BottomLineTonalOverlayPainter( + ContainerColorTokensSingleColorQuery.composite( + ContainerColorTokensSingleColorQuery.CONTAINER_OUTLINE, ColorTransform.alpha(72))), + DecorationAreaType.PRIMARY_TITLE_PANE, + DecorationAreaType.SECONDARY_TITLE_PANE); this.buttonShaper = new ClassicButtonShaper(); - this.fillPainter = new FractionBasedFillPainter("Office Silver 2007", + this.fillPainter = new FractionBasedTonalFillPainter("Office Silver 2007", new float[] {0.0f, 0.49999f, 0.5f, 1.0f}, - new ColorSchemeSingleColorQuery[] { - ColorSchemeSingleColorQuery.ULTRALIGHT, - ColorSchemeSingleColorQuery.LIGHT, - ColorSchemeSingleColorQuery.ULTRADARK, - ColorSchemeSingleColorQuery.EXTRALIGHT}); + new ContainerColorTokensSingleColorQuery[] { + ContainerColorTokensSingleColorQuery.CONTAINER_LOW, + ContainerColorTokensSingleColorQuery.CONTAINER_LOWEST, + ContainerColorTokensSingleColorQuery.CONTAINER, + ContainerColorTokensSingleColorQuery.CONTAINER_LOW}); - FractionBasedBorderPainter outerBorderPainter = new FractionBasedBorderPainter( + FractionBasedTonalBorderPainter outerBorderPainter = new FractionBasedTonalBorderPainter( "Office Silver 2007 Outer", new float[] {0.0f, 0.5f, 1.0f}, - new ColorSchemeSingleColorQuery[] { - ColorSchemeSingleColorQuery.LIGHT, - ColorSchemeSingleColorQuery.ULTRADARK, - ColorSchemeSingleColorQuery.MID}); - RadianceBorderPainter innerBorderPainter = new DelegateFractionBasedBorderPainter( + new ContainerColorTokensSingleColorQuery[] { + ContainerColorTokensSingleColorQuery.CONTAINER_LOW, + ContainerColorTokensSingleColorQuery.CONTAINER_HIGHEST, + ContainerColorTokensSingleColorQuery.CONTAINER + }); + RadianceBorderPainter innerBorderPainter = new DelegateFractionBasedTonalBorderPainter( "Office Silver 2007 Inner", outerBorderPainter, new int[] {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, - scheme -> scheme.tint(0.8f)); + scheme -> ColorSchemeUtils.tint(scheme, 0.8f)); this.borderPainter = new CompositeBorderPainter("Office Silver 2007", outerBorderPainter, innerBorderPainter); - this.decorationPainter = new FractionBasedDecorationPainter( + this.decorationPainter = new FractionBasedTonalDecorationPainter( "Office Silver 2007", new float[] {0.0f, 0.2499999f, 0.25f, 0.3f, 0.7f, 1.0f}, - new ColorSchemeSingleColorQuery[] { - ColorSchemeSingleColorQuery.ULTRALIGHT, - ColorSchemeSingleColorQuery.EXTRALIGHT, - ColorSchemeSingleColorQuery.DARK, - ColorSchemeSingleColorQuery.MID, - ColorSchemeSingleColorQuery.LIGHT, - ColorSchemeSingleColorQuery.ULTRALIGHT}); - - this.highlightFillPainter = new ClassicFillPainter(); + new ContainerColorTokensSingleColorQuery[] { + ContainerColorTokensSingleColorQuery.CONTAINER_LOWEST, + ContainerColorTokensSingleColorQuery.CONTAINER_LOW, + ContainerColorTokensSingleColorQuery.CONTAINER_HIGH, + ContainerColorTokensSingleColorQuery.CONTAINER, + ContainerColorTokensSingleColorQuery.CONTAINER_LOW, + ContainerColorTokensSingleColorQuery.CONTAINER_LOWEST}); + + this.highlightFillPainter = new ClassicTonalFillPainter(); } @Override diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/RadianceColorSchemeBundle2.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/RadianceColorSchemeBundle2.java index b849f40ae..23ad270d6 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/RadianceColorSchemeBundle2.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/RadianceColorSchemeBundle2.java @@ -33,7 +33,10 @@ import org.pushingpixels.radiance.theming.api.palette.ExtendedContainerColorTokens; import org.pushingpixels.radiance.theming.api.palette.RadianceColorScheme2; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; /** * Color scheme bundle. Defines the visual appearance of a single decoration area of a skin. @@ -59,7 +62,7 @@ public class RadianceColorSchemeBundle2 { * scheme. This map doesn't have to contain entries for all * {@link ComponentState} instances. */ - private Map stateHighlightAlphaMap; + //private Map stateHighlightAlphaMap; /** * Maps from color scheme association kinds to the map of color schemes. @@ -99,7 +102,7 @@ public RadianceColorSchemeBundle2(RadianceColorScheme2 mainColorScheme) { this.mainColorScheme = mainColorScheme; this.stateAlphaMap = new HashMap<>(); - this.stateHighlightAlphaMap = new HashMap<>(); + //this.stateHighlightAlphaMap = new HashMap<>(); this.colorSchemeMap = new HashMap<>(); for (RadianceThemingSlices.ContainerColorTokensAssociationKind associationKind : @@ -153,29 +156,29 @@ public void registerColorScheme(RadianceColorScheme2 stateColorScheme, Component * @param states Component states. If null, the specified color * scheme will be applied for all states left unspecified. */ - public void registerHighlightColorScheme(RadianceColorScheme2 stateHighlightScheme, ComponentState... states) { - if (stateHighlightScheme == null) { - throw new IllegalArgumentException("Cannot pass null color scheme"); - } - if ((states == null) || (states.length == 0)) { - for (ComponentState state : ComponentState.getAllStates()) { - if (this.colorSchemeMap.get(RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT).containsKey(state)) { - continue; - } - if (state.isDisabled()) { - continue; - } - if (state == ComponentState.ENABLED) { - continue; - } - this.colorSchemeMap.get(RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT).put(state, stateHighlightScheme); - } - } else { - for (ComponentState state : states) { - this.colorSchemeMap.get(RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT).put(state, stateHighlightScheme); - } - } - } +// public void registerHighlightColorScheme(RadianceColorScheme2 stateHighlightScheme, ComponentState... states) { +// if (stateHighlightScheme == null) { +// throw new IllegalArgumentException("Cannot pass null color scheme"); +// } +// if ((states == null) || (states.length == 0)) { +// for (ComponentState state : ComponentState.getAllStates()) { +// if (this.colorSchemeMap.get(RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT).containsKey(state)) { +// continue; +// } +// if (state.isDisabled()) { +// continue; +// } +// if (state == ComponentState.ENABLED) { +// continue; +// } +// this.colorSchemeMap.get(RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT).put(state, stateHighlightScheme); +// } +// } else { +// for (ComponentState state : states) { +// this.colorSchemeMap.get(RadianceThemingSlices.ContainerColorTokensAssociationKind.HIGHLIGHT).put(state, stateHighlightScheme); +// } +// } +// } /** * Registers a highlight alpha channel value for the specific component states. @@ -183,17 +186,17 @@ public void registerHighlightColorScheme(RadianceColorScheme2 stateHighlightSche * @param alpha Highlight alpha channel value. * @param states Component states. */ - public void registerHighlightAlpha(float alpha, ComponentState... states) { - if ((states == null) || (states.length == 0)) { - for (ComponentState state : ComponentState.getAllStates()) { - this.stateHighlightAlphaMap.put(state, alpha); - } - } else { - for (ComponentState state : states) { - this.stateHighlightAlphaMap.put(state, alpha); - } - } - } +// public void registerHighlightAlpha(float alpha, ComponentState... states) { +// if ((states == null) || (states.length == 0)) { +// for (ComponentState state : ComponentState.getAllStates()) { +// this.stateHighlightAlphaMap.put(state, alpha); +// } +// } else { +// for (ComponentState state : states) { +// this.stateHighlightAlphaMap.put(state, alpha); +// } +// } +// } /** * Returns the color scheme of the specified component in the specified @@ -212,7 +215,7 @@ public ContainerColorTokens getContainerTokens(ComponentState componentState, RadianceColorScheme2 registered = this.colorSchemeMap.get( RadianceThemingSlices.ContainerColorTokensAssociationKind.DEFAULT).get(componentState); if (registered != null) { - return componentState.isActive() ? registered.getContainerTokensForState(componentState) + return componentState.isActive() ? registered.getActiveContainerTokens() : registered.getContainerTokens(inactiveContainerType); } @@ -251,9 +254,9 @@ public ContainerColorTokens getSystemContainerTokens( } } - public boolean hasHighlightAlphaFor(ComponentState componentState) { - return this.stateHighlightAlphaMap.containsKey(componentState); - } +// public boolean hasHighlightAlphaFor(ComponentState componentState) { +// return this.stateHighlightAlphaMap.containsKey(componentState); +// } /** * Returns the alpha channel of the highlight color schemes for the specified component state. @@ -263,14 +266,14 @@ public boolean hasHighlightAlphaFor(ComponentState componentState) { * @param componentState Component state. * @return Highlight color scheme alpha channel. */ - public float getHighlightAlpha(ComponentState componentState) { - Float registered = this.stateHighlightAlphaMap.get(componentState); - if (registered != null) { - return registered.floatValue(); - } - - return 1.0f; - } +// public float getHighlightAlpha(ComponentState componentState) { +// Float registered = this.stateHighlightAlphaMap.get(componentState); +// if (registered != null) { +// return registered.floatValue(); +// } +// +// return 1.0f; +// } public boolean hasAlphaFor(ComponentState componentState) { return this.stateAlphaMap.containsKey(componentState); @@ -379,24 +382,32 @@ public ContainerColorTokens getContainerTokens( RadianceColorScheme2 registered = this.colorSchemeMap.get(associationKind).get(componentState); if (registered != null) { - return componentState.isActive() ? registered.getContainerTokensForState(componentState) + return componentState.isActive() ? registered.getActiveContainerTokens() : registered.getContainerTokens(inactiveContainerType); } - // for now look for the best fit only on active states - Map bestFitForState = this.bestFillMap.get(associationKind); - if (!bestFitForState.containsKey(componentState)) { - Collection registeredStates = this.colorSchemeMap.get(associationKind).keySet(); - bestFitForState.put(componentState, componentState.bestFit(registeredStates)); - } - ComponentState bestFit = bestFitForState.get(componentState); - if (bestFit != null) { - registered = this.colorSchemeMap.get(associationKind).get(bestFit); - if (registered != null) - return componentState.isActive() ? registered.getContainerTokensForState(componentState) - : registered.getContainerTokens(inactiveContainerType); + RadianceColorScheme2 enabledForAssociationKind = + this.colorSchemeMap.get(associationKind).get(ComponentState.ENABLED); + if (enabledForAssociationKind != null) { + return componentState.isActive() + ? enabledForAssociationKind.getContainerTokensForState(componentState) + : enabledForAssociationKind.getContainerTokens(inactiveContainerType); } +// // for now look for the best fit only on active states +// Map bestFitForState = this.bestFillMap.get(associationKind); +// if (!bestFitForState.containsKey(componentState)) { +// Collection registeredStates = this.colorSchemeMap.get(associationKind).keySet(); +// bestFitForState.put(componentState, componentState.bestFit(registeredStates)); +// } +// ComponentState bestFit = bestFitForState.get(componentState); +// if (bestFit != null) { +// registered = this.colorSchemeMap.get(associationKind).get(bestFit); +// if (registered != null) +// return componentState.isActive() ? registered.getContainerTokensForState(componentState) +// : registered.getContainerTokens(inactiveContainerType); +// } + if (!allowFallback) { return null; } diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/ContainerColorTokensSingleColorQuery.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/colorscheme/ContainerColorTokensSingleColorQuery.java similarity index 85% rename from theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/ContainerColorTokensSingleColorQuery.java rename to theming/src/main/java/org/pushingpixels/radiance/theming/api/colorscheme/ContainerColorTokensSingleColorQuery.java index 12dddb7d5..fe74751f5 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/ContainerColorTokensSingleColorQuery.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/colorscheme/ContainerColorTokensSingleColorQuery.java @@ -27,7 +27,9 @@ * 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.api.palette; +package org.pushingpixels.radiance.theming.api.colorscheme; + +import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokens; import java.awt.*; @@ -56,4 +58,14 @@ public interface ContainerColorTokensSingleColorQuery { ContainerColorTokensSingleColorQuery CONTAINER_OUTLINE_VARIANT = (colorTokens) -> colorTokens.getContainerOutlineVariant(); + static ContainerColorTokensSingleColorQuery composite( + ContainerColorTokensSingleColorQuery base, ColorTransform... transforms) { + return colorTokens -> { + Color result = base.query(colorTokens); + for (ColorTransform transform: transforms) { + result = transform.transform(result); + } + return result; + }; + } } diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/colorscheme/ContainerColorTokensTransform.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/colorscheme/ContainerColorTokensTransform.java new file mode 100644 index 000000000..81b1a6d4e --- /dev/null +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/colorscheme/ContainerColorTokensTransform.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005-2025 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.api.colorscheme; + +import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokens; + +/** + * Defines a transformation on a color scheme. + * + * @author Kirill Grouchnikov + */ +@FunctionalInterface +public interface ContainerColorTokensTransform { + /** + * Transforms the specified color tokens. + * + * @param containerColorTokens + * The original color tokens to transform. + * @return The transformed color tokens. + */ + ContainerColorTokens transform(ContainerColorTokens containerColorTokens); +} diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/FractionBasedTonalPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/FractionBasedTonalPainter.java index 9c8929ff4..c9220d25e 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/FractionBasedTonalPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/FractionBasedTonalPainter.java @@ -30,8 +30,8 @@ package org.pushingpixels.radiance.theming.api.painter; import org.pushingpixels.radiance.theming.api.colorscheme.ColorSchemeSingleColorQuery; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.trait.RadianceTrait; /** @@ -132,10 +132,11 @@ public float[] getFractions() { * * @return Color queries of this painter. */ - public ColorSchemeSingleColorQuery[] getColorQueries() { - ColorSchemeSingleColorQuery[] result = new ColorSchemeSingleColorQuery[this.colorQueries.length]; + public ContainerColorTokensSingleColorQuery[] getColorQueries() { + ContainerColorTokensSingleColorQuery[] result = + new ContainerColorTokensSingleColorQuery[this.colorQueries.length]; System.arraycopy(this.colorQueries, 0, result, 0, - this.colorQueries.length); + this.colorQueries.length); return result; } } diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/ClassicTonalBorderPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/ClassicTonalBorderPainter.java index bea11bb80..708e3f2df 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/ClassicTonalBorderPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/ClassicTonalBorderPainter.java @@ -29,7 +29,7 @@ */ package org.pushingpixels.radiance.theming.api.painter.border; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; /** * Border painter that draws visuals with classic appearance. This class is diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/CompositeBorderPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/CompositeBorderPainter.java index fab1a3fbb..e888914ab 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/CompositeBorderPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/CompositeBorderPainter.java @@ -30,6 +30,7 @@ package org.pushingpixels.radiance.theming.api.painter.border; import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; +import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokens; import java.awt.*; @@ -81,12 +82,21 @@ public boolean isPaintingInnerContour() { public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, Shape innerContour, RadianceColorScheme borderScheme) { if (innerContour != null) { - this.inner.paintBorder(g, c, width, height, innerContour, null, - borderScheme); + this.inner.paintBorder(g, c, width, height, innerContour, null, borderScheme); } if (contour != null) { - this.outer.paintBorder(g, c, width, height, contour, null, - borderScheme); + this.outer.paintBorder(g, c, width, height, contour, null, borderScheme); + } + } + + @Override + public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, + Shape innerContour, ContainerColorTokens colorTokens) { + if (innerContour != null) { + this.inner.paintBorder(g, c, width, height, innerContour, null, colorTokens); + } + if (contour != null) { + this.outer.paintBorder(g, c, width, height, contour, null, colorTokens); } } diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/DelegateFractionBasedTonalBorderPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/DelegateFractionBasedTonalBorderPainter.java new file mode 100644 index 000000000..51bba4296 --- /dev/null +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/DelegateFractionBasedTonalBorderPainter.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2005-2025 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.api.painter.border; + +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensTransform; +import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; +import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokens; +import org.pushingpixels.radiance.theming.internal.utils.HashMapKey; +import org.pushingpixels.radiance.theming.internal.utils.LazyResettableHashMap; +import org.pushingpixels.radiance.theming.internal.utils.RadianceCoreUtilities; +import org.pushingpixels.radiance.theming.internal.utils.RadianceInternalArrowButton; + +import java.awt.*; +import java.awt.MultipleGradientPaint.CycleMethod; + +/** + * Delegate border painter that allows tweaking the visual appearance of + * borders. + * + * @author Kirill Grouchnikov + */ +public class DelegateFractionBasedTonalBorderPainter implements RadianceBorderPainter { + /** + * Display name of this border painter. + */ + protected String displayName; + + /** + * The delegate border painter. + */ + protected FractionBasedTonalBorderPainter delegate; + + /** + * 8-digit hexadecimal masks applied on the colors painted by + * {@link #delegate}. Can be used to apply custom translucency. For example, + * value 0x80FFFFFF will result in 50% translucency of the original border + * color. + */ + protected int[] masks; + + /** + * Transformation to be applied on the color schemes prior to compute the + * colors to be used for border painting. + */ + protected ContainerColorTokensTransform transform; + + /** + * Creates a new delegate border painter + * + * @param displayName + * Display name of this border painter. + * @param delegate + * The delegate border painter. + * @param masks + * Array of 8-digit hexadecimal masks applied on the relevant + * colors painted by the delegate. + * @param transform + * Transformation to be applied on the color schemes prior to + * compute the colors to be used for border painting. + */ + public DelegateFractionBasedTonalBorderPainter(String displayName, + FractionBasedTonalBorderPainter delegate, int[] masks, + ContainerColorTokensTransform transform) { + + this.displayName = displayName; + this.delegate = delegate; + this.masks = new int[masks.length]; + System.arraycopy(masks, 0, this.masks, 0, masks.length); + this.transform = transform; + } + + /** + * Map of transformed color tokens (to speed up the subsequent lookups). + */ + protected final static LazyResettableHashMap transformMap = + new LazyResettableHashMap<>("DelegateFractionBasedTonalBorderPainter"); + + @Override + public boolean isPaintingInnerContour() { + return false; + } + + @Override + public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, + Shape innerContour, ContainerColorTokens colorTokens) { + Graphics2D graphics = (Graphics2D) g.create(); + + // shift color tokens + ContainerColorTokens shifted = getShiftColorTokens(colorTokens); + + float[] fractions = delegate.getFractions(); + ContainerColorTokensSingleColorQuery[] colorQueries = delegate.getColorQueries(); + Color[] fillColors = new Color[fractions.length]; + for (int i = 0; i < fractions.length; i++) { + ContainerColorTokensSingleColorQuery colorQuery = colorQueries[i]; + Color color = colorQuery.query(shifted); + // apply masks + color = new Color(this.masks[i] & color.getRGB(), true); + fillColors[i] = color; + } + + // issue 433 - the "c" can be null when painting + // the border of a tree icon used outside the + // JTree context. + boolean isSpecialButton = (c != null) && c.getClass() + .isAnnotationPresent(RadianceInternalArrowButton.class); + int joinKind = isSpecialButton ? BasicStroke.JOIN_MITER : BasicStroke.JOIN_ROUND; + int capKind = isSpecialButton ? BasicStroke.CAP_SQUARE : BasicStroke.CAP_BUTT; + graphics.setStroke(new BasicStroke(1.0f, capKind, joinKind)); + + MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0, height, fractions, + fillColors, CycleMethod.REPEAT); + graphics.setPaint(gradient); + graphics.draw(contour); + graphics.dispose(); + } + + @Override + public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, + Shape innerContour, RadianceColorScheme borderScheme) { + + } + + @Override + public String getDisplayName() { + return this.displayName; + } + + /** + * Retrieves a transformed color scheme. + * + * @param orig + * Original color scheme. + * @return Transformed color scheme. + */ + private ContainerColorTokens getShiftColorTokens(ContainerColorTokens orig) { + HashMapKey key = RadianceCoreUtilities.getHashKey(orig.hashCode(), + this.getDisplayName(), this.transform); + ContainerColorTokens result = transformMap.get(key); + if (result == null) { + result = this.transform.transform(orig); + transformMap.put(key, result); + } + return result; + } + + @Override + public Color getRepresentativeColor(RadianceColorScheme borderScheme) { + return Color.BLACK; + } +} diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/FractionBasedTonalBorderPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/FractionBasedTonalBorderPainter.java index 837d039d7..e791f2fd0 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/FractionBasedTonalBorderPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/border/FractionBasedTonalBorderPainter.java @@ -29,10 +29,10 @@ */ package org.pushingpixels.radiance.theming.api.painter.border; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; import org.pushingpixels.radiance.theming.api.painter.FractionBasedTonalPainter; import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokens; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.internal.utils.RadianceInternalArrowButton; import java.awt.*; @@ -66,7 +66,8 @@ public FractionBasedTonalBorderPainter(String displayName, float[] fractions, } @Override - public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, Shape innerContour, ContainerColorTokens colorTokens) { + public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, + Shape innerContour, ContainerColorTokens colorTokens) { if (contour == null) return; @@ -95,7 +96,8 @@ public void paintBorder(Graphics g, Component c, float width, float height, Shap } @Override - public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, Shape innerContour, RadianceColorScheme borderScheme) { + public void paintBorder(Graphics g, Component c, float width, float height, Shape contour, + Shape innerContour, RadianceColorScheme borderScheme) { } @Override diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/FractionBasedDecorationPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/FractionBasedDecorationPainter.java index e38b99493..508d5d64c 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/FractionBasedDecorationPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/FractionBasedDecorationPainter.java @@ -29,8 +29,8 @@ */ package org.pushingpixels.radiance.theming.api.painter.decoration; -import org.pushingpixels.radiance.theming.api.RadianceThemingSlices; import org.pushingpixels.radiance.theming.api.RadianceSkin; +import org.pushingpixels.radiance.theming.api.RadianceThemingSlices; import org.pushingpixels.radiance.theming.api.colorscheme.ColorSchemeSingleColorQuery; import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; import org.pushingpixels.radiance.theming.api.painter.FractionBasedPainter; @@ -39,6 +39,7 @@ import javax.swing.*; import java.awt.*; import java.awt.MultipleGradientPaint.CycleMethod; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -96,16 +97,14 @@ public FractionBasedDecorationPainter(String displayName, super(displayName, fractions, colorQueries); this.decoratedAreas = new HashSet<>(); if (decorationAreas != null) { - for (RadianceThemingSlices.DecorationAreaType decorationArea : decorationAreas) { - this.decoratedAreas.add(decorationArea); - } + this.decoratedAreas.addAll(Arrays.asList(decorationAreas)); } } @Override public void paintDecorationArea(Graphics2D graphics, Component comp, - RadianceThemingSlices.DecorationAreaType decorationAreaType, int width, int height, - RadianceSkin skin) { + RadianceThemingSlices.DecorationAreaType decorationAreaType, int width, int height, + RadianceSkin skin) { RadianceColorScheme colorScheme = skin.getBackgroundColorScheme(decorationAreaType); if (this.decoratedAreas.contains(decorationAreaType)) { this.paintDecoratedBackground(graphics, comp, decorationAreaType, @@ -116,8 +115,9 @@ public void paintDecorationArea(Graphics2D graphics, Component comp, } @Override - public void paintDecorationArea(Graphics2D graphics, Component comp, RadianceThemingSlices.DecorationAreaType - decorationAreaType, Shape contour, RadianceColorScheme colorScheme) { + public void paintDecorationArea(Graphics2D graphics, Component comp, + RadianceThemingSlices.DecorationAreaType decorationAreaType, Shape contour, + RadianceColorScheme colorScheme) { if (this.decoratedAreas.contains(decorationAreaType)) { this.paintDecoratedBackground(graphics, comp, decorationAreaType, contour, colorScheme); @@ -127,8 +127,8 @@ public void paintDecorationArea(Graphics2D graphics, Component comp, RadianceThe } private void paintDecoratedBackground(Graphics2D graphics, Component comp, - RadianceThemingSlices.DecorationAreaType decorationAreaType, int width, int height, - RadianceColorScheme scheme) { + RadianceThemingSlices.DecorationAreaType decorationAreaType, int width, int height, + RadianceColorScheme scheme) { Graphics2D g2d = (Graphics2D) graphics.create(); Color[] fillColors = new Color[this.fractions.length]; for (int i = 0; i < this.fractions.length; i++) { @@ -154,8 +154,8 @@ private void paintDecoratedBackground(Graphics2D graphics, Component comp, } private void paintDecoratedBackground(Graphics2D graphics, Component comp, - RadianceThemingSlices.DecorationAreaType decorationAreaType, Shape contour, - RadianceColorScheme scheme) { + RadianceThemingSlices.DecorationAreaType decorationAreaType, Shape contour, + RadianceColorScheme scheme) { Graphics2D g2d = (Graphics2D) graphics.create(); Color[] fillColors = new Color[this.fractions.length]; for (int i = 0; i < this.fractions.length; i++) { @@ -180,14 +180,12 @@ private void paintDecoratedBackground(Graphics2D graphics, Component comp, g2d.dispose(); } - private void paintSolidBackground(Graphics2D graphics, - int width, int height, RadianceColorScheme scheme) { + private void paintSolidBackground(Graphics2D graphics, int width, int height, RadianceColorScheme scheme) { graphics.setColor(scheme.getMidColor()); graphics.fillRect(0, 0, width, height); } - private void paintSolidBackground(Graphics2D graphics, - Shape contour, RadianceColorScheme scheme) { + private void paintSolidBackground(Graphics2D graphics, Shape contour, RadianceColorScheme scheme) { graphics.setColor(scheme.getMidColor()); graphics.fill(contour); } diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/FractionBasedTonalDecorationPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/FractionBasedTonalDecorationPainter.java new file mode 100644 index 000000000..6bec1c4f1 --- /dev/null +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/FractionBasedTonalDecorationPainter.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2005-2025 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.api.painter.decoration; + +import org.pushingpixels.radiance.theming.api.RadianceSkin; +import org.pushingpixels.radiance.theming.api.RadianceThemingSlices; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; +import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; +import org.pushingpixels.radiance.theming.api.painter.FractionBasedTonalPainter; +import org.pushingpixels.radiance.theming.api.palette.ExtendedContainerColorTokens; +import org.pushingpixels.radiance.theming.internal.utils.RadianceCoreUtilities; + +import javax.swing.*; +import java.awt.*; +import java.awt.MultipleGradientPaint.CycleMethod; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Decoration painter with fraction-based stops and a color query associated + * with each stop. This class allows creating multi-gradient decorations with + * exact control over which color is used at every gradient control point. + * + * @author Kirill Grouchnikov + */ +public class FractionBasedTonalDecorationPainter extends FractionBasedTonalPainter + implements RadianceDecorationPainter { + private Set decoratedAreas; + + /** + * Creates a new fraction-based decoration painter. + * + * @param displayName + * The display name of this painter. + * @param fractions + * The fractions of this painter. Must be strictly increasing, + * starting from 0.0 and ending at 1.0. + * @param colorQueries + * The color queries of this painter. Must have the same size as + * the fractions array, and all entries must be non- + * null. + */ + public FractionBasedTonalDecorationPainter(String displayName, + float[] fractions, ContainerColorTokensSingleColorQuery[] colorQueries) { + this(displayName, fractions, colorQueries, + RadianceThemingSlices.DecorationAreaType.PRIMARY_TITLE_PANE, + RadianceThemingSlices.DecorationAreaType.SECONDARY_TITLE_PANE); + } + + /** + * Creates a new fraction-based decoration painter. + * + * @param displayName + * The display name of this painter. + * @param fractions + * The fractions of this painter. Must be strictly increasing, + * starting from 0.0 and ending at 1.0. + * @param colorQueries + * The color queries of this painter. Must have the same size as + * the fractions array, and all entries must be non- + * null. + * @param decorationAreas + * Decoration areas that should be painted based on the color + * queries. All the rest will be filled with a solid color from + * the background color scheme of the matching decoration area. + */ + public FractionBasedTonalDecorationPainter(String displayName, + float[] fractions, ContainerColorTokensSingleColorQuery[] colorQueries, + RadianceThemingSlices.DecorationAreaType... decorationAreas) { + super(displayName, fractions, colorQueries); + this.decoratedAreas = new HashSet<>(); + if (decorationAreas != null) { + this.decoratedAreas.addAll(Arrays.asList(decorationAreas)); + } + } + + @Override + public void paintDecorationArea(Graphics2D graphics, Component comp, + RadianceThemingSlices.DecorationAreaType decorationAreaType, int width, int height, + RadianceSkin skin) { + ExtendedContainerColorTokens colorTokens = + skin.getBackgroundExtendedContainerTokens(decorationAreaType); + if (this.decoratedAreas.contains(decorationAreaType)) { + this.paintDecoratedBackground(graphics, comp, decorationAreaType, + width, height, colorTokens); + } else { + this.paintSolidBackground(graphics, width, height, colorTokens); + } + } + + @Override + public void paintDecorationArea(Graphics2D graphics, Component comp, + RadianceThemingSlices.DecorationAreaType decorationAreaType, Shape contour, + RadianceColorScheme colorScheme) { + } + + @Override + public void paintDecorationArea(Graphics2D graphics, Component comp, + RadianceThemingSlices.DecorationAreaType decorationAreaType, Shape contour, + ExtendedContainerColorTokens colorTokens) { + + if (this.decoratedAreas.contains(decorationAreaType)) { + this.paintDecoratedBackground(graphics, comp, decorationAreaType, + contour, colorTokens); + } else { + this.paintSolidBackground(graphics, contour, colorTokens); + } + } + + private void paintDecoratedBackground(Graphics2D graphics, Component comp, + RadianceThemingSlices.DecorationAreaType decorationAreaType, int width, int height, + ExtendedContainerColorTokens colorTokens) { + + Graphics2D g2d = (Graphics2D) graphics.create(); + Color[] fillColors = new Color[this.fractions.length]; + for (int i = 0; i < this.fractions.length; i++) { + ContainerColorTokensSingleColorQuery colorQuery = this.colorQueries[i]; + fillColors[i] = colorQuery.query(colorTokens.getBaseContainerTokens()); + } + + Component topMostWithSameDecorationAreaType = RadianceCoreUtilities + .getTopMostParentWithDecorationAreaType(comp, + decorationAreaType); + Point inTopMost = SwingUtilities.convertPoint(comp, new Point(0, 0), + topMostWithSameDecorationAreaType); + int dy = inTopMost.y; + + MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0, + topMostWithSameDecorationAreaType.getHeight(), this.fractions, + fillColors, CycleMethod.REPEAT); + g2d.setPaint(gradient); + g2d.translate(0, -dy); + g2d.fillRect(0, 0, width, topMostWithSameDecorationAreaType.getHeight()); + + g2d.dispose(); + } + + private void paintDecoratedBackground(Graphics2D graphics, Component comp, + RadianceThemingSlices.DecorationAreaType decorationAreaType, Shape contour, + ExtendedContainerColorTokens colorTokens) { + + Graphics2D g2d = (Graphics2D) graphics.create(); + Color[] fillColors = new Color[this.fractions.length]; + for (int i = 0; i < this.fractions.length; i++) { + ContainerColorTokensSingleColorQuery colorQuery = this.colorQueries[i]; + fillColors[i] = colorQuery.query(colorTokens.getBaseContainerTokens()); + } + + Component topMostWithSameDecorationAreaType = RadianceCoreUtilities + .getTopMostParentWithDecorationAreaType(comp, + decorationAreaType); + Point inTopMost = SwingUtilities.convertPoint(comp, new Point(0, 0), + topMostWithSameDecorationAreaType); + int dy = inTopMost.y; + + MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0, + topMostWithSameDecorationAreaType.getHeight(), this.fractions, + fillColors, CycleMethod.REPEAT); + g2d.setPaint(gradient); + g2d.translate(0, -dy); + g2d.fill(contour); + + g2d.dispose(); + } + + private void paintSolidBackground(Graphics2D graphics, int width, int height, + ExtendedContainerColorTokens colorTokens) { + + graphics.setColor(colorTokens.getBaseContainerTokens().getContainerSurface()); + graphics.fillRect(0, 0, width, height); + } + + private void paintSolidBackground(Graphics2D graphics, Shape contour, + ExtendedContainerColorTokens colorTokens) { + + graphics.setColor(colorTokens.getBaseContainerTokens().getContainerSurface()); + graphics.fill(contour); + } +} diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/MatteDecorationPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/MatteDecorationPainter.java index 8978fcbbd..d476723ec 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/MatteDecorationPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/decoration/MatteDecorationPainter.java @@ -168,10 +168,8 @@ protected void fill(Graphics2D graphics, ExtendedContainerColorTokens colorToken // 0 - flex : light -> medium // flex - : medium fill - Color startColor = colorTokens.getBaseContainerTokens() - .getContainerSurfaceLowest(); - Color endColor = colorTokens.getBaseContainerTokens() - .getContainerSurface(); + Color startColor = colorTokens.getBaseContainerTokens().getContainerSurfaceLowest(); + Color endColor = colorTokens.getBaseContainerTokens().getContainerSurface(); int gradientHeight = Math.max(FLEX_POINT, height + offsetY); Paint paint = (gradientHeight == FLEX_POINT) ? diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/ClassicTonalFillPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/ClassicTonalFillPainter.java index 8222a73df..573a902cd 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/ClassicTonalFillPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/ClassicTonalFillPainter.java @@ -29,7 +29,7 @@ */ package org.pushingpixels.radiance.theming.api.painter.fill; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; /** * Fill painter that draws visuals with classic appearance. This class is part diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/FractionBasedTonalFillPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/FractionBasedTonalFillPainter.java index 235e47ceb..4cd7ed356 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/FractionBasedTonalFillPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/FractionBasedTonalFillPainter.java @@ -29,10 +29,10 @@ */ package org.pushingpixels.radiance.theming.api.painter.fill; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; import org.pushingpixels.radiance.theming.api.painter.FractionBasedTonalPainter; import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokens; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; import java.awt.*; import java.awt.MultipleGradientPaint.CycleMethod; diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/GlassTonalFillPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/GlassTonalFillPainter.java index 58dd4cfad..25e45754b 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/GlassTonalFillPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/fill/GlassTonalFillPainter.java @@ -29,7 +29,7 @@ */ package org.pushingpixels.radiance.theming.api.painter.fill; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; /** * Fill painter that draws visuals with glass appearance. This class is part diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/overlay/BottomLineTonalOverlayPainter.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/overlay/BottomLineTonalOverlayPainter.java index d90a72b6f..cec2c3810 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/overlay/BottomLineTonalOverlayPainter.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/painter/overlay/BottomLineTonalOverlayPainter.java @@ -32,7 +32,7 @@ import org.pushingpixels.radiance.common.api.RadianceCommonCortex; import org.pushingpixels.radiance.theming.api.RadianceSkin; import org.pushingpixels.radiance.theming.api.RadianceThemingSlices; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.palette.ExtendedContainerColorTokens; import org.pushingpixels.radiance.theming.internal.utils.RadianceColorUtilities; import org.pushingpixels.radiance.theming.internal.utils.RadianceCoreUtilities; diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/ColorSchemeUtils.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/ColorSchemeUtils.java index 4e19d9675..df4f3ba39 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/ColorSchemeUtils.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/ColorSchemeUtils.java @@ -29,6 +29,7 @@ */ package org.pushingpixels.radiance.theming.api.palette; +import org.pushingpixels.ephemeral.chroma.blend.Blend; import org.pushingpixels.ephemeral.chroma.dynamiccolor.DynamicScheme; import org.pushingpixels.ephemeral.chroma.hct.Hct; import org.pushingpixels.ephemeral.chroma.palettes.TonalPalette; @@ -193,6 +194,13 @@ public ContainerColorTokens getPrimaryContainerTokens() { return primaryContainerTokens; } + @Override + public ContainerColorTokens getActiveContainerTokens() { + return (activeStatesContainerType == ActiveStatesContainerType.PRIMARY) + ? this.getPrimaryContainerTokens() + : this.getTonalContainerTokens(); + } + @Override public ContainerColorTokens getContainerTokensForState(ComponentState componentState) { if (componentState.isDisabled()) { @@ -200,11 +208,7 @@ public ContainerColorTokens getContainerTokensForState(ComponentState componentS return getContainerTokensForState(componentState.getEnabledMatch()); } - // TODO: TONAL - configurable at the skin definition level - ContainerColorTokens defaultActive = - (activeStatesContainerType == ActiveStatesContainerType.PRIMARY) - ? this.getPrimaryContainerTokens() - : this.getTonalContainerTokens(); + ContainerColorTokens defaultActive = getActiveContainerTokens(); if ((componentState == ComponentState.PRESSED_UNSELECTED) || (componentState == ComponentState.ARMED)) { if (!stateTokens.containsKey(componentState)) { @@ -362,6 +366,13 @@ public ContainerColorTokens getPrimaryContainerTokens() { return primaryContainerTokens; } + @Override + public ContainerColorTokens getActiveContainerTokens() { + return (activeStatesContainerType == ActiveStatesContainerType.PRIMARY) + ? this.getPrimaryContainerTokens() + : this.getTonalContainerTokens(); + } + @Override public ContainerColorTokens getContainerTokensForState(ComponentState componentState) { if (componentState.isDisabled()) { @@ -369,11 +380,7 @@ public ContainerColorTokens getContainerTokensForState(ComponentState componentS return getContainerTokensForState(componentState.getEnabledMatch()); } - // TODO: TONAL - configurable at the skin definition level - ContainerColorTokens defaultActive = - (activeStatesContainerType == ActiveStatesContainerType.PRIMARY) - ? this.getPrimaryContainerTokens() - : this.getTonalContainerTokens(); + ContainerColorTokens defaultActive = getActiveContainerTokens(); if ((componentState == ComponentState.PRESSED_UNSELECTED) || (componentState == ComponentState.ARMED)) { if (!stateTokens.containsKey(componentState)) { @@ -820,4 +827,82 @@ public static RadianceColorScheme2 getDarkPrimaryFidelityColorScheme( return result; } + + public static ContainerColorTokens tint(ContainerColorTokens original, float tintFactor) { + return new ContainerColorTokens() { + @Override + public boolean isDark() { + return original.isDark(); + } + + @Override + public Color getContainerSurfaceLowest() { + return new Color(Blend.hctHue(original.getContainerSurfaceLowest().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getContainerSurfaceLow() { + return new Color(Blend.hctHue(original.getContainerSurfaceLow().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getContainerSurface() { + return new Color(Blend.hctHue(original.getContainerSurface().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getContainerSurfaceHigh() { + return new Color(Blend.hctHue(original.getContainerSurfaceHigh().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getContainerSurfaceHighest() { + return new Color(Blend.hctHue(original.getContainerSurfaceHighest().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getOnContainer() { + return new Color(Blend.hctHue(original.getOnContainer().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getOnContainerVariant() { + return new Color(Blend.hctHue(original.getOnContainerVariant().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getContainerOutline() { + return new Color(Blend.hctHue(original.getContainerOutline().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public Color getContainerOutlineVariant() { + return new Color(Blend.hctHue(original.getContainerOutlineVariant().getRGB(), + Color.WHITE.getRGB(), tintFactor)); + } + + @Override + public float getContainerSurfaceDisabledAlpha() { + return original.getContainerSurfaceDisabledAlpha(); + } + + @Override + public float getOnContainerDisabledAlpha() { + return original.getOnContainerDisabledAlpha(); + } + + @Override + public float getContainerOutlineDisabledAlpha() { + return original.getContainerOutlineDisabledAlpha(); + } + }; + } } diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/RadianceColorScheme2.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/RadianceColorScheme2.java index d9ea54673..d86499336 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/RadianceColorScheme2.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/palette/RadianceColorScheme2.java @@ -49,6 +49,8 @@ public interface RadianceColorScheme2 { ContainerColorTokens getPrimaryContainerTokens(); + ContainerColorTokens getActiveContainerTokens(); + ContainerColorTokens getContainerTokensForState(ComponentState componentState); default ExtendedContainerColorTokens getExtendedContainerTokens(ComponentState componentState) { diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/BusinessAccentedTonalSkin.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/BusinessAccentedTonalSkin.java index 2a3b587af..fd90a6b13 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/BusinessAccentedTonalSkin.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/BusinessAccentedTonalSkin.java @@ -31,6 +31,7 @@ import org.pushingpixels.ephemeral.chroma.hct.Hct; import org.pushingpixels.radiance.theming.api.*; +import org.pushingpixels.radiance.theming.api.colorscheme.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; import org.pushingpixels.radiance.theming.api.painter.border.ClassicTonalBorderPainter; import org.pushingpixels.radiance.theming.api.painter.decoration.ArcDecorationPainter; @@ -40,7 +41,6 @@ import org.pushingpixels.radiance.theming.api.painter.overlay.BottomLineTonalOverlayPainter; import org.pushingpixels.radiance.theming.api.painter.overlay.TopShadowOverlayPainter; import org.pushingpixels.radiance.theming.api.palette.ColorSchemeUtils; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.palette.RadianceColorScheme2; import org.pushingpixels.radiance.theming.api.shaper.ClassicButtonShaper; diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/ModerateSkin.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/ModerateSkin.java index 8e0160e7a..1adc3977d 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/ModerateSkin.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/ModerateSkin.java @@ -31,10 +31,7 @@ import org.pushingpixels.ephemeral.chroma.hct.Hct; import org.pushingpixels.radiance.theming.api.*; -import org.pushingpixels.radiance.theming.api.colorscheme.ColorSchemeSingleColorQuery; -import org.pushingpixels.radiance.theming.api.colorscheme.MetallicColorScheme; -import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; -import org.pushingpixels.radiance.theming.api.colorscheme.SteelBlueColorScheme; +import org.pushingpixels.radiance.theming.api.colorscheme.*; import org.pushingpixels.radiance.theming.api.painter.border.ClassicBorderPainter; import org.pushingpixels.radiance.theming.api.painter.border.ClassicTonalBorderPainter; import org.pushingpixels.radiance.theming.api.painter.decoration.MatteDecorationPainter; @@ -43,7 +40,6 @@ import org.pushingpixels.radiance.theming.api.painter.overlay.BottomLineTonalOverlayPainter; import org.pushingpixels.radiance.theming.api.painter.overlay.TopShadowOverlayPainter; import org.pushingpixels.radiance.theming.api.palette.ColorSchemeUtils; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.palette.RadianceColorScheme2; import org.pushingpixels.radiance.theming.api.palette.TonalSkin; import org.pushingpixels.radiance.theming.api.shaper.ClassicButtonShaper; diff --git a/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/SaharaSkin.java b/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/SaharaSkin.java index 4ae8ae4e1..185f88697 100644 --- a/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/SaharaSkin.java +++ b/theming/src/main/java/org/pushingpixels/radiance/theming/api/skin/SaharaSkin.java @@ -31,10 +31,7 @@ import org.pushingpixels.ephemeral.chroma.hct.Hct; import org.pushingpixels.radiance.theming.api.*; -import org.pushingpixels.radiance.theming.api.colorscheme.ColorSchemeSingleColorQuery; -import org.pushingpixels.radiance.theming.api.colorscheme.DesertSandColorScheme; -import org.pushingpixels.radiance.theming.api.colorscheme.MetallicColorScheme; -import org.pushingpixels.radiance.theming.api.colorscheme.RadianceColorScheme; +import org.pushingpixels.radiance.theming.api.colorscheme.*; import org.pushingpixels.radiance.theming.api.painter.border.ClassicBorderPainter; import org.pushingpixels.radiance.theming.api.painter.border.ClassicTonalBorderPainter; import org.pushingpixels.radiance.theming.api.painter.decoration.MatteDecorationPainter; @@ -45,7 +42,6 @@ import org.pushingpixels.radiance.theming.api.painter.overlay.BottomLineTonalOverlayPainter; import org.pushingpixels.radiance.theming.api.painter.overlay.TopShadowOverlayPainter; import org.pushingpixels.radiance.theming.api.palette.ColorSchemeUtils; -import org.pushingpixels.radiance.theming.api.palette.ContainerColorTokensSingleColorQuery; import org.pushingpixels.radiance.theming.api.palette.RadianceColorScheme2; import org.pushingpixels.radiance.theming.api.palette.TonalSkin; import org.pushingpixels.radiance.theming.api.shaper.ClassicButtonShaper;