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 638e8cce2..b87a4cefa 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,33 @@ 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.Point; +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 +72,8 @@ * * @uiDefault List.selectionInactiveBackground Color * @uiDefault List.selectionInactiveForeground Color + * @uiDefault List.selectionInsets Insets + * @uiDefault List.selectionArc int * * * @@ -76,6 +91,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 +120,8 @@ protected void installDefaults() { 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 +180,29 @@ protected PropertyChangeListener createPropertyChangeListener() { }; } + @Override + protected ListSelectionListener createListSelectionListener() { + ListSelectionListener superListener = super.createListSelectionListener(); + return e -> { + superListener.valueChanged( e ); + + // for united rounded selection, repaint parts of the rows/columns that adjoin to the changed rows/columns + if( useUnitedRoundedSelection( true, true ) && + !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 - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) ); + } + } + }; + } + /** @since 2 */ protected void installStyle() { try { @@ -234,4 +276,163 @@ private void toggleSelectionColors() { 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 && // rounded selection is not supported for file list + 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 { + // used to avoid endless loop in case that paintCellSelection() invokes + // g.fillRect() with full bounds (selectionInsets is 0,0,0,0) + private boolean inPaintSelection; + + 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( !inPaintSelection && + x == 0 && y == 0 && width == rowBounds.width && height == rowBounds.height && + this.getColor() == rendererComponent.getBackground() ) + { + inPaintSelection = true; + paintCellSelection( this, row, x, y, width, height ); + inPaintSelection = false; + } 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 arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; + arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f ); + + if( list.getLayoutOrientation() == JList.VERTICAL ) { + // layout orientation: VERTICAL + if( useUnitedRoundedSelection( true, false ) ) { + if( row > 0 && list.isSelectedIndex( row - 1 ) ) + arcTopLeft = arcTopRight = 0; + if( row < list.getModel().getSize() - 1 && list.isSelectedIndex( row + 1 ) ) + arcBottomLeft = arcBottomRight = 0; + } + } else { + // layout orientation: VERTICAL_WRAP or HORIZONTAL_WRAP + Rectangle r = null; + if( useUnitedRoundedSelection( true, false ) ) { + // vertical: check whether cells above or below are selected + r = getCellBounds( list, row, row ); + + int topIndex = locationToIndex( list, new Point( r.x, r.y - 1 ) ); + int bottomIndex = locationToIndex( list, new Point( r.x, r.y + r.height ) ); + + if( topIndex >= 0 && topIndex != row && list.isSelectedIndex( topIndex ) ) + arcTopLeft = arcTopRight = 0; + if( bottomIndex >= 0 && bottomIndex != row && list.isSelectedIndex( bottomIndex ) ) + arcBottomLeft = arcBottomRight = 0; + } + + if( useUnitedRoundedSelection( false, true ) ) { + // horizontal: check whether cells left or right are selected + if( r == null ) + r = getCellBounds( list, row, row ); + + int leftIndex = locationToIndex( list, new Point( r.x - 1, r.y ) ); + int rightIndex = locationToIndex( list, new Point( r.x + r.width, r.y ) ); + + // special handling for the case that last column contains less cells than the other columns + boolean ltr = list.getComponentOrientation().isLeftToRight(); + if( !ltr && leftIndex >= 0 && leftIndex != row && leftIndex == locationToIndex( list, new Point( r.x - 1, r.y - 1 ) ) ) + leftIndex = -1; + if( ltr && rightIndex >= 0 && rightIndex != row && rightIndex == locationToIndex( list, new Point( r.x + r.width, r.y - 1 ) ) ) + rightIndex = -1; + + if( leftIndex >= 0 && leftIndex != row && list.isSelectedIndex( leftIndex ) ) + arcTopLeft = arcBottomLeft = 0; + if( rightIndex >= 0 && rightIndex != row && list.isSelectedIndex( rightIndex ) ) + arcTopRight = arcBottomRight = 0; + } + } + + FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height, + UIScale.scale( selectionInsets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 ); + } + + private boolean useUnitedRoundedSelection( boolean vertical, boolean horizontal ) { + return selectionArc > 0 && + (selectionInsets == null || + (vertical && selectionInsets.top == 0 && selectionInsets.bottom == 0) || + (horizontal && selectionInsets.left == 0 && selectionInsets.right == 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 30f6ebd8c..46eb4c866 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 @@ protected TreeSelectionListener createTreeSelectionListener() { // 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 fa8590a37..613492af2 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 2fa1990ee..5f71d2e83 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 @@ void list() { "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 06c964973..fbf447a0d 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 @@ void list() { 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 f1dd5bd19..99e970ba2 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 @@ 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.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.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 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 Component getListCellRendererComponent( JList list, Object value, 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 b3a6d69a8..d8d7f678f 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 d2203245b..ecacc9b3b 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 0c7bb7c92..7f4f354a4 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 54aae7944..a9183c70a 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 @@ -21,6 +21,7 @@ import java.awt.ComponentOrientation; import java.awt.Dimension; import java.awt.EventQueue; +import java.awt.Graphics; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; @@ -44,6 +45,8 @@ import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.icons.FlatMenuArrowIcon; +import com.formdev.flatlaf.ui.FlatEmptyBorder; +import com.formdev.flatlaf.ui.FlatListUI; import com.formdev.flatlaf.util.UIScale; import com.jidesoft.swing.*; import com.jidesoft.swing.CheckBoxTreeCellRenderer; @@ -79,6 +82,8 @@ public static void main( String[] args ) { 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 allTables = new ArrayList<>(); private final List allTablesInclRowHeader = new ArrayList<>(); @@ -87,6 +92,8 @@ public static void main( String[] args ) { FlatComponents2Test() { initComponents(); + allLists = new JList[] { list1, list2 }; + treeWideSelectionCheckBox.setSelected( UIManager.getBoolean( "Tree.wideSelection" ) ); allTrees = new JTree[] { tree1, tree2, xTree1, checkBoxTree1 }; @@ -336,6 +343,61 @@ private void focusCellEditorChanged() { table.setSurrendersFocusOnKeystroke( focusCellEditorCheckBox.isSelected() ); } + @SuppressWarnings( "unchecked" ) + private void listRendererChanged() { + Object sel = listRendererComboBox.getSelectedItem(); + if( !(sel instanceof String) ) + return; + + switch( (String) sel ) { + case "default": + for( JList list : allLists ) + list.setCellRenderer( new DefaultListCellRenderer() ); + break; + + case "defaultSubclass": + for( JList list : allLists ) + list.setCellRenderer( new TestDefaultListCellRenderer() ); + break; + + case "label": + for( JList list : allLists ) + list.setCellRenderer( new TestLabelListCellRenderer() ); + break; + + case "labelRounded": + for( JList list : allLists ) + list.setCellRenderer( new TestLabelRoundedListCellRenderer() ); + break; + } + + String style = sel.equals( "labelRounded" ) + ? "selectionArc: 6; selectionInsets: 0,1,0,1" + : null; + for( JList list : allLists ) + list.putClientProperty( FlatClientProperties.STYLE, style ); + } + + private void listLayoutOrientationChanged() { + int layoutOrientation = JList.VERTICAL; + Object sel = listLayoutOrientationField.getSelectedItem(); + if( sel instanceof String ) { + switch( (String) sel ) { + case "vertical": layoutOrientation = JList.VERTICAL; break; + case "vertical wrap": layoutOrientation = JList.VERTICAL_WRAP; break; + case "horzontal wrap": layoutOrientation = JList.HORIZONTAL_WRAP; break; + } + } + for( JList list : allLists ) + list.setLayoutOrientation( layoutOrientation ); + } + + private void listVisibleRowCountChanged() { + int visibleRowCount = (Integer) listVisibleRowCountSpinner.getValue(); + for( JList list : allLists ) + list.setVisibleRowCount( visibleRowCount ); + } + private void treeRendererChanged() { Object sel = treeRendererComboBox.getSelectedItem(); if( !(sel instanceof String) ) @@ -477,6 +539,13 @@ private void initComponents() { xTreeTable1 = new JXTreeTable(); JPanel panel5 = new JPanel(); dndCheckBox = new JCheckBox(); + JPanel panel6 = new JPanel(); + JLabel listRendererLabel = new JLabel(); + listRendererComboBox = new JComboBox<>(); + JLabel listLayoutOrientationLabel = new JLabel(); + listLayoutOrientationField = new JComboBox<>(); + JLabel listVisibleRowCountLabel = new JLabel(); + listVisibleRowCountSpinner = new JSpinner(); JPanel treeOptionsPanel = new JPanel(); JLabel treeRendererLabel = new JLabel(); treeRendererComboBox = new JComboBox<>(); @@ -743,18 +812,68 @@ public void mouseClicked(MouseEvent e) { panel5.setLayout(new MigLayout( "hidemode 3", // columns - "[fill]" + "[fill]", // rows "[]")); //---- dndCheckBox ---- - dndCheckBox.setText("enable drag and drop"); + dndCheckBox.setText("drag and drop"); dndCheckBox.setMnemonic('D'); dndCheckBox.addActionListener(e -> dndChanged()); panel5.add(dndCheckBox, "cell 0 0"); } - add(panel5, "cell 1 4"); + add(panel5, "cell 0 4 4 1"); + + //======== panel6 ======== + { + panel6.setBorder(new TitledBorder("JList Control")); + panel6.setLayout(new MigLayout( + "hidemode 3", + // columns + "[fill]" + + "[fill]", + // rows + "[]" + + "[]" + + "[]")); + + //---- listRendererLabel ---- + listRendererLabel.setText("Renderer:"); + panel6.add(listRendererLabel, "cell 0 0"); + + //---- listRendererComboBox ---- + listRendererComboBox.setModel(new DefaultComboBoxModel<>(new String[] { + "default", + "defaultSubclass", + "label", + "labelRounded" + })); + listRendererComboBox.addActionListener(e -> listRendererChanged()); + panel6.add(listRendererComboBox, "cell 1 0"); + + //---- listLayoutOrientationLabel ---- + listLayoutOrientationLabel.setText("Orientation:"); + panel6.add(listLayoutOrientationLabel, "cell 0 1"); + + //---- listLayoutOrientationField ---- + listLayoutOrientationField.setModel(new DefaultComboBoxModel<>(new String[] { + "vertical", + "vertical wrap", + "horzontal wrap" + })); + listLayoutOrientationField.addActionListener(e -> listLayoutOrientationChanged()); + panel6.add(listLayoutOrientationField, "cell 1 1"); + + //---- listVisibleRowCountLabel ---- + listVisibleRowCountLabel.setText("Visible row count:"); + panel6.add(listVisibleRowCountLabel, "cell 0 2"); + + //---- listVisibleRowCountSpinner ---- + listVisibleRowCountSpinner.setModel(new SpinnerNumberModel(8, 0, null, 1)); + listVisibleRowCountSpinner.addChangeListener(e -> listVisibleRowCountChanged()); + panel6.add(listVisibleRowCountSpinner, "cell 1 2"); + } + add(panel6, "cell 0 4 4 1"); //======== treeOptionsPanel ======== { @@ -801,7 +920,7 @@ public void mouseClicked(MouseEvent e) { treeEditableCheckBox.addActionListener(e -> treeEditableChanged()); treeOptionsPanel.add(treeEditableCheckBox, "cell 0 3"); } - add(treeOptionsPanel, "cell 2 4"); + add(treeOptionsPanel, "cell 0 4 4 1"); //======== tableOptionsPanel ======== { @@ -916,6 +1035,9 @@ public void mouseClicked(MouseEvent e) { private JScrollPane xTreeTable1ScrollPane; private JXTreeTable xTreeTable1; private JCheckBox dndCheckBox; + private JComboBox listRendererComboBox; + private JComboBox listLayoutOrientationField; + private JSpinner listVisibleRowCountSpinner; private JComboBox treeRendererComboBox; private JCheckBox treeWideSelectionCheckBox; private JCheckBox treePaintSelectionCheckBox; @@ -1270,6 +1392,89 @@ public void tableChanged( TableModelEvent e ) { } } + //---- class TestDefaultListCellRenderer ---------------------------------- + + private static class TestDefaultListCellRenderer + extends DefaultListCellRenderer + { + @Override + public Component getListCellRendererComponent( JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus ) + { + super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus ); + + Color nonSelectionBg = null; + Color nonSelectionFg = null; + switch( String.valueOf( value ) ) { + case "item 2": nonSelectionFg = Color.blue; break; + case "item 4": nonSelectionFg = Color.red; break; + case "item 3": nonSelectionBg = Color.yellow; break; + case "item 5": nonSelectionBg = Color.magenta; break; + } + setBackground( isSelected ? Color.green : (nonSelectionBg != null ? nonSelectionBg : list.getBackground()) ); + setForeground( isSelected ? Color.blue : (nonSelectionFg != null ? nonSelectionFg : list.getForeground()) ); + + return this; + } + } + + //---- class TestLabelListCellRenderer ------------------------------------ + + private static class TestLabelListCellRenderer + extends JLabel + implements ListCellRenderer + { + @Override + public Component getListCellRendererComponent( JList list, + String value, int index, boolean isSelected, boolean cellHasFocus ) + { + setText( String.valueOf( value ) ); + setBackground( isSelected ? Color.green : list.getBackground() ); + setForeground( isSelected ? Color.blue : list.getForeground() ); + setOpaque( true ); + return this; + } + } + + //---- class TestLabelRoundedListCellRenderer ----------------------------- + + private static class TestLabelRoundedListCellRenderer + extends JLabel + implements ListCellRenderer + { + private JList list; + private int index; + private boolean isSelected; + + TestLabelRoundedListCellRenderer() { + setBorder( new FlatEmptyBorder( 1, 6, 1, 6 ) ); + } + + @Override + public Component getListCellRendererComponent( JList list, + String value, int index, boolean isSelected, boolean cellHasFocus ) + { + this.list = list; + this.index = index; + this.isSelected = isSelected; + + setText( String.valueOf( value ) ); + setBackground( isSelected ? Color.green : list.getBackground() ); + setForeground( isSelected ? Color.blue : list.getForeground() ); + return this; + } + + @Override + protected void paintComponent( Graphics g ) { + if( isSelected ) { + g.setColor( getBackground() ); + FlatListUI.paintCellSelection( list, g, index, 0, 0, getWidth(), getHeight() ); + } + + super.paintComponent( g ); + } + } + //---- class TestDefaultTreeCellRenderer ---------------------------------- private static class TestDefaultTreeCellRenderer diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd index 2ded48034..44624732d 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.5.0.404" Java: "17.0.2" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -296,14 +296,14 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" - "$columnConstraints": "[fill][fill]" + "$columnConstraints": "[fill]" "$rowConstraints": "[]" } ) { name: "panel5" "border": new javax.swing.border.TitledBorder( "General Control" ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "dndCheckBox" - "text": "enable drag and drop" + "text": "drag and drop" "mnemonic": 68 auxiliary() { "JavaCodeGenerator.variableLocal": false @@ -313,7 +313,80 @@ new FormModel { "value": "cell 0 0" } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 4" + "value": "cell 0 4 4 1" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "hidemode 3" + "$columnConstraints": "[fill][fill]" + "$rowConstraints": "[][][]" + } ) { + name: "panel6" + "border": new javax.swing.border.TitledBorder( "JList Control" ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "listRendererLabel" + "text": "Renderer:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JComboBox" ) { + name: "listRendererComboBox" + "model": new javax.swing.DefaultComboBoxModel { + selectedItem: "default" + addElement( "default" ) + addElement( "defaultSubclass" ) + addElement( "label" ) + addElement( "labelRounded" ) + } + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "listRendererChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "listLayoutOrientationLabel" + "text": "Orientation:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JComboBox" ) { + name: "listLayoutOrientationField" + "model": new javax.swing.DefaultComboBoxModel { + selectedItem: "vertical" + addElement( "vertical" ) + addElement( "vertical wrap" ) + addElement( "horzontal wrap" ) + } + auxiliary() { + "JavaCodeGenerator.variableLocal": false + "JavaCodeGenerator.typeParameters": "String" + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "listLayoutOrientationChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "listVisibleRowCountLabel" + "text": "Visible row count:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JSpinner" ) { + name: "listVisibleRowCountSpinner" + "model": new javax.swing.SpinnerNumberModel { + minimum: 0 + value: 8 + } + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "listVisibleRowCountChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4 4 1" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" @@ -378,7 +451,7 @@ new FormModel { "value": "cell 0 3" } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 4" + "value": "cell 0 4 4 1" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index 4baa1e5eb..456b18a39 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -424,10 +424,12 @@ List.focusSelectedCellHighlightBorder List.font List.foreground List.noFocusBorder +List.selectionArc List.selectionBackground List.selectionForeground List.selectionInactiveBackground List.selectionInactiveForeground +List.selectionInsets List.showCellFocusIndicator List.timeFactor ListUI