Skip to content

Commit

Permalink
Button: hover and pressed background colors are now derived from actu…
Browse files Browse the repository at this point in the history
…al button background color (issue #21)
  • Loading branch information
DevCharly committed Oct 27, 2019
1 parent ec57243 commit 2ec142f
Show file tree
Hide file tree
Showing 11 changed files with 700 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ FlatLaf Change Log
`JPasswordField`, `JScrollPane` and `JTextField` are non-opaque if they have
an outside focus border (e.g. IntelliJ and Darcula themes). (issues #20 and
#17)
- Button: Hover and pressed background colors are now derived from actual button
background color. (issue #21)


## 0.16
Expand Down
105 changes: 87 additions & 18 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import javax.swing.plaf.InsetsUIResource;
import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.ScaledNumber;

/**
Expand Down Expand Up @@ -101,7 +103,11 @@ static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, UIDefaults de
continue;

String value = resolveValue( properties, (String) e.getValue() );
globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver ) );
try {
globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver ) );
} catch( RuntimeException ex ) {
logParseError( key, value, ex );
}
}

// override UI defaults with globals
Expand All @@ -122,13 +128,22 @@ static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, UIDefaults de
continue;

String value = resolveValue( properties, (String) e.getValue() );
defaults.put( key, parseValue( key, value, resolver ) );
try {
defaults.put( key, parseValue( key, value, resolver ) );
} catch( RuntimeException ex ) {
logParseError( key, value, ex );
}
}
} catch( IOException ex ) {
ex.printStackTrace();
}
}

private static void logParseError( String key, String value, RuntimeException ex ) {
System.err.println( "Failed to parse: '" + key + '=' + value + '\'' );
System.err.println( " " + ex.getMessage() );
}

private static String resolveValue( Properties properties, String value ) {
if( !value.startsWith( VARIABLE_PREFIX ) )
return value;
Expand All @@ -147,8 +162,7 @@ private static String resolveValue( Properties properties, String value ) {
if( optional )
return "null";

System.err.println( "variable or reference '" + value + "' not found" );
throw new IllegalArgumentException( value );
throw new IllegalArgumentException( "variable or reference '" + value + "' not found" );
}

return resolveValue( properties, newValue );
Expand Down Expand Up @@ -264,8 +278,7 @@ private static Insets parseInsets( String value ) {
Integer.parseInt( numbers.get( 2 ) ),
Integer.parseInt( numbers.get( 3 ) ) );
} catch( NumberFormatException ex ) {
System.err.println( "invalid insets '" + value + "'" );
throw ex;
throw new IllegalArgumentException( "invalid insets '" + value + "'" );
}
}

Expand All @@ -276,12 +289,14 @@ private static Dimension parseSize( String value ) {
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ) );
} catch( NumberFormatException ex ) {
System.err.println( "invalid size '" + value + "'" );
throw ex;
throw new IllegalArgumentException( "invalid size '" + value + "'" );
}
}

private static ColorUIResource parseColor( String value, boolean reportError ) {
if( value.endsWith( ")" ) )
return parseColorFunctions( value, reportError );

try {
int rgb = Integer.parseInt( value, 16 );
if( value.length() == 6 )
Expand All @@ -292,23 +307,78 @@ private static ColorUIResource parseColor( String value, boolean reportError ) {
if( reportError )
throw new NumberFormatException( value );
} catch( NumberFormatException ex ) {
if( reportError ) {
System.err.println( "invalid color '" + value + "'" );
throw ex;
}
if( reportError )
throw new IllegalArgumentException( "invalid color '" + value + "'" );

// not a color --> ignore
}
return null;
}

private static ColorUIResource parseColorFunctions( String value, boolean reportError ) {
int paramsStart = value.indexOf( '(' );
if( paramsStart < 0 ) {
if( reportError )
throw new IllegalArgumentException( "missing opening parenthesis in function '" + value + "'" );
return null;
}

String function = value.substring( 0, paramsStart ).trim();
List<String> params = split( value.substring( paramsStart + 1, value.length() - 1 ), ',' );
if( params.isEmpty() )
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );

switch( function ) {
case "lighten": return parseColorLightenOrDarken( true, params, reportError );
case "darken": return parseColorLightenOrDarken( false, params, reportError );
}

throw new IllegalArgumentException( "unknown color function '" + value + "'" );
}

/**
* Syntax: lighten(amount[,options]) or darken(amount[,options])
* - amount: percentage 0-100%
* - options: [relative] [autoInverse]
*/
private static ColorUIResource parseColorLightenOrDarken( boolean lighten, List<String> params, boolean reportError ) {
int amount = parsePercentage( params.get( 0 ) );
boolean relative = false;
boolean autoInverse = false;

if( params.size() >= 2 ) {
String options = params.get( 1 );
relative = options.contains( "relative" );
autoInverse = options.contains( "autoInverse" );
}

return new DerivedColor( lighten
? new ColorFunctions.Lighten( amount, relative, autoInverse )
: new ColorFunctions.Darken( amount, relative, autoInverse ) );
}

private static int parsePercentage( String value ) {
if( !value.endsWith( "%" ) )
throw new NumberFormatException( "invalid percentage '" + value + "'" );

int val;
try {
val = Integer.parseInt( value.substring( 0, value.length() - 1 ) );
} catch( NumberFormatException ex ) {
throw new NumberFormatException( "invalid percentage '" + value + "'" );
}

if( val < 0 || val > 100 )
throw new IllegalArgumentException( "percentage out of range (0-100%) '" + value + "'" );
return val;
}

private static Integer parseInteger( String value, boolean reportError ) {
try {
return Integer.parseInt( value );
} catch( NumberFormatException ex ) {
if( reportError ) {
System.err.println( "invalid integer '" + value + "'" );
throw ex;
}
if( reportError )
throw new NumberFormatException( "invalid integer '" + value + "'" );
}
return null;
}
Expand All @@ -317,8 +387,7 @@ private static ScaledNumber parseScaledNumber( String value ) {
try {
return new ScaledNumber( Integer.parseInt( value ) );
} catch( NumberFormatException ex ) {
System.err.println( "invalid integer '" + value + "'" );
throw ex;
throw new NumberFormatException( "invalid integer '" + value + "'" );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,13 @@ protected void paintIcon( Component c, Graphics2D g2 ) {
paintBorder( g2 );

// paint background
g2.setColor( FlatButtonUI.buttonStateColor( c,
FlatUIUtils.setColor( g2, FlatButtonUI.buttonStateColor( c,
selected ? selectedBackground : background,
disabledBackground,
focusedBackground,
selected && selectedHoverBackground != null ? selectedHoverBackground : hoverBackground,
selected && selectedPressedBackground != null ? selectedPressedBackground : pressedBackground ) );
selected && selectedPressedBackground != null ? selectedPressedBackground : pressedBackground ),
background );
paintBackground( g2 );

// paint checkmark
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;

/**
* Help button icon for {@link javax.swing.JButton}.
Expand Down Expand Up @@ -99,12 +100,12 @@ protected void paintIcon( Component c, Graphics2D g2 ) {
g2.fill( new Ellipse2D.Float( focusWidth + 0.5f, focusWidth + 0.5f, 21, 21 ) );

// paint background
g2.setColor( FlatButtonUI.buttonStateColor( c,
FlatUIUtils.setColor( g2, FlatButtonUI.buttonStateColor( c,
background,
disabledBackground,
focusedBackground,
hoverBackground,
pressedBackground ) );
pressedBackground ), background );
g2.fill( new Ellipse2D.Float( focusWidth + 1.5f, focusWidth + 1.5f, 19, 19 ) );

// paint question mark
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public void update( Graphics g, JComponent c ) {
float focusWidth = (border instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (border instanceof FlatButtonBorder || isToolBarButton( c )) ? scale( (float) this.arc ) : 0;

g2.setColor( background );
FlatUIUtils.setColor( g2, background, isDefaultButton(c) ? defaultBackground : c.getBackground() );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
} finally {
g2.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.JavaCompatibility;
import com.formdev.flatlaf.util.UIScale;

Expand Down Expand Up @@ -104,6 +105,12 @@ public static void setRenderingHints( Graphics2D g ) {
MAC_USE_QUARTZ ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE );
}

public static void setColor( Graphics g, Color color, Color baseColor ) {
if( color instanceof DerivedColor )
color = ((DerivedColor)color).derive( baseColor );
g.setColor( color );
}

/**
* Draws a round rectangle.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.formdev.flatlaf.util;

import java.awt.Color;

/**
* Functions that modify colors.
*
* @author Karl Tauber
*/
public class ColorFunctions
{
public static Color applyFunctions( Color color, ColorFunction[] functions ) {
float[] hsl = HSLColor.fromRGB( color );
float alpha = color.getAlpha() / 255f;

for( ColorFunction function : functions )
function.apply( hsl );

return HSLColor.toRGB( hsl, alpha );
}

private static float clamp( float value ) {
return (value < 0)
? 0
: ((value > 100)
? 100
: value);
}

//---- interface ColorFunction --------------------------------------------

public interface ColorFunction {
void apply( float[] hsl );
}

//---- class Lighten ------------------------------------------------------

/**
* Increase the lightness of a color in the HSL color space by an absolute
* or relative amount.
*/
public static class Lighten
implements ColorFunction
{
private final float amount;
private final boolean relative;
private final boolean autoInverse;

public Lighten( float amount, boolean relative, boolean autoInverse ) {
this.amount = amount;
this.relative = relative;
this.autoInverse = autoInverse;
}

@Override
public void apply( float[] hsl ) {
float amount2 = autoInverse && shouldInverse( hsl ) ? -amount : amount;
hsl[2] = clamp( relative
? (hsl[2] * ((100 + amount2) / 100))
: (hsl[2] + amount2) );
}

protected boolean shouldInverse( float[] hsl ) {
return hsl[2] >= 50;
}
}

//---- class Darken -------------------------------------------------------

/**
* Decrease the lightness of a color in the HSL color space by an absolute
* or relative amount.
*/
public static class Darken
extends Lighten
{
public Darken( float amount, boolean relative, boolean autoInverse ) {
super( -amount, relative, autoInverse );
}

@Override
protected boolean shouldInverse( float[] hsl ) {
return hsl[2] < 50;
}
}
}
Loading

0 comments on commit 2ec142f

Please sign in to comment.