Skip to content

Commit

Permalink
Merge PR #546: Tree: rounded selection
Browse files Browse the repository at this point in the history
  • Loading branch information
DevCharly committed Oct 30, 2022
2 parents 873a7e8 + 3802c64 commit 22bb802
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ FlatLaf Change Log
#### New features and improvements

- Menus: Support rounded selection. (PR #536)
- Tree: Support rounded selection. (PR #546)

#### Fixed bugs

Expand Down
Expand Up @@ -369,15 +369,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 @@ -347,7 +382,7 @@ public Object getStyleableValue( JComponent c, String key ) {
}

/**
* 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 @@ -428,7 +463,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 @@ -442,7 +477,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 @@ -497,7 +532,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 @@ -507,7 +553,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 @@ -520,7 +568,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 @@ -443,9 +443,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 @@ -476,7 +476,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 @@ -674,6 +674,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 @@ -765,7 +809,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 @@ -889,6 +889,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 @@ -939,6 +939,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 @@ -1145,6 +1145,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 @@ -1386,11 +1386,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 @@ -1391,11 +1391,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 @@ -1437,11 +1437,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 @@ -1137,11 +1137,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

0 comments on commit 22bb802

Please sign in to comment.