Panel: support painting background with rounded corners (issue #367)

FlatLineBorder: support rounded corners
This commit is contained in:
Karl Tauber
2021-12-10 22:40:17 +01:00
parent 023e356057
commit 600e0f3d67
8 changed files with 90 additions and 12 deletions

View File

@@ -64,6 +64,7 @@ FlatLaf Change Log
- MenuItem: - MenuItem:
- Paint the selected icon when the item is selected. (PR #415) - Paint the selected icon when the item is selected. (PR #415)
- Vertically align text if icons have different widths. (issue #437) - Vertically align text if icons have different widths. (issue #437)
- Panel: Support painting background with rounded corners. (issue #367)
- Added more color functions to class `ColorFunctions` for easy use in - Added more color functions to class `ColorFunctions` for easy use in
applications: `lighten()`, `darken()`, `saturate()`, `desaturate()`, `spin()`, applications: `lighten()`, `darken()`, `saturate()`, `desaturate()`, `spin()`,
`tint()`, `shade()` and `luma()`. `tint()`, `shade()` and `luma()`.

View File

@@ -582,17 +582,18 @@ class UIDefaultsLoader
private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) { private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
if( value.indexOf( ',' ) >= 0 ) { if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor[,lineThickness]] // top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' ); List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value ); Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5) ColorUIResource lineColor = (parts.size() >= 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true )
: null; : null;
float lineThickness = (parts.size() >= 6) ? parseFloat( parts.get( 5 ), true ) : 1f; float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ), true ) : 1f;
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ), true ) : 0;
return (LazyValue) t -> { return (LazyValue) t -> {
return (lineColor != null) return (lineColor != null)
? new FlatLineBorder( insets, lineColor, lineThickness ) ? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets ); : new FlatEmptyBorder( insets );
}; };
} else } else

View File

@@ -37,15 +37,18 @@ public class FlatLineBorder
{ {
private final Color lineColor; private final Color lineColor;
private final float lineThickness; private final float lineThickness;
/** @since 2 */ private final int arc;
public FlatLineBorder( Insets insets, Color lineColor ) { public FlatLineBorder( Insets insets, Color lineColor ) {
this( insets, lineColor, 1f ); this( insets, lineColor, 1f, 0 );
} }
public FlatLineBorder( Insets insets, Color lineColor, float lineThickness ) { /** @since 2 */
public FlatLineBorder( Insets insets, Color lineColor, float lineThickness, int arc ) {
super( insets ); super( insets );
this.lineColor = lineColor; this.lineColor = lineColor;
this.lineThickness = lineThickness; this.lineThickness = lineThickness;
this.arc = arc;
} }
public Color getLineColor() { public Color getLineColor() {
@@ -56,13 +59,18 @@ public class FlatLineBorder
return lineThickness; return lineThickness;
} }
/** @since 2 */
public int getArc() {
return arc;
}
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create(); Graphics2D g2 = (Graphics2D) g.create();
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
0, 0, 0, scale( getLineThickness() ), 0, null, getLineColor(), null ); 0, 0, 0, scale( getLineThickness() ), scale( getArc() ), null, getLineColor(), null );
} finally { } finally {
g2.dispose(); g2.dispose();
} }

View File

@@ -16,14 +16,18 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI; import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPanel}. * Provides the Flat LaF UI delegate for {@link javax.swing.JPanel}.
@@ -32,7 +36,7 @@ import com.formdev.flatlaf.util.LoggingFacade;
* *
* @uiDefault Panel.font Font unused * @uiDefault Panel.font Font unused
* @uiDefault Panel.background Color only used if opaque * @uiDefault Panel.background Color only used if opaque
* @uiDefault Panel.foreground Color * @uiDefault Panel.foreground Color unused
* @uiDefault Panel.border Border * @uiDefault Panel.border Border
* *
* @author Karl Tauber * @author Karl Tauber
@@ -41,6 +45,9 @@ public class FlatPanelUI
extends BasicPanelUI extends BasicPanelUI
implements StyleableUI implements StyleableUI
{ {
// only used via styling (not in UI defaults)
/** @since 2 */ @Styleable protected int arc = -1;
private final boolean shared; private final boolean shared;
private PropertyChangeListener propertyChangeListener; private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues; private Map<String, Object> oldStyleValues;
@@ -113,4 +120,34 @@ public class FlatPanelUI
public Map<String, Class<?>> getStyleableInfos( JComponent c ) { public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this ); return FlatStylingSupport.getAnnotatedStyleableInfos( this );
} }
@Override
public void update( Graphics g, JComponent c ) {
// fill background
if( c.isOpaque() ) {
int width = c.getWidth();
int height = c.getHeight();
int arc = (this.arc >= 0)
? this.arc
: ((c.getBorder() instanceof FlatLineBorder)
? ((FlatLineBorder)c.getBorder()).getArc()
: 0);
// fill background with parent color to avoid garbage in rounded corners
if( arc > 0 )
FlatUIUtils.paintParentBackground( g, c );
g.setColor( c.getBackground() );
if( arc > 0 ) {
// fill rounded rectangle if having rounded corners
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, width, height,
0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} else
g.fillRect( 0, 0, width, height );
}
paint( g, c );
}
} }

View File

@@ -22,8 +22,12 @@ import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.Insets; import java.awt.Insets;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.UIDefaults.ActiveValue; import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder;
/** /**
* @author Karl Tauber * @author Karl Tauber
@@ -46,8 +50,8 @@ public class TestUIDefaultsLoader
assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", null ) ); assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", null ) );
assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummyWidth", "1.23", null ) ); assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummyWidth", "1.23", null ) );
assertEquals( new Insets( 2,2,2,2 ), UIDefaultsLoader.parseValue( "dummyInsets", "2,2,2,2", null ) ); assertEquals( new Insets( 1,2,3,4 ), UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3,4", null ) );
assertEquals( new Dimension( 2,2 ), UIDefaultsLoader.parseValue( "dummySize", "2,2", null ) ); assertEquals( new Dimension( 1,2 ), UIDefaultsLoader.parseValue( "dummySize", "1,2", null ) );
assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", null ) ); assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", null ) );
assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummyColor", "#f00", null ) ); assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummyColor", "#f00", null ) );
} }
@@ -70,11 +74,35 @@ public class TestUIDefaultsLoader
assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", float.class ) ); assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", float.class ) );
assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", Float.class ) ); assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", Float.class ) );
assertEquals( new Insets( 2,2,2,2 ), UIDefaultsLoader.parseValue( "dummy", "2,2,2,2", Insets.class ) ); assertEquals( new Insets( 1,2,3,4 ), UIDefaultsLoader.parseValue( "dummy", "1,2,3,4", Insets.class ) );
assertEquals( new Dimension( 2,2 ), UIDefaultsLoader.parseValue( "dummy", "2,2", Dimension.class ) ); assertEquals( new Dimension( 1,2 ), UIDefaultsLoader.parseValue( "dummy", "1,2", Dimension.class ) );
assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", Color.class ) ); assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", Color.class ) );
} }
@Test
void parseBorders() {
Insets insets = new Insets( 1,2,3,4 );
assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4" );
assertBorderEquals( new FlatLineBorder( insets, Color.red ), "1,2,3,4,#f00" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 0 ), "1,2,3,4,#f00,2.5" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 6 ), "1,2,3,4,#f00,2.5,6" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 1, 6 ), "1,2,3,4,#f00,,6" );
}
private void assertBorderEquals( Border expected, String actualStyle ) {
Border actual = (Border) ((LazyValue)UIDefaultsLoader.parseValue( "dummyBorder", actualStyle, null )).createValue( null );
assertEquals( expected.getClass(), actual.getClass() );
if( expected instanceof FlatEmptyBorder )
assertEquals( ((FlatEmptyBorder)actual).getBorderInsets(), ((FlatEmptyBorder)expected).getBorderInsets() );
if( expected instanceof FlatLineBorder ) {
FlatLineBorder a = (FlatLineBorder) actual;
FlatLineBorder e = (FlatLineBorder) expected;
assertEquals( a.getLineColor(), e.getLineColor() );
assertEquals( a.getLineThickness(), e.getLineThickness() );
assertEquals( a.getArc(), e.getArc() );
}
}
@Test @Test
void parseFonts() { void parseFonts() {
// style // style

View File

@@ -386,6 +386,7 @@ public class TestFlatStyleableInfo
FlatPanelUI ui = (FlatPanelUI) c.getUI(); FlatPanelUI ui = (FlatPanelUI) c.getUI();
Map<String, Class<?>> expected = expectedMap( Map<String, Class<?>> expected = expectedMap(
"arc", int.class
); );
assertMapEquals( expected, ui.getStyleableInfos( c ) ); assertMapEquals( expected, ui.getStyleableInfos( c ) );

View File

@@ -529,6 +529,8 @@ public class TestFlatStyling
JPanel c = new JPanel(); JPanel c = new JPanel();
FlatPanelUI ui = (FlatPanelUI) c.getUI(); FlatPanelUI ui = (FlatPanelUI) c.getUI();
ui.applyStyle( c, "arc: 8" );
// JComponent properties // JComponent properties
ui.applyStyle( c, "background: #fff" ); ui.applyStyle( c, "background: #fff" );
ui.applyStyle( c, "foreground: #fff" ); ui.applyStyle( c, "foreground: #fff" );

View File

@@ -194,7 +194,7 @@ class FlatColorPipette
zoom = UIScale.scale( 16 ); zoom = UIScale.scale( 16 );
getRootPane().setBorder( new FlatLineBorder( new Insets( 2, 2, 2, 2 ), Color.red, 2 ) ); getRootPane().setBorder( new FlatLineBorder( new Insets( 2, 2, 2, 2 ), Color.red, 2, 0 ) );
view = new MagnifierView(); view = new MagnifierView();
view.setPreferredSize( new Dimension( pixels * zoom, pixels * zoom ) ); view.setPreferredSize( new Dimension( pixels * zoom, pixels * zoom ) );