From 007ee38cb4ef3febd224795885e5483aaac74792 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 22 Jun 2021 17:34:31 +0200 Subject: [PATCH] Styling: support List, Table and Tree --- .../com/formdev/flatlaf/ui/FlatListUI.java | 65 ++++++++- .../com/formdev/flatlaf/ui/FlatTableUI.java | 77 ++++++++++- .../com/formdev/flatlaf/ui/FlatTreeUI.java | 129 ++++++++++++++---- .../formdev/flatlaf/ui/FlatStylingTests.java | 36 +++++ 4 files changed, 271 insertions(+), 36 deletions(-) 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 d639ecb3..1d4f13db 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 @@ -20,10 +20,13 @@ import java.awt.Color; import java.awt.EventQueue; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicListUI; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JList}. @@ -64,15 +67,24 @@ import javax.swing.plaf.basic.BasicListUI; public class FlatListUI extends BasicListUI { - protected Color selectionBackground; - protected Color selectionForeground; - protected Color selectionInactiveBackground; - protected Color selectionInactiveForeground; + @Styleable protected Color selectionBackground; + @Styleable protected Color selectionForeground; + @Styleable protected Color selectionInactiveBackground; + @Styleable protected Color selectionInactiveForeground; + + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatListUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -93,6 +105,8 @@ public class FlatListUI selectionForeground = null; selectionInactiveBackground = null; selectionInactiveForeground = null; + + oldStyleValues = null; } @Override @@ -116,6 +130,49 @@ public class FlatListUI }; } + @Override + protected PropertyChangeListener createPropertyChangeListener() { + return FlatStyleSupport.createPropertyChangeListener( list, this::applyStyle, + super.createPropertyChangeListener() ); + } + + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + Color oldSelectionBackground = selectionBackground; + Color oldSelectionForeground = selectionForeground; + Color oldSelectionInactiveBackground = selectionInactiveBackground; + Color oldSelectionInactiveForeground = selectionInactiveForeground; + + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + // update selection background + if( selectionBackground != oldSelectionBackground ) { + Color selBg = list.getSelectionBackground(); + if( selBg == oldSelectionBackground ) + list.setSelectionBackground( selectionBackground ); + else if( selBg == oldSelectionInactiveBackground ) + list.setSelectionBackground( selectionInactiveBackground ); + } + + // update selection foreground + if( selectionForeground != oldSelectionForeground ) { + Color selFg = list.getSelectionForeground(); + if( selFg == oldSelectionForeground ) + list.setSelectionForeground( selectionForeground ); + else if( selFg == oldSelectionInactiveForeground ) + list.setSelectionForeground( selectionInactiveForeground ); + } + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } + /** * Toggle selection colors from focused to inactive and vice versa. * diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java index 84e4edfa..a422986d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java @@ -25,6 +25,8 @@ import java.awt.Graphics2D; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.geom.Rectangle2D; +import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JViewport; @@ -34,6 +36,8 @@ import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTableUI; import javax.swing.table.JTableHeader; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -92,19 +96,29 @@ public class FlatTableUI protected boolean showVerticalLines; protected Dimension intercellSpacing; - protected Color selectionBackground; - protected Color selectionForeground; - protected Color selectionInactiveBackground; - protected Color selectionInactiveForeground; + @Styleable protected Color selectionBackground; + @Styleable protected Color selectionForeground; + @Styleable protected Color selectionInactiveBackground; + @Styleable protected Color selectionInactiveForeground; private boolean oldShowHorizontalLines; private boolean oldShowVerticalLines; private Dimension oldIntercellSpacing; + private PropertyChangeListener propertyChangeListener; + private Map oldStyleValues; + public static ComponentUI createUI( JComponent c ) { return new FlatTableUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -148,6 +162,8 @@ public class FlatTableUI selectionInactiveBackground = null; selectionInactiveForeground = null; + oldStyleValues = null; + // restore old show horizontal/vertical lines (if not modified) if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() ) table.setShowHorizontalLines( true ); @@ -159,6 +175,22 @@ public class FlatTableUI table.setIntercellSpacing( oldIntercellSpacing ); } + @Override + protected void installListeners() { + super.installListeners(); + + propertyChangeListener = FlatStyleSupport.createPropertyChangeListener( table, this::applyStyle, null ); + table.addPropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + + table.removePropertyChangeListener( FlatClientProperties.STYLE, propertyChangeListener ); + propertyChangeListener = null; + } + @Override protected FocusListener createFocusListener() { return new BasicTableUI.FocusHandler() { @@ -180,6 +212,43 @@ public class FlatTableUI }; } + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + Color oldSelectionBackground = selectionBackground; + Color oldSelectionForeground = selectionForeground; + Color oldSelectionInactiveBackground = selectionInactiveBackground; + Color oldSelectionInactiveForeground = selectionInactiveForeground; + + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + + // update selection background + if( selectionBackground != oldSelectionBackground ) { + Color selBg = table.getSelectionBackground(); + if( selBg == oldSelectionBackground ) + table.setSelectionBackground( selectionBackground ); + else if( selBg == oldSelectionInactiveBackground ) + table.setSelectionBackground( selectionInactiveBackground ); + } + + // update selection foreground + if( selectionForeground != oldSelectionForeground ) { + Color selFg = table.getSelectionForeground(); + if( selFg == oldSelectionForeground ) + table.setSelectionForeground( selectionForeground ); + else if( selFg == oldSelectionInactiveForeground ) + table.setSelectionForeground( selectionInactiveForeground ); + } + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } + /** * Toggle selection colors from focused to inactive and vice versa. * 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 6dd2c9c4..147e608c 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 @@ -26,6 +26,7 @@ import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeListener; +import java.util.Map; import javax.swing.CellRendererPane; import javax.swing.Icon; import javax.swing.JComponent; @@ -39,6 +40,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreePath; +import com.formdev.flatlaf.ui.FlatStyleSupport.Styleable; import com.formdev.flatlaf.util.UIScale; /** @@ -99,20 +101,31 @@ import com.formdev.flatlaf.util.UIScale; public class FlatTreeUI extends BasicTreeUI { - protected Color selectionBackground; - protected Color selectionForeground; - protected Color selectionInactiveBackground; - protected Color selectionInactiveForeground; - protected Color selectionBorderColor; - protected boolean wideSelection; - protected boolean showCellFocusIndicator; + @Styleable protected Color selectionBackground; + @Styleable protected Color selectionForeground; + @Styleable protected Color selectionInactiveBackground; + @Styleable protected Color selectionInactiveForeground; + @Styleable protected Color selectionBorderColor; + @Styleable protected boolean wideSelection; + @Styleable protected boolean showCellFocusIndicator; private Color defaultCellNonSelectionBackground; + private Color defaultSelectionBackground; + private Color defaultSelectionForeground; + private Color defaultSelectionBorderColor; + private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { return new FlatTreeUI(); } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + applyStyle( FlatStyleSupport.getStyle( c ) ); + } + @Override protected void installDefaults() { super.installDefaults(); @@ -128,6 +141,9 @@ public class FlatTreeUI showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" ); defaultCellNonSelectionBackground = UIManager.getColor( "Tree.textBackground" ); + defaultSelectionBackground = selectionBackground; + defaultSelectionForeground = selectionForeground; + defaultSelectionBorderColor = selectionBorderColor; // scale int rowHeight = FlatUIUtils.getUIInt( "Tree.rowHeight", 16 ); @@ -150,6 +166,10 @@ public class FlatTreeUI selectionBorderColor = null; defaultCellNonSelectionBackground = null; + defaultSelectionBackground = null; + defaultSelectionForeground = null; + defaultSelectionBorderColor = null; + oldStyleValues = null; } @Override @@ -216,6 +236,12 @@ public class FlatTreeUI repaintWideDropLocation( tree.getDropLocation() ); } break; + + case STYLE: + applyStyle( e.getNewValue() ); + tree.revalidate(); + tree.repaint(); + break; } } }; @@ -230,6 +256,20 @@ public class FlatTreeUI tree.repaint( 0, r.y, tree.getWidth(), r.height ); } + /** + * @since TODO + */ + protected void applyStyle( Object style ) { + oldStyleValues = FlatStyleSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); + } + + /** + * @since TODO + */ + protected Object applyStyleProperty( String key, Object value ) { + return FlatStyleSupport.applyToAnnotatedObject( this, key, value ); + } + /** * Same as super.paintRow(), but supports wide selection and uses * inactive selection background/foreground if tree is not focused. @@ -259,35 +299,32 @@ public class FlatTreeUI Component rendererComponent = currentCellRenderer.getTreeCellRendererComponent( tree, path.getLastPathComponent(), isSelected, isExpanded, isLeaf, row, cellHasFocus ); - // apply inactive selection background/foreground if tree is not focused + // renderer background/foreground Color oldBackgroundSelectionColor = null; if( isSelected && !hasFocus && !isDropRow ) { - if( rendererComponent instanceof DefaultTreeCellRenderer ) { - DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; - if( renderer.getBackgroundSelectionColor() == selectionBackground ) { - oldBackgroundSelectionColor = renderer.getBackgroundSelectionColor(); - renderer.setBackgroundSelectionColor( selectionInactiveBackground ); - } - } else { - if( rendererComponent.getBackground() == selectionBackground ) - rendererComponent.setBackground( selectionInactiveBackground ); - } + // apply inactive selection background/foreground if tree is not focused + oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionInactiveBackground ); + setRendererForeground( rendererComponent, selectionInactiveForeground ); - if( rendererComponent.getForeground() == selectionForeground ) - rendererComponent.setForeground( selectionInactiveForeground ); + } else if( isSelected ) { + // update background/foreground if set via style + if( selectionBackground != defaultSelectionBackground ) + oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionBackground ); + if( selectionForeground != defaultSelectionForeground ) + setRendererForeground( rendererComponent, selectionForeground ); } - // remove focus selection border if exactly one item is selected + // update focus selection border Color oldBorderSelectionColor = null; if( isSelected && hasFocus && - (!showCellFocusIndicator || tree.getMinSelectionRow() == tree.getMaxSelectionRow()) && - rendererComponent instanceof DefaultTreeCellRenderer ) + (!showCellFocusIndicator || tree.getMinSelectionRow() == tree.getMaxSelectionRow()) ) { - DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; - if( renderer.getBorderSelectionColor() == selectionBorderColor ) { - oldBorderSelectionColor = renderer.getBorderSelectionColor(); - renderer.setBorderSelectionColor( null ); - } + // remove focus selection border if exactly one item is selected or if showCellFocusIndicator is false + oldBorderSelectionColor = setRendererBorderSelectionColor( rendererComponent, null ); + + } else if( hasFocus && selectionBorderColor != defaultSelectionBorderColor ) { + // update focus selection border if set via style + oldBorderSelectionColor = setRendererBorderSelectionColor( rendererComponent, selectionBorderColor ); } // paint selection background @@ -343,6 +380,42 @@ public class FlatTreeUI ((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor ); } + private Color setRendererBackgroundSelectionColor( Component rendererComponent, Color color ) { + Color oldColor = null; + + if( rendererComponent instanceof DefaultTreeCellRenderer ) { + DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; + if( renderer.getBackgroundSelectionColor() == defaultSelectionBackground ) { + oldColor = renderer.getBackgroundSelectionColor(); + renderer.setBackgroundSelectionColor( color ); + } + } else { + if( rendererComponent.getBackground() == defaultSelectionBackground ) + rendererComponent.setBackground( color ); + } + + return oldColor; + } + + private void setRendererForeground( Component rendererComponent, Color color ) { + if( rendererComponent.getForeground() == defaultSelectionForeground ) + rendererComponent.setForeground( color ); + } + + private Color setRendererBorderSelectionColor( Component rendererComponent, Color color ) { + Color oldColor = null; + + if( rendererComponent instanceof DefaultTreeCellRenderer ) { + DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; + if( renderer.getBorderSelectionColor() == defaultSelectionBorderColor ) { + oldColor = renderer.getBorderSelectionColor(); + renderer.setBorderSelectionColor( color ); + } + } + + return oldColor; + } + private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds ) { int xOffset = 0; int imageOffset = 0; diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java index fe15d12c..74ad4946 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/FlatStylingTests.java @@ -173,6 +173,17 @@ public class FlatStylingTests textField( ui ); } + @Test + void list() { + FlatListUI ui = new FlatListUI(); + ui.installUI( new JList<>() ); + + ui.applyStyle( "selectionBackground: #fff" ); + ui.applyStyle( "selectionForeground: #fff" ); + ui.applyStyle( "selectionInactiveBackground: #fff" ); + ui.applyStyle( "selectionInactiveForeground: #fff" ); + } + @Test void menu() { UIManager.put( "Menu.arrowIcon", new FlatMenuArrowIcon() ); @@ -461,6 +472,17 @@ public class FlatStylingTests ui.applyStyle( "gripGap: 2" ); } + @Test + void table() { + FlatTableUI ui = new FlatTableUI(); + ui.installUI( new JTable() ); + + ui.applyStyle( "selectionBackground: #fff" ); + ui.applyStyle( "selectionForeground: #fff" ); + ui.applyStyle( "selectionInactiveBackground: #fff" ); + ui.applyStyle( "selectionInactiveForeground: #fff" ); + } + @Test void textArea() { FlatTextAreaUI ui = new FlatTextAreaUI(); @@ -529,6 +551,20 @@ public class FlatStylingTests ui.applyStyle( b, "tab.focusBackground: #fff" ); } + @Test + void tree() { + FlatTreeUI ui = new FlatTreeUI(); + ui.installUI( new JTree() ); + + ui.applyStyle( "selectionBackground: #fff" ); + ui.applyStyle( "selectionForeground: #fff" ); + ui.applyStyle( "selectionInactiveBackground: #fff" ); + ui.applyStyle( "selectionInactiveForeground: #fff" ); + ui.applyStyle( "selectionBorderColor: #fff" ); + ui.applyStyle( "wideSelection: true" ); + ui.applyStyle( "showCellFocusIndicator: true" ); + } + //---- component borders -------------------------------------------------- private void flatButtonBorder( Consumer applyStyle ) {