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 32e214d4..b87a4cef 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 @@ -22,6 +22,7 @@ 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; @@ -185,8 +186,8 @@ public class FlatListUI return e -> { superListener.valueChanged( e ); - // for united rounded selection, repaint parts of the rows that adjoin to the changed rows - if( useUnitedRoundedSelection() && + // 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 ) { @@ -196,7 +197,7 @@ public class FlatListUI 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) ); + list.repaint( r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) ); } } }; @@ -359,23 +360,64 @@ public class FlatListUI /** @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 ); + float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; + arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = 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; + 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 ), arcTop, arcTop, arcBottom, arcBottom, 0 ); + UIScale.scale( selectionInsets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 ); } - private boolean useUnitedRoundedSelection() { + private boolean useUnitedRoundedSelection( boolean vertical, boolean horizontal ) { return selectionArc > 0 && - (selectionInsets == null || (selectionInsets.top == 0 && selectionInsets.bottom == 0)); + (selectionInsets == null || + (vertical && selectionInsets.top == 0 && selectionInsets.bottom == 0) || + (horizontal && selectionInsets.left == 0 && selectionInsets.right == 0)); } /** 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 731ce08c..a9183c70 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.Component; 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 javax.swing.tree.TreePath; 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; @@ -361,7 +364,38 @@ public class FlatComponents2Test 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() { @@ -508,6 +542,10 @@ public class FlatComponents2Test 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<>(); @@ -795,6 +833,8 @@ public class FlatComponents2Test "[fill]" + "[fill]", // rows + "[]" + + "[]" + "[]")); //---- listRendererLabel ---- @@ -805,10 +845,33 @@ public class FlatComponents2Test listRendererComboBox.setModel(new DefaultComboBoxModel<>(new String[] { "default", "defaultSubclass", - "label" + "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"); @@ -973,6 +1036,8 @@ public class FlatComponents2Test private JXTreeTable xTreeTable1; private JCheckBox dndCheckBox; private JComboBox listRendererComboBox; + private JComboBox listLayoutOrientationField; + private JSpinner listVisibleRowCountSpinner; private JComboBox treeRendererComboBox; private JCheckBox treeWideSelectionCheckBox; private JCheckBox treePaintSelectionCheckBox; @@ -1371,6 +1436,45 @@ public class FlatComponents2Test } } + //---- 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 20e1f59c..44624732 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 @@ -318,7 +318,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" "$columnConstraints": "[fill][fill]" - "$rowConstraints": "[]" + "$rowConstraints": "[][][]" } ) { name: "panel6" "border": new javax.swing.border.TitledBorder( "JList Control" ) @@ -335,6 +335,7 @@ new FormModel { addElement( "default" ) addElement( "defaultSubclass" ) addElement( "label" ) + addElement( "labelRounded" ) } auxiliary() { "JavaCodeGenerator.variableLocal": false @@ -343,6 +344,47 @@ new FormModel { }, 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" } )