From 14a9240c4542f1de70286dcaf8148b2ead43186a Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 16 Sep 2021 18:09:32 +0200 Subject: [PATCH] FlatUIUtils: joined the 3 component painting methods (for focus border, border and background) into a single method `paintOutlinedComponent()` - this allows optimized painting if focus color and border color are equal - avoids duplicate code - support focusWidthFraction for future animations --- .../com/formdev/flatlaf/ui/FlatBorder.java | 13 +- .../formdev/flatlaf/ui/FlatButtonBorder.java | 7 +- .../formdev/flatlaf/ui/FlatLineBorder.java | 4 +- .../com/formdev/flatlaf/ui/FlatUIUtils.java | 291 +++++++++++------- .../flatlaf/testing/FlatPaintingTest.java | 233 ++++++++++---- .../flatlaf/testing/FlatPaintingTest.jfd | 257 ++++++++++------ 6 files changed, 526 insertions(+), 279 deletions(-) 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: + *

+ * Each part is painted only if the corresponding part color is not {@code null}. + * The parts are painted in this order: + *

+ *

    + *
  1. background + *
  2. focus border + *
  3. 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"