Label: support painting background with rounded corners (issue #842)

Demo: added rounded panels and labels to "More Components" tab
This commit is contained in:
Karl Tauber
2024-05-21 13:37:11 +02:00
parent bbbdd7e4d3
commit 029f273dd9
12 changed files with 185 additions and 23 deletions

View File

@@ -3,6 +3,10 @@ FlatLaf Change Log
## 3.5-SNAPSHOT ## 3.5-SNAPSHOT
#### New features and improvements
- Label: Support painting background with rounded corners. (issue #842)
#### Incompatibilities #### Incompatibilities
- ProgressBar: Log warning (including stack trace) when uninstalling - ProgressBar: Log warning (including stack trace) when uninstalling

View File

@@ -638,14 +638,18 @@ class UIDefaultsLoader
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]] // Syntax: 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 && !parts.get( 4 ).isEmpty())
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null; : null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ) ) : 1f; float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ) ) : 0; ? parseFloat( parts.get( 5 ) )
: 1f;
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: 0;
return (LazyValue) t -> { return (LazyValue) t -> {
return (lineColor != null) return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc ) ? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets ); : new FlatEmptyBorder( insets );
}; };

View File

@@ -277,7 +277,7 @@ public class FlatBorder
} }
/** /**
* Returns the (unscaled) arc diameter of the border. * Returns the (unscaled) arc diameter of the border corners.
*/ */
protected int getArc( Component c ) { protected int getArc( Component c ) {
return 0; return 0;

View File

@@ -64,6 +64,9 @@ public class FlatLabelUI
{ {
@Styleable protected Color disabledForeground; @Styleable protected Color disabledForeground;
// only used via styling (not in UI defaults)
/** @since 3.5 */ @Styleable protected int arc = -1;
private final boolean shared; private final boolean shared;
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues; private Map<String, Object> oldStyleValues;
@@ -244,6 +247,12 @@ public class FlatLabelUI
return false; return false;
} }
@Override
public void update( Graphics g, JComponent c ) {
FlatPanelUI.fillRoundedBackground( g, c, arc );
paint( g, c );
}
static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) { static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) {
return (c.getClientProperty( BasicHTML.propertyKey ) != null) return (c.getClientProperty( BasicHTML.propertyKey ) != null)
? HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) ? HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g )

View File

@@ -26,10 +26,14 @@ import javax.swing.JComponent;
/** /**
* Line border for various components. * Line border for various components.
* * <p>
* Paints a scaled (usually 1px thick) line around the component. * Paints a scaled (usually 1px thick) line around the component.
* The line thickness is not added to the border insets. * The line thickness is not added to the border insets.
* The insets should be at least have line thickness (usually 1,1,1,1). * The insets should be at least have line thickness (usually 1,1,1,1).
* <p>
* For {@link javax.swing.JPanel} and {@link javax.swing.JLabel}, this border
* can be used paint rounded background (if line color is {@code null}) or
* paint rounded line border with rounded background.
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -52,15 +56,28 @@ public class FlatLineBorder
this.arc = arc; this.arc = arc;
} }
/** @since 3.5 */
public FlatLineBorder( Insets insets, int arc ) {
this( insets, null, 0, arc );
}
public Color getLineColor() { public Color getLineColor() {
return lineColor; return lineColor;
} }
/**
* Returns the (unscaled) line thickness used to paint the border.
* The line thickness does not affect the border insets.
*/
public float getLineThickness() { public float getLineThickness() {
return lineThickness; return lineThickness;
} }
/** @since 2 */ /**
* Returns the (unscaled) arc diameter of the border corners.
*
* @since 2
*/
public int getArc() { public int getArc() {
return arc; return arc;
} }
@@ -70,11 +87,16 @@ public class FlatLineBorder
if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null ) if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null )
return; return;
Color lineColor = getLineColor();
float lineThickness = getLineThickness();
if( lineColor == null || lineThickness <= 0 )
return;
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() ), scale( getArc() ), null, getLineColor(), null ); 0, 0, 0, scale( lineThickness ), scale( getArc() ), null, lineColor, null );
} finally { } finally {
g2.dispose(); g2.dispose();
} }

View File

@@ -25,6 +25,7 @@ import java.util.Map;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.border.Border;
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.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
@@ -160,11 +161,18 @@ public class FlatPanelUI
@Override @Override
public void update( Graphics g, JComponent c ) { public void update( Graphics g, JComponent c ) {
int arc = (this.arc >= 0) fillRoundedBackground( g, c, arc );
? this.arc paint( g, c );
: ((c.getBorder() instanceof FlatLineBorder) }
? ((FlatLineBorder)c.getBorder()).getArc()
/** @since 3.5 */
public static void fillRoundedBackground( Graphics g, JComponent c, int arc ) {
if( arc < 0 ) {
Border border = c.getBorder();
arc = ((border instanceof FlatLineBorder)
? ((FlatLineBorder)border).getArc()
: 0); : 0);
}
// fill background // fill background
if( c.isOpaque() ) { if( c.isOpaque() ) {
@@ -185,8 +193,6 @@ public class FlatPanelUI
0, UIScale.scale( arc ) ); 0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} }
paint( g, c );
} }
@Override @Override

View File

@@ -84,10 +84,12 @@ public class TestUIDefaultsLoader
void parseBorders() { void parseBorders() {
Insets insets = new Insets( 1,2,3,4 ); Insets insets = new Insets( 1,2,3,4 );
assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4" ); assertBorderEquals( new FlatEmptyBorder( 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 ), "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, 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, 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" ); assertBorderEquals( new FlatLineBorder( insets, Color.red, 1, 6 ), "1,2,3,4,#f00,,6" );
assertBorderEquals( new FlatLineBorder( insets, null, 1, 6 ), "1,2,3,4,,,6" );
} }
private void assertBorderEquals( Border expected, String actualStyle ) { private void assertBorderEquals( Border expected, String actualStyle ) {

View File

@@ -253,7 +253,8 @@ public class TestFlatStyleableInfo
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
Map<String, Class<?>> expected = expectedMap( Map<String, Class<?>> expected = expectedMap(
"disabledForeground", Color.class "disabledForeground", Color.class,
"arc", int.class
); );
assertMapEquals( expected, ui.getStyleableInfos( c ) ); assertMapEquals( expected, ui.getStyleableInfos( c ) );

View File

@@ -355,6 +355,7 @@ public class TestFlatStyleableValue
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
testColor( c, ui, "disabledForeground", 0x123456 ); testColor( c, ui, "disabledForeground", 0x123456 );
testInteger( c, ui, "arc", 123 );
} }
@Test @Test

View File

@@ -412,6 +412,7 @@ public class TestFlatStyling
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
ui.applyStyle( c, "disabledForeground: #fff" ); ui.applyStyle( c, "disabledForeground: #fff" );
ui.applyStyle( c, "arc: 8" );
// JComponent properties // JComponent properties
ui.applyStyle( c, "background: #fff" ); ui.applyStyle( c, "background: #fff" );

View File

@@ -118,6 +118,14 @@ class MoreComponentsPanel
JLabel label5 = new JLabel(); JLabel label5 = new JLabel();
JPanel panel13 = new JPanel(); JPanel panel13 = new JPanel();
JLabel label6 = new JLabel(); JLabel label6 = new JLabel();
JLabel panelLabel = new JLabel();
JPanel panel5 = new JPanel();
JLabel label9 = new JLabel();
JPanel panel4 = new JPanel();
JLabel label8 = new JLabel();
JLabel labelLabel = new JLabel();
JLabel label13 = new JLabel();
JLabel label10 = new JLabel();
//======== this ======== //======== this ========
setLayout(new MigLayout( setLayout(new MigLayout(
@@ -140,7 +148,9 @@ class MoreComponentsPanel
"[]" + "[]" +
"[]" + "[]" +
"[]" + "[]" +
"[100,top]")); "[100,top]" +
"[50,top]" +
"[]"));
//---- scrollPaneLabel ---- //---- scrollPaneLabel ----
scrollPaneLabel.setText("JScrollPane:"); scrollPaneLabel.setText("JScrollPane:");
@@ -441,7 +451,7 @@ class MoreComponentsPanel
//======== panel10 ======== //======== panel10 ========
{ {
panel10.setBackground(new Color(217, 163, 67)); panel10.setBackground(new Color(0xd9a343));
panel10.setLayout(new BorderLayout()); panel10.setLayout(new BorderLayout());
//---- label1 ---- //---- label1 ----
@@ -454,7 +464,7 @@ class MoreComponentsPanel
//======== panel11 ======== //======== panel11 ========
{ {
panel11.setBackground(new Color(98, 181, 67)); panel11.setBackground(new Color(0x62b543));
panel11.setLayout(new BorderLayout()); panel11.setLayout(new BorderLayout());
//---- label2 ---- //---- label2 ----
@@ -474,7 +484,7 @@ class MoreComponentsPanel
//======== panel12 ======== //======== panel12 ========
{ {
panel12.setBackground(new Color(242, 101, 34)); panel12.setBackground(new Color(0xf26522));
panel12.setLayout(new BorderLayout()); panel12.setLayout(new BorderLayout());
//---- label5 ---- //---- label5 ----
@@ -487,7 +497,7 @@ class MoreComponentsPanel
//======== panel13 ======== //======== panel13 ========
{ {
panel13.setBackground(new Color(64, 182, 224)); panel13.setBackground(new Color(0x40b6e0));
panel13.setLayout(new BorderLayout()); panel13.setLayout(new BorderLayout());
//---- label6 ---- //---- label6 ----
@@ -502,6 +512,52 @@ class MoreComponentsPanel
} }
add(splitPane3, "cell 1 11 4 1,grow"); add(splitPane3, "cell 1 11 4 1,grow");
//---- panelLabel ----
panelLabel.setText("JPanel:");
add(panelLabel, "cell 0 12");
//======== panel5 ========
{
panel5.putClientProperty("FlatLaf.style", "arc: 16; background: darken($Panel.background,5%)");
panel5.setLayout(new BorderLayout());
//---- label9 ----
label9.setText("rounded background");
label9.setHorizontalAlignment(SwingConstants.CENTER);
panel5.add(label9, BorderLayout.CENTER);
}
add(panel5, "cell 1 12 4 1,growy,width 150");
//======== panel4 ========
{
panel4.putClientProperty("FlatLaf.style", "border: 1,1,1,1,@disabledForeground,1,16; background: darken($Panel.background,5%)");
panel4.setLayout(new BorderLayout());
//---- label8 ----
label8.setText("rounded border");
label8.setHorizontalAlignment(SwingConstants.CENTER);
panel4.add(label8, BorderLayout.CENTER);
}
add(panel4, "cell 1 12 4 1,growy,width 150");
//---- labelLabel ----
labelLabel.setText("JLabel:");
add(labelLabel, "cell 0 13");
//---- label13 ----
label13.setText("rounded background");
label13.putClientProperty("FlatLaf.style", "arc: 999; border: 2,10,2,10");
label13.setBackground(new Color(0xb8e4f3));
label13.setForeground(new Color(0x135b76));
add(label13, "cell 1 13 4 1");
//---- label10 ----
label10.setText("rounded border");
label10.putClientProperty("FlatLaf.style", "border: 2,10,2,10,#135b76,1,999");
label10.setBackground(new Color(0xb8e4f3));
label10.setForeground(new Color(0x135b76));
add(label10, "cell 1 13 4 1");
//---- buttonGroup1 ---- //---- buttonGroup1 ----
ButtonGroup buttonGroup1 = new ButtonGroup(); ButtonGroup buttonGroup1 = new ButtonGroup();
buttonGroup1.add(toggleButton1); buttonGroup1.add(toggleButton1);

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "7.0.5.0.404" Java: "17.0.2" encoding: "UTF-8" JFDML JFormDesigner: "8.2.2.0.9999" Java: "21.0.1" encoding: "UTF-8"
new FormModel { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -9,7 +9,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3" "$layoutConstraints": "insets dialog,hidemode 3"
"$columnConstraints": "[][][][][]" "$columnConstraints": "[][][][][]"
"$rowConstraints": "[][][][][][][][][][][][100,top]" "$rowConstraints": "[][][][][][][][][][][][100,top][50,top][]"
} ) { } ) {
name: "this" name: "this"
add( new FormComponent( "javax.swing.JLabel" ) { add( new FormComponent( "javax.swing.JLabel" ) {
@@ -467,6 +467,62 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 11 4 1,grow" "value": "cell 1 11 4 1,grow"
} ) } )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "panelLabel"
"text": "JPanel:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 12"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "panel5"
"$client.FlatLaf.style": "arc: 16; background: darken($Panel.background,5%)"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label9"
"text": "rounded background"
"horizontalAlignment": 0
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 12 4 1,growy,width 150"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "panel4"
"$client.FlatLaf.style": "border: 1,1,1,1,@disabledForeground,1,16; background: darken($Panel.background,5%)"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label8"
"text": "rounded border"
"horizontalAlignment": 0
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 12 4 1,growy,width 150"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "labelLabel"
"text": "JLabel:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 13"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label13"
"text": "rounded background"
"$client.FlatLaf.style": "arc: 999; border: 2,10,2,10"
"background": new java.awt.Color( 184, 228, 243, 255 )
"foreground": new java.awt.Color( 19, 91, 118, 255 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 13 4 1"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label10"
"text": "rounded border"
"$client.FlatLaf.style": "border: 2,10,2,10,#135b76,1,999"
"background": new java.awt.Color( 184, 228, 243, 255 )
"foreground": new java.awt.Color( 19, 91, 118, 255 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 13 4 1"
} )
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 ) "location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 700, 550 ) "size": new java.awt.Dimension( 700, 550 )
@@ -474,7 +530,7 @@ new FormModel {
add( new FormNonVisual( "javax.swing.ButtonGroup" ) { add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
name: "buttonGroup1" name: "buttonGroup1"
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 560 ) "location": new java.awt.Point( 0, 600 )
} ) } )
} }
} }