diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java index 638e8cce..792d5068 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java @@ -17,20 +17,32 @@ package com.formdev.flatlaf.ui; import java.awt.Color; +import java.awt.Component; import java.awt.EventQueue; +import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Insets; +import java.awt.Rectangle; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.beans.PropertyChangeListener; import java.util.Map; +import javax.swing.DefaultListCellRenderer; import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; import javax.swing.UIManager; +import javax.swing.event.ListSelectionListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicListUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.LoggingFacade; +import com.formdev.flatlaf.util.UIScale; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JList}. @@ -59,6 +71,8 @@ import com.formdev.flatlaf.util.LoggingFacade; * * @uiDefault List.selectionInactiveBackground Color * @uiDefault List.selectionInactiveForeground Color + * @uiDefault List.selectionInsets Insets + * @uiDefault List.selectionArc int * * * @@ -76,6 +90,8 @@ public class FlatListUI @Styleable protected Color selectionForeground; @Styleable protected Color selectionInactiveBackground; @Styleable protected Color selectionInactiveForeground; + /** @since 3 */ @Styleable protected Insets selectionInsets; + /** @since 3 */ @Styleable protected int selectionArc; // for FlatListCellBorder /** @since 2 */ @Styleable protected Insets cellMargins; @@ -103,6 +119,8 @@ public class FlatListUI selectionForeground = UIManager.getColor( "List.selectionForeground" ); selectionInactiveBackground = UIManager.getColor( "List.selectionInactiveBackground" ); selectionInactiveForeground = UIManager.getColor( "List.selectionInactiveForeground" ); + selectionInsets = UIManager.getInsets( "List.selectionInsets" ); + selectionArc = UIManager.getInt( "List.selectionArc" ); toggleSelectionColors(); } @@ -161,6 +179,29 @@ public class FlatListUI }; } + @Override + protected ListSelectionListener createListSelectionListener() { + ListSelectionListener superListener = super.createListSelectionListener(); + return e -> { + superListener.valueChanged( e ); + + // for united rounded selection, repaint parts of the rows that adjoin to the changed rows + if( useUnitedRoundedSelection() && + !list.isSelectionEmpty() && + (list.getMaxSelectionIndex() - list.getMinSelectionIndex()) >= 1 ) + { + int size = list.getModel().getSize(); + int firstIndex = Math.min( Math.max( e.getFirstIndex(), 0 ), size - 1 ); + int lastIndex = Math.min( Math.max( e.getLastIndex(), 0 ), size - 1 ); + Rectangle r = getCellBounds( list, firstIndex, lastIndex ); + if( r != null ) { + int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) ); + list.repaint( r.x, r.y - arc, r.width, r.height + (arc * 2) ); + } + } + }; + } + /** @since 2 */ protected void installStyle() { try { @@ -234,4 +275,115 @@ public class FlatListUI list.setSelectionForeground( selectionInactiveForeground ); } } + + @SuppressWarnings( "rawtypes" ) + @Override + protected void paintCell( Graphics g, int row, Rectangle rowBounds, ListCellRenderer cellRenderer, + ListModel dataModel, ListSelectionModel selModel, int leadIndex ) + { + boolean isSelected = selModel.isSelectedIndex( row ); + + // get renderer component + @SuppressWarnings( "unchecked" ) + Component rendererComponent = cellRenderer.getListCellRendererComponent( list, + dataModel.getElementAt( row ), row, isSelected, list.hasFocus() && (row == leadIndex) ); + + // + boolean isFileList = Boolean.TRUE.equals( list.getClientProperty( "List.isFileList" ) ); + int cx, cw; + if( isFileList ) { + // see BasicListUI.paintCell() + cw = Math.min( rowBounds.width, rendererComponent.getPreferredSize().width + 4 ); + cx = list.getComponentOrientation().isLeftToRight() + ? rowBounds.x + : rowBounds.x + (rowBounds.width - cw); + } else { + cx = rowBounds.x; + cw = rowBounds.width; + } + + // rounded selection or selection insets + if( isSelected && + !isFileList && + rendererComponent instanceof DefaultListCellRenderer && + (selectionArc > 0 || + (selectionInsets != null && + (selectionInsets.top != 0 || selectionInsets.left != 0 || selectionInsets.bottom != 0 || selectionInsets.right != 0))) ) + { + // Because selection painting is done in the cell renderer, it would be + // necessary to require a FlatLaf specific renderer to implement rounded selection. + // Using a LaF specific renderer was avoided because often a custom renderer is + // already used in applications. Then either the rounded selection is not used, + // or the application has to be changed to extend a FlatLaf renderer. + // + // To solve this, a graphics proxy is used that paints rounded selection + // if row is selected and the renderer wants to fill the background. + class RoundedSelectionGraphics extends Graphics2DProxy { + RoundedSelectionGraphics( Graphics delegate ) { + super( (Graphics2D) delegate ); + } + + @Override + public Graphics create() { + return new RoundedSelectionGraphics( super.create() ); + } + + @Override + public Graphics create( int x, int y, int width, int height ) { + return new RoundedSelectionGraphics( super.create( x, y, width, height ) ); + } + + @Override + public void fillRect( int x, int y, int width, int height ) { + if( x == 0 && y == 0 && width == rowBounds.width && height == rowBounds.height && + this.getColor() == rendererComponent.getBackground() ) + { + paintCellSelection( this, row, x, y, width, height ); + } else + super.fillRect( x, y, width, height ); + } + } + g = new RoundedSelectionGraphics( g ); + } + + // paint renderer + rendererPane.paintComponent( g, rendererComponent, list, cx, rowBounds.y, cw, rowBounds.height, true ); + } + + /** @since 3 */ + protected void paintCellSelection( Graphics g, int row, int x, int y, int width, int height ) { + float arcTop, arcBottom; + arcTop = arcBottom = UIScale.scale( selectionArc / 2f ); + + if( useUnitedRoundedSelection() ) { + if( row > 0 && list.isSelectedIndex( row - 1 ) ) + arcTop = 0; + if( row < list.getModel().getSize() - 1 && list.isSelectedIndex( row + 1 ) ) + arcBottom = 0; + } + + FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height, + UIScale.scale( selectionInsets ), arcTop, arcTop, arcBottom, arcBottom, 0 ); + } + + private boolean useUnitedRoundedSelection() { + return selectionArc > 0 && + (selectionInsets == null || (selectionInsets.top == 0 && selectionInsets.bottom == 0)); + } + + /** + * Paints a cell selection at the given coordinates. + * The selection color must be set on the graphics context. + *
+ * This method is intended for use in custom cell renderers.
+ *
+ * @since 3
+ */
+ public static void paintCellSelection( JList> list, Graphics g, int row, int x, int y, int width, int height ) {
+ if( !(list.getUI() instanceof FlatListUI) )
+ return;
+
+ FlatListUI ui = (FlatListUI) list.getUI();
+ ui.paintCellSelection( g, row, x, y, width, height );
+ }
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java
index 30f6ebd8..46eb4c86 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java
@@ -318,12 +318,12 @@ public class FlatTreeUI
// same is done in BasicTreeUI.Handler.valueChanged()
tree.repaint();
} else {
- int arcHeight = (int) Math.ceil( UIScale.scale( (float) selectionArc ) );
+ int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) );
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) );
+ tree.repaint( r.x, r.y - arc, r.width, r.height + (arc * 2) );
}
}
}
diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
index fa8590a3..613492af 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
@@ -390,6 +390,8 @@ InternalFrameTitlePane.border = 0,8,0,0
List.border = 0,0,0,0
List.cellMargins = 1,6,1,6
+List.selectionInsets = 0,0,0,0
+List.selectionArc = 0
List.cellFocusColor = @cellFocusColor
List.cellNoFocusBorder = com.formdev.flatlaf.ui.FlatListCellBorder$Default
List.focusCellHighlightBorder = com.formdev.flatlaf.ui.FlatListCellBorder$Focused
@@ -904,7 +906,6 @@ Tree.icon.closedColor = @icon
Tree.icon.openColor = @icon
-
#---- Styles ------------------------------------------------------------------
#---- inTextField ----
diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java
index 2fa1990e..5f71d2e8 100644
--- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java
+++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java
@@ -254,6 +254,8 @@ public class TestFlatStyleableInfo
"selectionForeground", Color.class,
"selectionInactiveBackground", Color.class,
"selectionInactiveForeground", Color.class,
+ "selectionInsets", Insets.class,
+ "selectionArc", int.class,
// FlatListCellBorder
"cellMargins", Insets.class,
diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java
index 06c96497..fbf447a0 100644
--- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java
+++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java
@@ -401,6 +401,8 @@ public class TestFlatStyling
ui.applyStyle( "selectionForeground: #fff" );
ui.applyStyle( "selectionInactiveBackground: #fff" );
ui.applyStyle( "selectionInactiveForeground: #fff" );
+ ui.applyStyle( "selectionInsets: 1,2,3,4" );
+ ui.applyStyle( "selectionArc: 8" );
// FlatListCellBorder
ui.applyStyle( "cellMargins: 1,2,3,4" );
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java
index f1dd5bd1..99e970ba 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java
@@ -19,6 +19,7 @@ package com.formdev.flatlaf.demo.intellijthemes;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.EventQueue;
+import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.WindowAdapter;
@@ -40,6 +41,7 @@ import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import javax.swing.*;
+import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.event.*;
import com.formdev.flatlaf.FlatDarculaLaf;
@@ -52,6 +54,7 @@ import com.formdev.flatlaf.IntelliJTheme;
import com.formdev.flatlaf.demo.DemoPrefs;
import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
import com.formdev.flatlaf.extras.FlatSVGIcon;
+import com.formdev.flatlaf.ui.FlatListUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils;
import net.miginfocom.swing.*;
@@ -89,10 +92,18 @@ public class IJThemesPanel
// create renderer
themesList.setCellRenderer( new DefaultListCellRenderer() {
+ private int index;
+ private boolean isSelected;
+ private int titleHeight;
+
@Override
public Component getListCellRendererComponent( JList> list, Object value,
int index, boolean isSelected, boolean cellHasFocus )
{
+ this.index = index;
+ this.isSelected = isSelected;
+ this.titleHeight = 0;
+
String title = categories.get( index );
String name = ((IJThemeInfo)value).name;
int sep = name.indexOf( '/' );
@@ -101,11 +112,33 @@ public class IJThemesPanel
JComponent c = (JComponent) super.getListCellRendererComponent( list, name, index, isSelected, cellHasFocus );
c.setToolTipText( buildToolTip( (IJThemeInfo) value ) );
- if( title != null )
- c.setBorder( new CompoundBorder( new ListCellTitledBorder( themesList, title ), c.getBorder() ) );
+ if( title != null ) {
+ Border titledBorder = new ListCellTitledBorder( themesList, title );
+ c.setBorder( new CompoundBorder( titledBorder, c.getBorder() ) );
+ titleHeight = titledBorder.getBorderInsets( c ).top;
+ }
return c;
}
+ @Override
+ public boolean isOpaque() {
+ return !isSelectedTitle();
+ }
+
+ @Override
+ protected void paintComponent( Graphics g ) {
+ if( isSelectedTitle() ) {
+ g.setColor( getBackground() );
+ FlatListUI.paintCellSelection( themesList, g, index, 0, titleHeight, getWidth(), getHeight() - titleHeight );
+ }
+
+ super.paintComponent( g );
+ }
+
+ private boolean isSelectedTitle() {
+ return titleHeight > 0 && isSelected && UIManager.getLookAndFeel() instanceof FlatLaf;
+ }
+
private String buildToolTip( IJThemeInfo ti ) {
if( ti.themeFile != null )
return ti.themeFile.getPath();
diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
index b3a6d69a..d8d7f678 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
@@ -545,10 +545,12 @@ List.focusSelectedCellHighlightBorder [lazy] 1,6,1,6 false com.formdev.flatl
List.font [active] $defaultFont [UI]
List.foreground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]
List.noFocusBorder 1,1,1,1 false javax.swing.plaf.BorderUIResource$EmptyBorderUIResource [UI]
+List.selectionArc 0
List.selectionBackground #4b6eaf HSL 219 40 49 javax.swing.plaf.ColorUIResource [UI]
List.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]
List.selectionInactiveBackground #0f2a3d HSL 205 61 15 javax.swing.plaf.ColorUIResource [UI]
List.selectionInactiveForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]
+List.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
List.showCellFocusIndicator false
List.timeFactor 1000
ListUI com.formdev.flatlaf.ui.FlatListUI
diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
index d2203245..ecacc9b3 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
@@ -550,10 +550,12 @@ List.focusSelectedCellHighlightBorder [lazy] 1,6,1,6 false com.formdev.flatl
List.font [active] $defaultFont [UI]
List.foreground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI]
List.noFocusBorder 1,1,1,1 false javax.swing.plaf.BorderUIResource$EmptyBorderUIResource [UI]
+List.selectionArc 0
List.selectionBackground #2675bf HSL 209 67 45 javax.swing.plaf.ColorUIResource [UI]
List.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
List.selectionInactiveBackground #d3d3d3 HSL 0 0 83 javax.swing.plaf.ColorUIResource [UI]
List.selectionInactiveForeground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI]
+List.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
List.showCellFocusIndicator false
List.timeFactor 1000
ListUI com.formdev.flatlaf.ui.FlatListUI
diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
index 0c7bb7c9..7f4f354a 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
@@ -552,10 +552,12 @@ List.focusSelectedCellHighlightBorder [lazy] 1,6,1,6 false com.formdev.flatl
List.font [active] $defaultFont [UI]
List.foreground #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI]
List.noFocusBorder 1,1,1,1 false javax.swing.plaf.BorderUIResource$EmptyBorderUIResource [UI]
+List.selectionArc 0
List.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI]
List.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI]
List.selectionInactiveBackground #888888 HSL 0 0 53 javax.swing.plaf.ColorUIResource [UI]
List.selectionInactiveForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
+List.selectionInsets 0,0,0,0 javax.swing.plaf.InsetsUIResource [UI]
List.showCellFocusIndicator false
List.timeFactor 1000
ListUI com.formdev.flatlaf.ui.FlatListUI
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java
index 54aae794..731ce08c 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java
@@ -79,6 +79,8 @@ public class FlatComponents2Test
private final TestListModel listModel;
private final TestTreeModel treeModel;
private final TestTableModel tableModel;
+ @SuppressWarnings( "rawtypes" )
+ private final JList[] allLists;
private final JTree[] allTrees;
private final List