Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree: rounded selection #546

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -353,15 +353,11 @@ protected void paintBackground( Graphics g ) {

/** @since 3 */
protected void paintSelection( Graphics g, Color selectionBackground, Insets selectionInsets, int selectionArc ) {
Rectangle r = FlatUIUtils.subtractInsets( new Rectangle( menuItem.getSize() ), scale( selectionInsets ) );
float arc = scale( selectionArc / 2f );

g.setColor( deriveBackground( selectionBackground ) );
if( selectionArc > 0 ) {
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
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 );
FlatUIUtils.paintSelection( (Graphics2D) g, 0, 0, menuItem.getWidth(), menuItem.getHeight(),
scale( selectionInsets ), arc, arc, arc, arc, 0 );
}

/** @since 3 */
Expand Down
87 changes: 80 additions & 7 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java
Expand Up @@ -17,10 +17,10 @@
package com.formdev.flatlaf.ui;

import static com.formdev.flatlaf.FlatClientProperties.*;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
Expand All @@ -36,6 +36,7 @@
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.JTree.DropLocation;
import javax.swing.event.TreeSelectionListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultTreeCellRenderer;
Expand Down Expand Up @@ -96,6 +97,8 @@
* @uiDefault Tree.selectionForeground Color
* @uiDefault Tree.selectionInactiveBackground Color
* @uiDefault Tree.selectionInactiveForeground Color
* @uiDefault Tree.selectionInsets Insets
* @uiDefault Tree.selectionArc int
* @uiDefault Tree.wideSelection boolean
* @uiDefault Tree.showCellFocusIndicator boolean
*
Expand Down Expand Up @@ -132,6 +135,8 @@ public class FlatTreeUI
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
@Styleable protected Color selectionBorderColor;
/** @since 3 */ @Styleable protected Insets selectionInsets;
/** @since 3 */ @Styleable protected int selectionArc;
@Styleable protected boolean wideSelection;
@Styleable protected boolean showCellFocusIndicator;

Expand Down Expand Up @@ -175,6 +180,8 @@ protected void installDefaults() {
selectionInactiveBackground = UIManager.getColor( "Tree.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Tree.selectionInactiveForeground" );
selectionBorderColor = UIManager.getColor( "Tree.selectionBorderColor" );
selectionInsets = UIManager.getInsets( "Tree.selectionInsets" );
selectionArc = UIManager.getInt( "Tree.selectionArc" );
wideSelection = UIManager.getBoolean( "Tree.wideSelection" );
showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" );

Expand Down Expand Up @@ -295,6 +302,34 @@ private void repaintWideDropLocation(JTree.DropLocation loc) {
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
public Rectangle getPathBounds( JTree tree, TreePath path ) {
Rectangle bounds = super.getPathBounds( tree, path );
Expand Down Expand Up @@ -341,7 +376,7 @@ public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
}

/**
* Same as super.paintRow(), but supports wide selection and uses
* Similar to super.paintRow(), but supports wide selection and uses
* inactive selection background/foreground if tree is not focused.
*/
@Override
Expand Down Expand Up @@ -422,7 +457,7 @@ protected void paintRow( Graphics g, Rectangle clipBounds, Insets insets, Rectan
paintWideSelection( g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf );
} else {
// non-wide selection
paintCellBackground( g, rendererComponent, bounds );
paintCellBackground( g, rendererComponent, bounds, row, true );
}

// this is actually not necessary because renderer should always set color
Expand All @@ -436,7 +471,7 @@ protected void paintRow( Graphics g, Rectangle clipBounds, Insets insets, Rectan
if( bg != null && !bg.equals( defaultCellNonSelectionBackground ) ) {
Color oldColor = g.getColor();
g.setColor( bg );
paintCellBackground( g, rendererComponent, bounds );
paintCellBackground( g, rendererComponent, bounds, row, false );
g.setColor( oldColor );
}
}
Expand Down Expand Up @@ -491,7 +526,18 @@ private Color setRendererBorderSelectionColor( Component rendererComponent, Colo
private void paintWideSelection( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds,
TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf )
{
g.fillRect( 0, bounds.y, tree.getWidth(), bounds.height );
float arcTop, arcBottom;
arcTop = arcBottom = UIScale.scale( selectionArc / 2f );

if( useUnitedRoundedSelection() ) {
if( row > 0 && tree.isRowSelected( row - 1 ) )
arcTop = 0;
if( row < tree.getRowCount() - 1 && tree.isRowSelected( row + 1 ) )
arcBottom = 0;
}

FlatUIUtils.paintSelection( (Graphics2D) g, 0, bounds.y, tree.getWidth(), bounds.height,
UIScale.scale( selectionInsets ), arcTop, arcTop, arcBottom, arcBottom, 0 );

// paint expand/collapse icon
// (was already painted before, but painted over with wide selection)
Expand All @@ -501,7 +547,9 @@ private void paintWideSelection( Graphics g, Rectangle clipBounds, Insets insets
}
}

private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds ) {
private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds,
int row, boolean paintSelection )
{
int xOffset = 0;
int imageOffset = 0;

Expand All @@ -514,7 +562,32 @@ private void paintCellBackground( Graphics g, Component rendererComponent, Recta
xOffset = label.getComponentOrientation().isLeftToRight() ? imageOffset : 0;
}

g.fillRect( bounds.x + xOffset, bounds.y, bounds.width - imageOffset, bounds.height );
if( paintSelection ) {
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );

if( useUnitedRoundedSelection() ) {
if( row > 0 && tree.isRowSelected( row - 1 ) ) {
Rectangle r = getPathBounds( tree, tree.getPathForRow( row - 1 ) );
arcTopLeft = Math.min( arcTopLeft, r.x - bounds.x );
arcTopRight = Math.min( arcTopRight, (bounds.x + bounds.width) - (r.x + r.width) );
}
if( row < tree.getRowCount() - 1 && tree.isRowSelected( row + 1 ) ) {
Rectangle r = getPathBounds( tree, tree.getPathForRow( row + 1 ) );
arcBottomLeft = Math.min( arcBottomLeft, r.x - bounds.x );
arcBottomRight = Math.min( arcBottomRight, (bounds.x + bounds.width) - (r.x + r.width) );
}
}

FlatUIUtils.paintSelection( (Graphics2D) g, bounds.x + xOffset, bounds.y, bounds.width - imageOffset, bounds.height,
UIScale.scale( selectionInsets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 );
} else
g.fillRect( bounds.x + xOffset, bounds.y, bounds.width - imageOffset, bounds.height );
}

private boolean useUnitedRoundedSelection() {
return selectionArc > 0 &&
(selectionInsets == null || (selectionInsets.top == 0 && selectionInsets.bottom == 0));
}

/**
Expand Down
52 changes: 48 additions & 4 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java
Expand Up @@ -393,9 +393,9 @@ public static Color deriveColor( Color color, Color baseColor ) {
}

/**
* Fills the background of a component with a round rectangle.
* Fills the background of a component with a rounded rectangle.
* <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)}.
* The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
*
Expand Down Expand Up @@ -426,7 +426,7 @@ public static void paintComponentBackground( Graphics2D g, int x, int y, int wid
* <p>
*
* <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)]}.
* The focus border and the border may paint over the background.
* <p>
Expand Down Expand Up @@ -624,6 +624,50 @@ static void paintFilledRectangle( Graphics g, Color color, float x, float y, flo
}
}

/**
* 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 radius 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 arcTopLeft, float arcTopRight, float arcBottomLeft, float arcBottomRight, int flags )
{
if( insets != null ) {
x += insets.left;
y += insets.top;
width -= insets.left + insets.right;
height -= insets.top + insets.bottom;
}

if( arcTopLeft > 0 || arcTopRight > 0 || arcBottomLeft > 0 || arcBottomRight > 0 ) {
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,
boolean horizontal, int dotCount, int dotSize, int gap, boolean centerPrecise )
{
Expand Down Expand Up @@ -704,7 +748,7 @@ public static Path2D createRectangle( float x, float y, float width, float heigh
}

/**
* Creates a not-filled rounded rectangle shape and allows specifying the line width and the radius or each corner.
* Creates a not-filled rounded rectangle shape and allows specifying the line width and the radius of each corner.
*/
public static Path2D createRoundRectangle( float x, float y, float width, float height,
float lineWidth, float arcTopLeft, float arcTopRight, float arcBottomLeft, float arcBottomRight )
Expand Down
Expand Up @@ -881,6 +881,8 @@ Tree.dropCellForeground = @dropCellForeground
Tree.dropLineColor = @dropLineColor
Tree.rendererFillBackground = false
Tree.rendererMargins = 1,2,1,2
Tree.selectionInsets = 0,0,0,0
Tree.selectionArc = 0
Tree.wideSelection = true
Tree.repaintWholeRow = true
Tree.paintLines = false
Expand Down
Expand Up @@ -921,6 +921,8 @@ void tree() {
"selectionInactiveBackground", Color.class,
"selectionInactiveForeground", Color.class,
"selectionBorderColor", Color.class,
"selectionInsets", Insets.class,
"selectionArc", int.class,
"wideSelection", boolean.class,
"showCellFocusIndicator", boolean.class,

Expand Down
Expand Up @@ -1127,6 +1127,8 @@ void tree() {
ui.applyStyle( "selectionInactiveBackground: #fff" );
ui.applyStyle( "selectionInactiveForeground: #fff" );
ui.applyStyle( "selectionBorderColor: #fff" );
ui.applyStyle( "selectionInsets: 1,2,3,4" );
ui.applyStyle( "selectionArc: 8" );
ui.applyStyle( "wideSelection: true" );
ui.applyStyle( "showCellFocusIndicator: true" );

Expand Down
2 changes: 2 additions & 0 deletions flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
Expand Up @@ -1376,11 +1376,13 @@ Tree.repaintWholeRow true
Tree.rightChildIndent 11
Tree.rowHeight 0
Tree.scrollsOnExpand true
Tree.selectionArc 0
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.selectionForeground #bbbbbb HSL 0 0 73 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.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
Tree.showCellFocusIndicator false
Tree.textBackground #46494b HSL 204 3 28 javax.swing.plaf.ColorUIResource [UI]
Tree.textForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]
Expand Down
2 changes: 2 additions & 0 deletions flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
Expand Up @@ -1381,11 +1381,13 @@ Tree.repaintWholeRow true
Tree.rightChildIndent 11
Tree.rowHeight 0
Tree.scrollsOnExpand true
Tree.selectionArc 0
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.selectionForeground #ffffff HSL 0 0 100 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.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
Tree.showCellFocusIndicator false
Tree.textBackground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
Tree.textForeground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI]
Expand Down
2 changes: 2 additions & 0 deletions flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
Expand Up @@ -1401,11 +1401,13 @@ Tree.repaintWholeRow true
Tree.rightChildIndent 11
Tree.rowHeight 0
Tree.scrollsOnExpand true
Tree.selectionArc 0
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.selectionForeground #ffff00 HSL 60 100 50 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.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
Tree.showCellFocusIndicator false
Tree.textBackground #fff0ff HSL 300 100 97 javax.swing.plaf.ColorUIResource [UI]
Tree.textForeground #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI]
Expand Down
Expand Up @@ -1099,11 +1099,13 @@ Tree.repaintWholeRow
Tree.rightChildIndent
Tree.rowHeight
Tree.scrollsOnExpand
Tree.selectionArc
Tree.selectionBackground
Tree.selectionBorderColor
Tree.selectionForeground
Tree.selectionInactiveBackground
Tree.selectionInactiveForeground
Tree.selectionInsets
Tree.showCellFocusIndicator
Tree.textBackground
Tree.textForeground
Expand Down