diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java
index dccdffb5..cec81586 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java
@@ -103,9 +103,11 @@ public class FlatBorder
FlatUIUtils.setRenderingHints( g2 );
float focusWidth = scale( (float) getFocusWidth( c ) );
+ float focusInnerWidth = 0;
float borderWidth = scale( (float) getBorderWidth( c ) );
float arc = scale( (float) getArc( c ) );
Color outlineColor = getOutlineColor( c );
+ Color focusColor = null;
// paint outer border
if( outlineColor != null || isFocused( c ) ) {
@@ -114,15 +116,16 @@ public class FlatBorder
: 0;
if( focusWidth > 0 || innerWidth > 0 ) {
- g2.setColor( (outlineColor != null) ? outlineColor : getFocusColor( c ) );
- FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height,
- focusWidth, borderWidth + scale( innerWidth ), arc );
+ focusColor = (outlineColor != null) ? outlineColor : getFocusColor( c );
+ focusInnerWidth = borderWidth + scale( innerWidth );
}
}
// paint border
- g2.setPaint( (outlineColor != null) ? outlineColor : getBorderColor( c ) );
- FlatUIUtils.paintComponentBorder( g2, x, y, width, height, focusWidth, borderWidth, arc );
+ Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
+ FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
+ focusWidth, 1, focusInnerWidth, borderWidth, arc,
+ focusColor, borderColor, null );
} finally {
g2.dispose();
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java
index d9ac552a..083dd6a8 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java
@@ -114,9 +114,10 @@ public class FlatButtonBorder
width -= spacing.left + spacing.right;
height -= spacing.top + spacing.bottom;
- g2.setColor( (outlineColor != null) ? outlineColor : getFocusColor( c ) );
- // not using paintComponentOuterBorder() here because its round edges look too "thick"
- FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0, focusWidth, arc );
+ Color color = (outlineColor != null) ? outlineColor : getFocusColor( c );
+ // not using focus border painting of paintOutlinedComponent() here
+ // because its round edges look too "thick"
+ FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, 0, 0, 0, focusWidth, arc, null, color, null );
} finally {
g2.dispose();
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java
index d8e3ea69..6f9000eb 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLineBorder.java
@@ -61,8 +61,8 @@ public class FlatLineBorder
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
- g2.setColor( getLineColor() );
- FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0f, scale( getLineThickness() ), 0f );
+ FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
+ 0, 0, 0, scale( getLineThickness() ), 0, null, getLineColor(), null );
} finally {
g2.dispose();
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java
index c0dae004..d524cc8d 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java
@@ -28,6 +28,7 @@ import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
+import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
@@ -351,104 +352,6 @@ public class FlatUIUtils
: color;
}
- /**
- * Paints an outer border, which is usually a focus border.
- *
- * The outside bounds of the painted border are {@code x,y,width,height}.
- * The line thickness of the painted border is {@code focusWidth + lineWidth}.
- * The given arc diameter refers to the inner rectangle ({@code x,y,width,height} minus {@code focusWidth}).
- *
- * @see #paintComponentBorder
- * @see #paintComponentBackground
- */
- public static void paintComponentOuterBorder( Graphics2D g, int x, int y, int width, int height,
- float focusWidth, float lineWidth, float arc )
- {
- if( focusWidth + lineWidth == 0 )
- return; // nothing to paint
-
- double systemScaleFactor = UIScale.getSystemScaleFactor( g );
- if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
- // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
- HiDPIUtils.paintAtScale1x( g, x, y, width, height,
- (g2d, x2, y2, width2, height2, scaleFactor) -> {
- paintComponentOuterBorderImpl( g2d, x2, y2, width2, height2,
- (float) (focusWidth * scaleFactor), (float) (lineWidth * scaleFactor), (float) (arc * scaleFactor) );
- } );
- return;
- }
-
- paintComponentOuterBorderImpl( g, x, y, width, height, focusWidth, lineWidth, arc );
- }
-
- private static void paintComponentOuterBorderImpl( Graphics2D g, int x, int y, int width, int height,
- float focusWidth, float lineWidth, float arc )
- {
- float ow = focusWidth + lineWidth;
- float outerArc = arc + (focusWidth * 2);
- float innerArc = arc - (lineWidth * 2);
-
- // reduce outer arc slightly for small arcs to make the curve slightly wider
- if( focusWidth > 0 && arc > 0 && arc < UIScale.scale( 10 ) )
- outerArc -= UIScale.scale( 2f );
-
- Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
- path.append( createComponentRectangle( x, y, width, height, outerArc ), false );
- path.append( createComponentRectangle( x + ow, y + ow, width - (ow * 2), height - (ow * 2), innerArc ), false );
- g.fill( path );
- }
-
- /**
- * Draws the border of a component as round rectangle.
- *
- * The outside bounds of the painted border are
- * {@code x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)}.
- * The line thickness of the painted border is {@code lineWidth}.
- * The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
- *
- * @see #paintComponentOuterBorder
- * @see #paintComponentBackground
- */
- public static void paintComponentBorder( Graphics2D g, int x, int y, int width, int height,
- float focusWidth, float lineWidth, float arc )
- {
- if( lineWidth == 0 )
- return; // nothing to paint
-
- double systemScaleFactor = UIScale.getSystemScaleFactor( g );
- if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
- // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
- HiDPIUtils.paintAtScale1x( g, x, y, width, height,
- (g2d, x2, y2, width2, height2, scaleFactor) -> {
- paintComponentBorderImpl( g2d, x2, y2, width2, height2,
- (float) (focusWidth * scaleFactor), (float) (lineWidth * scaleFactor), (float) (arc * scaleFactor) );
- } );
- return;
- }
-
- paintComponentBorderImpl( g, x, y, width, height, focusWidth, lineWidth, arc );
- }
-
- private static void paintComponentBorderImpl( Graphics2D g, int x, int y, int width, int height,
- float focusWidth, float lineWidth, float arc )
- {
- float x1 = x + focusWidth;
- float y1 = y + focusWidth;
- float width1 = width - focusWidth * 2;
- float height1 = height - focusWidth * 2;
- float arc2 = arc - (lineWidth * 2);
-
- Shape r1 = createComponentRectangle( x1, y1, width1, height1, arc );
- Shape r2 = createComponentRectangle(
- x1 + lineWidth, y1 + lineWidth,
- width1 - lineWidth * 2, height1 - lineWidth * 2, arc2 );
-
- Path2D border = new Path2D.Float( Path2D.WIND_EVEN_ODD );
- border.append( r1, false );
- border.append( r2, false );
- g.fill( border );
- }
-
/**
* Fills the background of a component with a round rectangle.
*
@@ -456,32 +359,201 @@ public class FlatUIUtils
* {@code x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)}.
* The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
*
- * @see #paintComponentOuterBorder
- * @see #paintComponentBorder
+ * @see #paintOutlinedComponent
*/
public static void paintComponentBackground( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float arc )
+ {
+ paintOutlinedComponent( g, x, y, width, height, focusWidth, 0, 0, 0, arc, null, null, g.getPaint() );
+ }
+
+ /**
+ * Paints an outlined component with rounded corners, consisting of following parts:
+ *
+ * - an (optional) outer border, which is usually a focus indicator
+ *
- an (optional) component border
+ *
- the (optional) component background
+ *
+ * Each part is painted only if the corresponding part color is not {@code null}.
+ * The parts are painted in this order:
+ *
+ *
+ * - background
+ *
- focus border
+ *
- border
+ *
+ *
+ * Background:
+ * The bounds of the filled round rectangle are
+ * {@code [x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)]}.
+ * The focus border and the border may paint over the background.
+ *
+ *
+ * Focus border:
+ * The outside bounds of the painted focus border are {@code [x, y, width, height]}.
+ * The thickness of the painted focus border is {@code (focusWidth * focusWidthFraction) + focusInnerWidth}.
+ * The border may paint over the focus border if {@code focusInnerWidth > 0}.
+ *
+ *
+ * Border:
+ * The outside bounds of the painted border are
+ * {@code [x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)]}.
+ * The thickness of the painted border is {@code lineWidth}.
+ *
+ * @param g the graphics context used for painting
+ * @param x the x coordinate of the component
+ * @param y the y coordinate of the component
+ * @param width the width of the component
+ * @param height the height of the component
+ * @param focusWidth the width of the focus border, or {@code 0}
+ * @param focusWidthFraction specified how much of the focus border is painted (in range 0 - 1);
+ * can be used for animation;
+ * the painted thickness of the focus border is {@code (focusWidth * focusWidthFraction) + focusInnerWidth}
+ * @param focusInnerWidth the inner width of the focus border, or {@code 0};
+ * if a border is painted then {@code focusInnerWidth} needs to be larger
+ * than {@code lineWidth} to be not hidden by the border
+ * @param borderWidth the width of the border, or {@code 0}
+ * @param arc the arc diameter used for the outside shape of the component border;
+ * the other needed arc diameters are computed from this arc diameter
+ * @param focusColor the color of the focus border, or {@code null}
+ * @param borderColor the color of the border, or {@code null}
+ * @param background the background color of the component, or {@code null}
+ *
+ * @since 2
+ */
+ public static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
+ float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
+ Paint focusColor, Paint borderColor, Paint background )
{
double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
- paintComponentBackgroundImpl( g2d, x2, y2, width2, height2,
- (float) (focusWidth * scaleFactor), (float) (arc * scaleFactor) );
+ paintOutlinedComponentImpl( g2d, x2, y2, width2, height2,
+ (float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor),
+ (float) (borderWidth * scaleFactor), (float) (arc * scaleFactor),
+ focusColor, borderColor, background );
} );
return;
}
- paintComponentBackgroundImpl( g, x, y, width, height, focusWidth, arc );
+ paintOutlinedComponentImpl( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
+ borderWidth, arc, focusColor, borderColor, background );
}
- private static void paintComponentBackgroundImpl( Graphics2D g, int x, int y, int width, int height,
- float focusWidth, float arc )
+ private static void paintOutlinedComponentImpl( Graphics2D g, int x, int y, int width, int height,
+ float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
+ Paint focusColor, Paint borderColor, Paint background )
{
- g.fill( createComponentRectangle(
- x + focusWidth, y + focusWidth,
- width - focusWidth * 2, height - focusWidth * 2, arc ) );
+ // outside bounds of the border and the background
+ float x1 = x + focusWidth;
+ float y1 = y + focusWidth;
+ float w1 = width - focusWidth * 2;
+ float h1 = height - focusWidth * 2;
+
+ // fill background
+ // bounds: x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)
+ // arc diameter: arc
+ if( background != null ) {
+ g.setPaint( background );
+ g.fill( createComponentRectangle( x1, y1, w1, h1, arc ) );
+ }
+
+ // optimization: paint focus border and border in single operation if colors are equal
+ if( borderColor != null && borderColor.equals( focusColor ) ) {
+ borderColor = null;
+ focusInnerWidth = Math.max( focusInnerWidth, borderWidth );
+ }
+
+ // paint focus border
+ // outer bounds: x, y, width, height
+ // thickness: focusWidth + focusInnerWidth
+ // outer arc diameter: arc + (focusWidth * 2)
+ // inner arc diameter: arc - (focusInnerWidth * 2)
+ float paintedFocusWidth = (focusWidth * focusWidthFraction) + focusInnerWidth;
+ if( focusColor != null && paintedFocusWidth != 0 ) {
+ // outside bounds of the focus border
+ float inset = focusWidth - (focusWidth * focusWidthFraction);
+ float x2 = x + inset;
+ float y2 = y + inset;
+ float w2 = width - (inset * 2);
+ float h2 = height - (inset * 2);
+
+ float outerArc = arc + (focusWidth * 2);
+ float innerArc = arc - (focusInnerWidth * 2);
+
+ // reduce outer arc slightly for small arcs to make the curve slightly wider
+ if( focusWidth > 0 && arc > 0 && arc < UIScale.scale( 10 ) )
+ outerArc -= UIScale.scale( 2f );
+
+ // consider focus width fraction
+ if( focusWidthFraction != 1 )
+ outerArc = arc + ((outerArc - arc) * focusWidthFraction);
+
+ g.setPaint( focusColor );
+ paintOutline( g, x2, y2, w2, h2, paintedFocusWidth, outerArc, innerArc );
+ }
+
+ // paint border
+ // outer bounds: x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)
+ // thickness: borderWidth
+ // outer arc diameter: arc
+ // inner arc diameter: arc - (borderWidth * 2)
+ if( borderColor != null && borderWidth != 0 ) {
+ g.setPaint( borderColor );
+ paintOutline( g, x1, y1, w1, h1, borderWidth, arc );
+ }
+ }
+
+ /**
+ * Paints an outline at the given bounds using the given line width.
+ * Depending on the given arc, a rectangle, rounded rectangle or circle (if w == h) is painted.
+ *
+ * @param g the graphics context used for painting
+ * @param x the x coordinate of the outline
+ * @param y the y coordinate of the outline
+ * @param w the width of the outline
+ * @param h the height of the outline
+ * @param lineWidth the width of the outline
+ * @param arc the arc diameter used for the outside shape of the outline
+ *
+ * @since 2
+ */
+ public static void paintOutline( Graphics2D g, float x, float y, float w, float h,
+ float lineWidth, float arc )
+ {
+ paintOutline( g, x, y, w, h, lineWidth, arc, arc - (lineWidth * 2) );
+ }
+
+ /**
+ * Paints an outline at the given bounds using the given line width.
+ * Depending on the given arc, a rectangle, rounded rectangle or circle (if w == h) is painted.
+ *
+ * @param g the graphics context used for painting
+ * @param x the x coordinate of the outline
+ * @param y the y coordinate of the outline
+ * @param w the width of the outline
+ * @param h the height of the outline
+ * @param lineWidth the width of the outline
+ * @param arc the arc diameter used for the outside shape of the outline
+ * @param innerArc the arc diameter used for the inside shape of the outline
+ *
+ * @since 2
+ */
+ public static void paintOutline( Graphics2D g, float x, float y, float w, float h,
+ float lineWidth, float arc, float innerArc )
+ {
+ if( lineWidth == 0 || w <= 0 || h <= 0 )
+ return;
+
+ float t = lineWidth;
+ float t2x = t * 2;
+
+ Path2D border = new Path2D.Float( Path2D.WIND_EVEN_ODD );
+ border.append( createComponentRectangle( x, y, w, h, arc ), false );
+ border.append( createComponentRectangle( x + t, y + t, w - t2x, h - t2x, innerArc ), false );
+ g.fill( border );
}
/**
@@ -492,6 +564,9 @@ public class FlatUIUtils
if( arc <= 0 )
return new Rectangle2D.Float( x, y, w, h );
+ if( w == h && arc >= w )
+ return new Ellipse2D.Float( x, y, w, h );
+
arc = Math.min( arc, Math.min( w, h ) );
return new RoundRectangle2D.Float( x, y, w, h, arc, arc );
}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.java
index c37f93fa..c1101e75 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.java
@@ -22,6 +22,7 @@ import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
+import java.util.Hashtable;
import javax.swing.*;
import javax.swing.border.*;
import com.formdev.flatlaf.ui.FlatArrowButton;
@@ -45,6 +46,11 @@ public class FlatPaintingTest
FlatPaintingTest() {
initComponents();
+
+ Hashtable labels = new Hashtable<>();
+ for( int i = 0; i <= 5; i++ )
+ labels.put( i * 10, new JLabel( Integer.toString( i ) ) );
+ focusInnerWidthSlider.setLabelTable( labels );
}
@Override
@@ -55,6 +61,39 @@ public class FlatPaintingTest
getVerticalScrollBar().setUnitIncrement( UIScale.scale( 25 ) );
}
+ private void focusWidthFractionChanged() {
+ float focusWidthFraction = focusWidthFractionSlider.getValue() / 100f;
+
+ FlatTestFrame.updateComponentsRecur( (Container) getViewport().getView(), (c, type) -> {
+ if( c instanceof BorderPainter )
+ ((BorderPainter)c).focusWidthFraction = focusWidthFraction;
+ } );
+
+ repaint();
+ }
+
+ private void focusInnerWidthChanged() {
+ float focusInnerWidth = focusInnerWidthSlider.getValue() / 10f;
+
+ FlatTestFrame.updateComponentsRecur( (Container) getViewport().getView(), (c, type) -> {
+ if( c instanceof BorderPainter )
+ ((BorderPainter)c).focusInnerWidth = focusInnerWidth;
+ } );
+
+ repaint();
+ }
+
+ private void translucentChanged() {
+ boolean translucent = translucentCheckBox.isSelected();
+
+ FlatTestFrame.updateComponentsRecur( (Container) getViewport().getView(), (c, type) -> {
+ if( c instanceof BorderPainter )
+ ((BorderPainter)c).translucent = translucent;
+ } );
+
+ repaint();
+ }
+
private void arrowSizeChanged() {
int width = (int) arrowWidthSpinner.getValue();
int height = (int) arrowHeightSpinner.getValue();
@@ -167,17 +206,24 @@ public class FlatPaintingTest
JPanel panel2 = new JPanel();
arrowPainter7 = new FlatPaintingTest.ArrowPainter();
arrowPainter8 = new FlatPaintingTest.ArrowPainter();
+ JPanel panel6 = new JPanel();
+ JPanel panel7 = new JPanel();
+ JLabel focusWidthFractionLabel = new JLabel();
+ focusWidthFractionSlider = new JSlider();
+ JLabel focusInnerWidthLabel = new JLabel();
+ focusInnerWidthSlider = new JSlider();
+ translucentCheckBox = new JCheckBox();
JPanel panel5 = new JPanel();
JLabel arrowWidthLabel = new JLabel();
arrowWidthSpinner = new JSpinner();
+ vectorCheckBox = new JCheckBox();
JLabel arrowHeightLabel = new JLabel();
arrowHeightSpinner = new JSpinner();
+ buttonCheckBox = new JCheckBox();
JLabel arrowSizeLabel = new JLabel();
arrowSizeSpinner = new JSpinner();
JLabel offsetLabel = new JLabel();
offsetSpinner = new JSpinner();
- vectorCheckBox = new JCheckBox();
- buttonCheckBox = new JCheckBox();
FlatPaintingTest.ArrowPainter arrowPainter9 = new FlatPaintingTest.ArrowPainter();
FlatPaintingTest.ArrowPainter arrowPainter10 = new FlatPaintingTest.ArrowPainter();
FlatPaintingTest.ArrowPainter arrowPainter11 = new FlatPaintingTest.ArrowPainter();
@@ -527,69 +573,122 @@ public class FlatPaintingTest
}
flatTestPanel1.add(panel2, "cell 5 5,align left top,grow 0 0");
- //======== panel5 ========
+ //======== panel6 ========
{
- panel5.setBorder(new TitledBorder("Arrow Control"));
- panel5.setLayout(new MigLayout(
- "hidemode 3",
+ panel6.setLayout(new MigLayout(
+ "insets 0,hidemode 3",
// columns
- "[fill]" +
- "[fill]",
+ "[grow,fill]",
// rows
- "[]" +
- "[]" +
- "[]" +
- "[]" +
- "[]" +
+ "[]unrel" +
"[]"));
- //---- arrowWidthLabel ----
- arrowWidthLabel.setText("Width:");
- panel5.add(arrowWidthLabel, "cell 0 0");
+ //======== panel7 ========
+ {
+ panel7.setBorder(new TitledBorder("Outlined Component Control"));
+ panel7.setLayout(new MigLayout(
+ "hidemode 3",
+ // columns
+ "[fill]" +
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]"));
- //---- arrowWidthSpinner ----
- arrowWidthSpinner.setModel(new SpinnerNumberModel(20, 0, null, 1));
- arrowWidthSpinner.addChangeListener(e -> arrowSizeChanged());
- panel5.add(arrowWidthSpinner, "cell 1 0");
+ //---- focusWidthFractionLabel ----
+ focusWidthFractionLabel.setText("Focus width fraction:");
+ panel7.add(focusWidthFractionLabel, "cell 0 0");
- //---- arrowHeightLabel ----
- arrowHeightLabel.setText("Height:");
- panel5.add(arrowHeightLabel, "cell 0 1");
+ //---- focusWidthFractionSlider ----
+ focusWidthFractionSlider.setValue(100);
+ focusWidthFractionSlider.setMajorTickSpacing(25);
+ focusWidthFractionSlider.setPaintLabels(true);
+ focusWidthFractionSlider.addChangeListener(e -> focusWidthFractionChanged());
+ panel7.add(focusWidthFractionSlider, "cell 1 0");
- //---- arrowHeightSpinner ----
- arrowHeightSpinner.setModel(new SpinnerNumberModel(20, 0, null, 1));
- arrowHeightSpinner.addChangeListener(e -> arrowSizeChanged());
- panel5.add(arrowHeightSpinner, "cell 1 1");
+ //---- focusInnerWidthLabel ----
+ focusInnerWidthLabel.setText("Focus inner width:");
+ panel7.add(focusInnerWidthLabel, "cell 0 1");
- //---- arrowSizeLabel ----
- arrowSizeLabel.setText("Arrow Size:");
- panel5.add(arrowSizeLabel, "cell 0 2");
+ //---- focusInnerWidthSlider ----
+ focusInnerWidthSlider.setPaintLabels(true);
+ focusInnerWidthSlider.setValue(10);
+ focusInnerWidthSlider.setMaximum(50);
+ focusInnerWidthSlider.addChangeListener(e -> focusInnerWidthChanged());
+ panel7.add(focusInnerWidthSlider, "cell 1 1");
- //---- arrowSizeSpinner ----
- arrowSizeSpinner.setModel(new SpinnerNumberModel(8, 2, null, 1));
- arrowSizeSpinner.addChangeListener(e -> arrowSizeChanged());
- panel5.add(arrowSizeSpinner, "cell 1 2");
+ //---- translucentCheckBox ----
+ translucentCheckBox.setText("translucent");
+ translucentCheckBox.addActionListener(e -> translucentChanged());
+ panel7.add(translucentCheckBox, "cell 0 2 2 1,alignx left,growx 0");
+ }
+ panel6.add(panel7, "cell 0 0");
- //---- offsetLabel ----
- offsetLabel.setText("Offset:");
- panel5.add(offsetLabel, "cell 0 3");
+ //======== panel5 ========
+ {
+ panel5.setBorder(new TitledBorder("Arrow Control"));
+ panel5.setLayout(new MigLayout(
+ "hidemode 3",
+ // columns
+ "[fill]" +
+ "[fill]" +
+ "[fill]",
+ // rows
+ "[]" +
+ "[]" +
+ "[]" +
+ "[]"));
- //---- offsetSpinner ----
- offsetSpinner.setModel(new SpinnerNumberModel(1.0F, null, null, 0.05F));
- offsetSpinner.addChangeListener(e -> offsetChanged());
- panel5.add(offsetSpinner, "cell 1 3");
+ //---- arrowWidthLabel ----
+ arrowWidthLabel.setText("Width:");
+ panel5.add(arrowWidthLabel, "cell 0 0");
- //---- vectorCheckBox ----
- vectorCheckBox.setText("vector");
- vectorCheckBox.addActionListener(e -> vectorChanged());
- panel5.add(vectorCheckBox, "cell 0 4 2 1,alignx left,growx 0");
+ //---- arrowWidthSpinner ----
+ arrowWidthSpinner.setModel(new SpinnerNumberModel(20, 0, null, 1));
+ arrowWidthSpinner.addChangeListener(e -> arrowSizeChanged());
+ panel5.add(arrowWidthSpinner, "cell 1 0");
- //---- buttonCheckBox ----
- buttonCheckBox.setText("FlatArrowButton");
- buttonCheckBox.addActionListener(e -> arrowButtonChanged());
- panel5.add(buttonCheckBox, "cell 0 5 2 1,alignx left,growx 0");
+ //---- vectorCheckBox ----
+ vectorCheckBox.setText("vector");
+ vectorCheckBox.addActionListener(e -> vectorChanged());
+ panel5.add(vectorCheckBox, "cell 2 0,alignx left,growx 0");
+
+ //---- arrowHeightLabel ----
+ arrowHeightLabel.setText("Height:");
+ panel5.add(arrowHeightLabel, "cell 0 1");
+
+ //---- arrowHeightSpinner ----
+ arrowHeightSpinner.setModel(new SpinnerNumberModel(20, 0, null, 1));
+ arrowHeightSpinner.addChangeListener(e -> arrowSizeChanged());
+ panel5.add(arrowHeightSpinner, "cell 1 1");
+
+ //---- buttonCheckBox ----
+ buttonCheckBox.setText("FlatArrowButton");
+ buttonCheckBox.addActionListener(e -> arrowButtonChanged());
+ panel5.add(buttonCheckBox, "cell 2 1,alignx left,growx 0");
+
+ //---- arrowSizeLabel ----
+ arrowSizeLabel.setText("Arrow Size:");
+ panel5.add(arrowSizeLabel, "cell 0 2");
+
+ //---- arrowSizeSpinner ----
+ arrowSizeSpinner.setModel(new SpinnerNumberModel(8, 2, null, 1));
+ arrowSizeSpinner.addChangeListener(e -> arrowSizeChanged());
+ panel5.add(arrowSizeSpinner, "cell 1 2");
+
+ //---- offsetLabel ----
+ offsetLabel.setText("Offset:");
+ panel5.add(offsetLabel, "cell 0 3");
+
+ //---- offsetSpinner ----
+ offsetSpinner.setModel(new SpinnerNumberModel(1.0F, null, null, 0.05F));
+ offsetSpinner.addChangeListener(e -> offsetChanged());
+ panel5.add(offsetSpinner, "cell 1 3");
+ }
+ panel6.add(panel5, "cell 0 1");
}
- flatTestPanel1.add(panel5, "cell 6 5 1 2,aligny top,growy 0");
+ flatTestPanel1.add(panel6, "cell 6 5 3 2,aligny top,growy 0");
//---- arrowPainter9 ----
arrowPainter9.setScale(8.0F);
@@ -670,12 +769,15 @@ public class FlatPaintingTest
private FlatPaintingTest.ArrowPainter arrowPainter6;
private FlatPaintingTest.ArrowPainter arrowPainter7;
private FlatPaintingTest.ArrowPainter arrowPainter8;
+ private JSlider focusWidthFractionSlider;
+ private JSlider focusInnerWidthSlider;
+ private JCheckBox translucentCheckBox;
private JSpinner arrowWidthSpinner;
+ private JCheckBox vectorCheckBox;
private JSpinner arrowHeightSpinner;
+ private JCheckBox buttonCheckBox;
private JSpinner arrowSizeSpinner;
private JSpinner offsetSpinner;
- private JCheckBox vectorCheckBox;
- private JCheckBox buttonCheckBox;
private FlatPaintingTest.ArrowPainter arrowPainter13;
private FlatPaintingTest.ArrowPainter arrowPainter14;
private FlatPaintingTest.ArrowPainter arrowPainter15;
@@ -687,6 +789,9 @@ public class FlatPaintingTest
public static class BorderPainter
extends JComponent
{
+ private static final Color TRANSLUCENT_BLUE = new Color( 0x880000ff, true );
+ private static final Color TRANSLUCENT_RED = new Color( 0x88ff0000, true );
+
private int w = 20;
private int h = 20;
private int focusWidth = 2;
@@ -697,6 +802,10 @@ public class FlatPaintingTest
private boolean paintBorder = true;
private boolean paintFocus = true;
+ float focusWidthFraction = 1;
+ float focusInnerWidth = 1;
+ boolean translucent;
+
public BorderPainter() {
}
@@ -793,23 +902,17 @@ public class FlatPaintingTest
int width = UIScale.scale( w );
int height = UIScale.scale( h );
float focusWidth = UIScale.scale( (float) this.focusWidth );
+ float focusInnerWidth = UIScale.scale( this.focusInnerWidth );
float lineWidth = UIScale.scale( (float) this.lineWidth );
float arc = UIScale.scale( (float) this.arc );
- if( paintBackground ) {
- g.setColor( Color.green );
- FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
- }
+ Color background = paintBackground ? Color.green : null;
+ Color focusColor = paintFocus ? (translucent ? TRANSLUCENT_BLUE : Color.blue) : null;
+ Color borderColor = paintBorder ? (translucent ? TRANSLUCENT_RED : Color.red) : null;
- if( paintFocus ) {
- g.setColor( Color.blue );
- FlatUIUtils.paintComponentOuterBorder( g2, 0, 0, width, height, focusWidth, lineWidth, arc );
- }
-
- if( paintBorder ) {
- g.setColor( Color.red );
- FlatUIUtils.paintComponentBorder( g2, 0, 0, width, height, focusWidth, lineWidth, arc );
- }
+ FlatUIUtils.paintOutlinedComponent( g2, 0, 0, width, height,
+ focusWidth, focusWidthFraction, focusInnerWidth, lineWidth, arc,
+ focusColor, borderColor, background );
HiDPIUtils.paintAtScale1x( g2, 0, 0, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.jfd
index bd77fa17..3b23d601 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.jfd
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingTest.jfd
@@ -428,110 +428,175 @@ new FormModel {
"value": "cell 5 5,align left top,grow 0 0"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
- "$layoutConstraints": "hidemode 3"
- "$columnConstraints": "[fill][fill]"
- "$rowConstraints": "[][][][][][]"
+ "$columnConstraints": "[grow,fill]"
+ "$rowConstraints": "[]unrel[]"
+ "$layoutConstraints": "insets 0,hidemode 3"
} ) {
- name: "panel5"
- "border": new javax.swing.border.TitledBorder( "Arrow Control" )
- add( new FormComponent( "javax.swing.JLabel" ) {
- name: "arrowWidthLabel"
- "text": "Width:"
+ name: "panel6"
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "hidemode 3"
+ "$columnConstraints": "[fill][fill]"
+ "$rowConstraints": "[][][]"
+ } ) {
+ name: "panel7"
+ "border": new javax.swing.border.TitledBorder( "Outlined Component Control" )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "focusWidthFractionLabel"
+ "text": "Focus width fraction:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JSlider" ) {
+ name: "focusWidthFractionSlider"
+ "value": 100
+ "majorTickSpacing": 25
+ "paintLabels": true
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "focusWidthFractionChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "focusInnerWidthLabel"
+ "text": "Focus inner width:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JSlider" ) {
+ name: "focusInnerWidthSlider"
+ "paintLabels": true
+ "value": 10
+ "maximum": 50
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "focusInnerWidthChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "translucentCheckBox"
+ "text": "translucent"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "translucentChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2 2 1,alignx left,growx 0"
+ } )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
- add( new FormComponent( "javax.swing.JSpinner" ) {
- name: "arrowWidthSpinner"
- "model": new javax.swing.SpinnerNumberModel {
- minimum: 0
- value: 20
- }
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- }
- addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arrowSizeChanged", false ) )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 0"
- } )
- add( new FormComponent( "javax.swing.JLabel" ) {
- name: "arrowHeightLabel"
- "text": "Height:"
+ add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
+ "$layoutConstraints": "hidemode 3"
+ "$columnConstraints": "[fill][fill][fill]"
+ "$rowConstraints": "[][][][]"
+ } ) {
+ name: "panel5"
+ "border": new javax.swing.border.TitledBorder( "Arrow Control" )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "arrowWidthLabel"
+ "text": "Width:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 0"
+ } )
+ add( new FormComponent( "javax.swing.JSpinner" ) {
+ name: "arrowWidthSpinner"
+ "model": new javax.swing.SpinnerNumberModel {
+ minimum: 0
+ value: 20
+ }
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arrowSizeChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 0"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "vectorCheckBox"
+ "text": "vector"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "vectorChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 0,alignx left,growx 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "arrowHeightLabel"
+ "text": "Height:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 1"
+ } )
+ add( new FormComponent( "javax.swing.JSpinner" ) {
+ name: "arrowHeightSpinner"
+ "model": new javax.swing.SpinnerNumberModel {
+ minimum: 0
+ value: 20
+ }
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arrowSizeChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 1"
+ } )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "buttonCheckBox"
+ "text": "FlatArrowButton"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "arrowButtonChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 2 1,alignx left,growx 0"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "arrowSizeLabel"
+ "text": "Arrow Size:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 2"
+ } )
+ add( new FormComponent( "javax.swing.JSpinner" ) {
+ name: "arrowSizeSpinner"
+ "model": new javax.swing.SpinnerNumberModel {
+ minimum: 2
+ value: 8
+ }
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arrowSizeChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 2"
+ } )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "offsetLabel"
+ "text": "Offset:"
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 0 3"
+ } )
+ add( new FormComponent( "javax.swing.JSpinner" ) {
+ name: "offsetSpinner"
+ "model": new javax.swing.SpinnerNumberModel {
+ stepSize: 0.05f
+ value: 1.0f
+ }
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "offsetChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 1 3"
+ } )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
- add( new FormComponent( "javax.swing.JSpinner" ) {
- name: "arrowHeightSpinner"
- "model": new javax.swing.SpinnerNumberModel {
- minimum: 0
- value: 20
- }
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- }
- addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arrowSizeChanged", false ) )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 1"
- } )
- add( new FormComponent( "javax.swing.JLabel" ) {
- name: "arrowSizeLabel"
- "text": "Arrow Size:"
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 2"
- } )
- add( new FormComponent( "javax.swing.JSpinner" ) {
- name: "arrowSizeSpinner"
- "model": new javax.swing.SpinnerNumberModel {
- minimum: 2
- value: 8
- }
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- }
- addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arrowSizeChanged", false ) )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 2"
- } )
- add( new FormComponent( "javax.swing.JLabel" ) {
- name: "offsetLabel"
- "text": "Offset:"
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 3"
- } )
- add( new FormComponent( "javax.swing.JSpinner" ) {
- name: "offsetSpinner"
- "model": new javax.swing.SpinnerNumberModel {
- stepSize: 0.05f
- value: 1.0f
- }
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- }
- addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "offsetChanged", false ) )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 1 3"
- } )
- add( new FormComponent( "javax.swing.JCheckBox" ) {
- name: "vectorCheckBox"
- "text": "vector"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- }
- addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "vectorChanged", false ) )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 4 2 1,alignx left,growx 0"
- } )
- add( new FormComponent( "javax.swing.JCheckBox" ) {
- name: "buttonCheckBox"
- "text": "FlatArrowButton"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- }
- addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "arrowButtonChanged", false ) )
- }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 0 5 2 1,alignx left,growx 0"
- } )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 6 5 1 2,aligny top,growy 0"
+ "value": "cell 6 5 3 2,aligny top,growy 0"
} )
add( new FormComponent( "com.formdev.flatlaf.testing.FlatPaintingTest$ArrowPainter" ) {
name: "arrowPainter9"