Tree: support rounded selection

This commit is contained in:
Karl Tauber
2022-06-02 12:03:20 +02:00
parent f1792e46c6
commit 7bf1b26812
10 changed files with 133 additions and 13 deletions

View File

@@ -353,15 +353,9 @@ debug*/
/** @since 3 */ /** @since 3 */
protected void paintSelection( Graphics g, Color selectionBackground, Insets selectionInsets, int selectionArc ) { protected void paintSelection( Graphics g, Color selectionBackground, Insets selectionInsets, int selectionArc ) {
Rectangle r = FlatUIUtils.subtractInsets( new Rectangle( menuItem.getSize() ), scale( selectionInsets ) );
g.setColor( deriveBackground( selectionBackground ) ); g.setColor( deriveBackground( selectionBackground ) );
if( selectionArc > 0 ) { FlatUIUtils.paintSelection( (Graphics2D) g, 0, 0, menuItem.getWidth(), menuItem.getHeight(),
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); scale( selectionInsets ), scale( (float) selectionArc ), 0 );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, r.x, r.y, r.width, r.height, 0, scale( selectionArc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} else
g.fillRect( r.x, r.y, r.width, r.height );
} }
/** @since 3 */ /** @since 3 */

View File

@@ -21,6 +21,7 @@ import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@@ -36,6 +37,7 @@ import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.JTree.DropLocation; import javax.swing.JTree.DropLocation;
import javax.swing.event.TreeSelectionListener;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeCellRenderer;
@@ -96,6 +98,8 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Tree.selectionForeground Color * @uiDefault Tree.selectionForeground Color
* @uiDefault Tree.selectionInactiveBackground Color * @uiDefault Tree.selectionInactiveBackground Color
* @uiDefault Tree.selectionInactiveForeground Color * @uiDefault Tree.selectionInactiveForeground Color
* @uiDefault Tree.selectionInsets Insets
* @uiDefault Tree.selectionArc int
* @uiDefault Tree.wideSelection boolean * @uiDefault Tree.wideSelection boolean
* @uiDefault Tree.showCellFocusIndicator boolean * @uiDefault Tree.showCellFocusIndicator boolean
* *
@@ -132,6 +136,8 @@ public class FlatTreeUI
@Styleable protected Color selectionInactiveBackground; @Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground; @Styleable protected Color selectionInactiveForeground;
@Styleable protected Color selectionBorderColor; @Styleable protected Color selectionBorderColor;
/** @since 3 */ @Styleable protected Insets selectionInsets;
/** @since 3 */ @Styleable protected int selectionArc;
@Styleable protected boolean wideSelection; @Styleable protected boolean wideSelection;
@Styleable protected boolean showCellFocusIndicator; @Styleable protected boolean showCellFocusIndicator;
@@ -175,6 +181,8 @@ public class FlatTreeUI
selectionInactiveBackground = UIManager.getColor( "Tree.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "Tree.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Tree.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "Tree.selectionInactiveForeground" );
selectionBorderColor = UIManager.getColor( "Tree.selectionBorderColor" ); selectionBorderColor = UIManager.getColor( "Tree.selectionBorderColor" );
selectionInsets = UIManager.getInsets( "Tree.selectionInsets" );
selectionArc = UIManager.getInt( "Tree.selectionArc" );
wideSelection = UIManager.getBoolean( "Tree.wideSelection" ); wideSelection = UIManager.getBoolean( "Tree.wideSelection" );
showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" ); showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" );
@@ -295,6 +303,34 @@ public class FlatTreeUI
tree.repaint( 0, r.y, tree.getWidth(), r.height ); tree.repaint( 0, r.y, tree.getWidth(), r.height );
} }
@Override
protected TreeSelectionListener createTreeSelectionListener() {
TreeSelectionListener superListener = super.createTreeSelectionListener();
return e -> {
superListener.valueChanged( e );
// for united rounded selection, repaint parts of the rows that adjoin to the changed rows
TreePath[] changedPaths;
if( useUnitedRoundedSelection() &&
tree.getSelectionCount() > 1 &&
(changedPaths = e.getPaths()) != null )
{
if( changedPaths.length > 4 ) {
// same is done in BasicTreeUI.Handler.valueChanged()
tree.repaint();
} else {
int arcHeight = (int) Math.ceil( UIScale.scale( (float) selectionArc ) );
for( TreePath path : changedPaths ) {
Rectangle r = getPathBounds( tree, path );
if( r != null )
tree.repaint( r.x, r.y - arcHeight, r.width, r.height + (arcHeight * 2) );
}
}
}
};
}
@Override @Override
public Rectangle getPathBounds( JTree tree, TreePath path ) { public Rectangle getPathBounds( JTree tree, TreePath path ) {
Rectangle bounds = super.getPathBounds( tree, path ); Rectangle bounds = super.getPathBounds( tree, path );
@@ -491,7 +527,16 @@ public class FlatTreeUI
private void paintWideSelection( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, private void paintWideSelection( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds,
TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf ) TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf )
{ {
g.fillRect( 0, bounds.y, tree.getWidth(), bounds.height ); int flags = 0;
if( useUnitedRoundedSelection() ) {
if( row > 0 && tree.isRowSelected( row - 1 ) )
flags |= FlatUIUtils.FLAG_TOP_NOT_ROUNDED;
if( row < tree.getRowCount() - 1 && tree.isRowSelected( row + 1 ) )
flags |= FlatUIUtils.FLAG_BOTTOM_NOT_ROUNDED;
}
FlatUIUtils.paintSelection( (Graphics2D) g, 0, bounds.y, tree.getWidth(), bounds.height,
UIScale.scale( selectionInsets ), UIScale.scale( (float) selectionArc ), flags );
// paint expand/collapse icon // paint expand/collapse icon
// (was already painted before, but painted over with wide selection) // (was already painted before, but painted over with wide selection)
@@ -501,6 +546,11 @@ public class FlatTreeUI
} }
} }
private boolean useUnitedRoundedSelection() {
return selectionArc > 0 &&
(selectionInsets == null || (selectionInsets.top == 0 && selectionInsets.bottom == 0));
}
private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds ) { private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds ) {
int xOffset = 0; int xOffset = 0;
int imageOffset = 0; int imageOffset = 0;
@@ -514,7 +564,8 @@ public class FlatTreeUI
xOffset = label.getComponentOrientation().isLeftToRight() ? imageOffset : 0; xOffset = label.getComponentOrientation().isLeftToRight() ? imageOffset : 0;
} }
g.fillRect( bounds.x + xOffset, bounds.y, bounds.width - imageOffset, bounds.height ); FlatUIUtils.paintSelection( (Graphics2D) g, bounds.x + xOffset, bounds.y, bounds.width - imageOffset, bounds.height,
UIScale.scale( selectionInsets ), UIScale.scale( (float) selectionArc ), 0 );
} }
/** /**

View File

@@ -393,9 +393,9 @@ public class FlatUIUtils
} }
/** /**
* Fills the background of a component with a round rectangle. * Fills the background of a component with a rounded rectangle.
* <p> * <p>
* The bounds of the painted round rectangle are * The bounds of the painted rounded rectangle are
* {@code x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)}. * {@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}). * The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
* *
@@ -426,7 +426,7 @@ public class FlatUIUtils
* <p> * <p>
* *
* <strong>Background</strong>: * <strong>Background</strong>:
* The bounds of the filled round rectangle are * The bounds of the filled rounded rectangle are
* {@code [x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)]}. * {@code [x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)]}.
* The focus border and the border may paint over the background. * The focus border and the border may paint over the background.
* <p> * <p>
@@ -624,6 +624,67 @@ public class FlatUIUtils
} }
} }
// flags for paintSelection()
public static final int
FLAG_TOP_LEFT_NOT_ROUNDED = (1 << 0),
FLAG_TOP_RIGHT_NOT_ROUNDED = (1 << 1),
FLAG_BOTTOM_LEFT_NOT_ROUNDED = (1 << 2),
FLAG_BOTTOM_RIGHT_NOT_ROUNDED = (1 << 3),
FLAG_TOP_NOT_ROUNDED = FLAG_TOP_LEFT_NOT_ROUNDED | FLAG_TOP_RIGHT_NOT_ROUNDED,
FLAG_BOTTOM_NOT_ROUNDED = FLAG_BOTTOM_LEFT_NOT_ROUNDED | FLAG_BOTTOM_RIGHT_NOT_ROUNDED;
/**
* Paints a selection.
* <p>
* The bounds of the painted selection (rounded) rectangle are
* {@code x + insets.left, y + insets.top, width - insets.left - insets.right, height - insets.top - insets.bottom}.
* The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
*
* @since 3
*/
public static void paintSelection( Graphics2D g, int x, int y, int width, int height,
Insets insets, float arc, int flags )
{
if( insets != null ) {
x += insets.left;
y += insets.top;
width -= insets.left + insets.right;
height -= insets.top + insets.bottom;
}
if( arc > 0 ) {
// because createRoundRectanglePath() expects a radius
float arcRadius = arc / 2;
float arcTopLeft = ((flags & FLAG_TOP_LEFT_NOT_ROUNDED) != 0) ? 0 : arcRadius;
float arcTopRight = ((flags & FLAG_TOP_RIGHT_NOT_ROUNDED) != 0) ? 0 : arcRadius;
float arcBottomLeft = ((flags & FLAG_BOTTOM_LEFT_NOT_ROUNDED) != 0) ? 0 : arcRadius;
float arcBottomRight = ((flags & FLAG_BOTTOM_RIGHT_NOT_ROUNDED) != 0) ? 0 : arcRadius;
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) -> {
paintRoundedSelectionImpl( g2d, x2, y2, width2, height2,
(float) (arcTopLeft * scaleFactor), (float) (arcTopRight * scaleFactor),
(float) (arcBottomLeft * scaleFactor), (float) (arcBottomRight * scaleFactor) );
} );
} else
paintRoundedSelectionImpl( g, x, y, width, height, arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight );
} else
g.fillRect( x, y, width, height );
}
private static void paintRoundedSelectionImpl( Graphics2D g, int x, int y, int width, int height,
float arcTopLeft, float arcTopRight, float arcBottomLeft, float arcBottomRight )
{
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.fill( FlatUIUtils.createRoundRectanglePath( x, y, width, height, arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
public static void paintGrip( Graphics g, int x, int y, int width, int height, public static void paintGrip( Graphics g, int x, int y, int width, int height,
boolean horizontal, int dotCount, int dotSize, int gap, boolean centerPrecise ) boolean horizontal, int dotCount, int dotSize, int gap, boolean centerPrecise )
{ {

View File

@@ -881,6 +881,8 @@ Tree.dropCellForeground = @dropCellForeground
Tree.dropLineColor = @dropLineColor Tree.dropLineColor = @dropLineColor
Tree.rendererFillBackground = false Tree.rendererFillBackground = false
Tree.rendererMargins = 1,2,1,2 Tree.rendererMargins = 1,2,1,2
Tree.selectionInsets = 0,0,0,0
Tree.selectionArc = 0
Tree.wideSelection = true Tree.wideSelection = true
Tree.repaintWholeRow = true Tree.repaintWholeRow = true
Tree.paintLines = false Tree.paintLines = false

View File

@@ -921,6 +921,8 @@ public class TestFlatStyleableInfo
"selectionInactiveBackground", Color.class, "selectionInactiveBackground", Color.class,
"selectionInactiveForeground", Color.class, "selectionInactiveForeground", Color.class,
"selectionBorderColor", Color.class, "selectionBorderColor", Color.class,
"selectionInsets", Insets.class,
"selectionArc", int.class,
"wideSelection", boolean.class, "wideSelection", boolean.class,
"showCellFocusIndicator", boolean.class, "showCellFocusIndicator", boolean.class,

View File

@@ -1127,6 +1127,8 @@ public class TestFlatStyling
ui.applyStyle( "selectionInactiveBackground: #fff" ); ui.applyStyle( "selectionInactiveBackground: #fff" );
ui.applyStyle( "selectionInactiveForeground: #fff" ); ui.applyStyle( "selectionInactiveForeground: #fff" );
ui.applyStyle( "selectionBorderColor: #fff" ); ui.applyStyle( "selectionBorderColor: #fff" );
ui.applyStyle( "selectionInsets: 1,2,3,4" );
ui.applyStyle( "selectionArc: 8" );
ui.applyStyle( "wideSelection: true" ); ui.applyStyle( "wideSelection: true" );
ui.applyStyle( "showCellFocusIndicator: true" ); ui.applyStyle( "showCellFocusIndicator: true" );

View File

@@ -1376,11 +1376,13 @@ Tree.repaintWholeRow true
Tree.rightChildIndent 11 Tree.rightChildIndent 11
Tree.rowHeight 0 Tree.rowHeight 0
Tree.scrollsOnExpand true Tree.scrollsOnExpand true
Tree.selectionArc 0
Tree.selectionBackground #4b6eaf HSL 219 40 49 javax.swing.plaf.ColorUIResource [UI] Tree.selectionBackground #4b6eaf HSL 219 40 49 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionBorderColor #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI] Tree.selectionBorderColor #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] Tree.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInactiveBackground #0f2a3d HSL 205 61 15 javax.swing.plaf.ColorUIResource [UI] Tree.selectionInactiveBackground #0f2a3d HSL 205 61 15 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInactiveForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] Tree.selectionInactiveForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
Tree.showCellFocusIndicator false Tree.showCellFocusIndicator false
Tree.textBackground #46494b HSL 204 3 28 javax.swing.plaf.ColorUIResource [UI] Tree.textBackground #46494b HSL 204 3 28 javax.swing.plaf.ColorUIResource [UI]
Tree.textForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] Tree.textForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]

View File

@@ -1381,11 +1381,13 @@ Tree.repaintWholeRow true
Tree.rightChildIndent 11 Tree.rightChildIndent 11
Tree.rowHeight 0 Tree.rowHeight 0
Tree.scrollsOnExpand true Tree.scrollsOnExpand true
Tree.selectionArc 0
Tree.selectionBackground #2675bf HSL 209 67 45 javax.swing.plaf.ColorUIResource [UI] Tree.selectionBackground #2675bf HSL 209 67 45 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionBorderColor #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI] Tree.selectionBorderColor #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] Tree.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInactiveBackground #d3d3d3 HSL 0 0 83 javax.swing.plaf.ColorUIResource [UI] Tree.selectionInactiveBackground #d3d3d3 HSL 0 0 83 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInactiveForeground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI] Tree.selectionInactiveForeground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
Tree.showCellFocusIndicator false Tree.showCellFocusIndicator false
Tree.textBackground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] Tree.textBackground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
Tree.textForeground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI] Tree.textForeground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI]

View File

@@ -1401,11 +1401,13 @@ Tree.repaintWholeRow true
Tree.rightChildIndent 11 Tree.rightChildIndent 11
Tree.rowHeight 0 Tree.rowHeight 0
Tree.scrollsOnExpand true Tree.scrollsOnExpand true
Tree.selectionArc 0
Tree.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI] Tree.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionBorderColor #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI] Tree.selectionBorderColor #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI] Tree.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInactiveBackground #888888 HSL 0 0 53 javax.swing.plaf.ColorUIResource [UI] Tree.selectionInactiveBackground #888888 HSL 0 0 53 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInactiveForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] Tree.selectionInactiveForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
Tree.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
Tree.showCellFocusIndicator false Tree.showCellFocusIndicator false
Tree.textBackground #fff0ff HSL 300 100 97 javax.swing.plaf.ColorUIResource [UI] Tree.textBackground #fff0ff HSL 300 100 97 javax.swing.plaf.ColorUIResource [UI]
Tree.textForeground #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI] Tree.textForeground #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI]

View File

@@ -1099,11 +1099,13 @@ Tree.repaintWholeRow
Tree.rightChildIndent Tree.rightChildIndent
Tree.rowHeight Tree.rowHeight
Tree.scrollsOnExpand Tree.scrollsOnExpand
Tree.selectionArc
Tree.selectionBackground Tree.selectionBackground
Tree.selectionBorderColor Tree.selectionBorderColor
Tree.selectionForeground Tree.selectionForeground
Tree.selectionInactiveBackground Tree.selectionInactiveBackground
Tree.selectionInactiveForeground Tree.selectionInactiveForeground
Tree.selectionInsets
Tree.showCellFocusIndicator Tree.showCellFocusIndicator
Tree.textBackground Tree.textBackground
Tree.textForeground Tree.textForeground