diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6dba2a..2f9a9123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ FlatLaf Change Log - TabbedPane: Support equal and compact tab width modes. (set client property `JTabbedPane.tabWidthMode` to `"preferred"`, `"equal"` or `"compact"`) (PR #199) +- TabbedPane: Support left, right, top and bottom tab icon placement. (set + client property `JTabbedPane.tabIconPlacement` to `SwingConstants.LEADING`, + `SwingConstants.TRAILING`, `SwingConstants.TOP` or `SwingConstants.BOTTOM`) + (PR #199) - Support painting separator line between window title and content (use UI value `TitlePane.borderColor`). (issue #184) - Extras: `FlatSVGIcon` now allows specifying icon width and height in diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java index c9b8bc5a..2cbf1ab4 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf; import java.awt.Color; import java.util.Objects; import javax.swing.JComponent; +import javax.swing.SwingConstants; /** * @author Karl Tauber @@ -101,7 +102,7 @@ public interface FlatClientProperties /** * Specifies whether the button preferred size will be made square (quadratically). *
- * Components {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
+ * Components {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
* Value type {@link java.lang.Boolean}
*/
String SQUARE_SIZE = "JButton.squareSize";
@@ -113,7 +114,7 @@ public interface FlatClientProperties
*
* Component {@link javax.swing.JButton}, {@link javax.swing.JToggleButton},
* {@link javax.swing.JComboBox}, {@link javax.swing.JSpinner} and {@link javax.swing.text.JTextComponent}
- * Value type {@link java.lang.Integer}
+ * Value type {@link java.lang.Integer}
*/
String MINIMUM_WIDTH = "JComponent.minimumWidth";
@@ -121,7 +122,7 @@ public interface FlatClientProperties
* Specifies minimum height of a component.
*
* Component {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
- * Value type {@link java.lang.Integer}
+ * Value type {@link java.lang.Integer}
*/
String MINIMUM_HEIGHT = "JComponent.minimumHeight";
@@ -157,7 +158,7 @@ public interface FlatClientProperties
* Paint the component with round edges.
*
* Components {@link javax.swing.JComboBox}, {@link javax.swing.JSpinner},
- * {@link javax.swing.JTextField}, {@link javax.swing.JFormattedTextField} and {@link javax.swing.JPasswordField}
+ * {@link javax.swing.JTextField}, {@link javax.swing.JFormattedTextField} and {@link javax.swing.JPasswordField}
* Value type {@link java.lang.Boolean}
*/
String COMPONENT_ROUND_RECT = "JComponent.roundRect";
@@ -249,7 +250,7 @@ public interface FlatClientProperties
/**
* Specifies the minimum width of a tab.
*
- * Component {@link javax.swing.JTabbedPane}
+ * Component {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})
* Value type {@link java.lang.Integer}
*/
@@ -261,7 +262,7 @@ public interface FlatClientProperties
* Applied only if tab does not have a custom tab component
* (see {@link javax.swing.JTabbedPane#setTabComponentAt(int, java.awt.Component)}).
*
- * Component {@link javax.swing.JTabbedPane}
+ * Component {@link javax.swing.JTabbedPane}
* or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})
* Value type {@link java.lang.Integer}
*/
@@ -287,7 +288,7 @@ public interface FlatClientProperties
/**
* Specifies the insets of the tab area.
*
- * Component {@link javax.swing.JTabbedPane}
+ * Component {@link javax.swing.JTabbedPane}
* Value type {@link java.awt.Insets}
*/
String TABBED_PANE_TAB_AREA_INSETS = "JTabbedPane.tabAreaInsets";
@@ -359,7 +360,7 @@ public interface FlatClientProperties
* Specifies how to navigate to hidden tabs.
*
* Component {@link javax.swing.JTabbedPane}
- * Value type {@link java.lang.String}
+ * Value type {@link java.lang.String}
* Allowed Values {@link #TABBED_PANE_HIDDEN_TABS_NAVIGATION_MORE_TABS_BUTTON}
* or {@link #TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS}
*/
@@ -383,7 +384,7 @@ public interface FlatClientProperties
* Specifies the alignment of the tab area.
*
* Component {@link javax.swing.JTabbedPane}
- * Value type {@link java.lang.String}
+ * Value type {@link java.lang.String}
* Allowed Values {@link #TABBED_PANE_TAB_AREA_ALIGN_LEADING} (default),
* {@link #TABBED_PANE_TAB_AREA_ALIGN_TRAILING}, {@link #TABBED_PANE_TAB_AREA_ALIGN_CENTER}
* or {@link #TABBED_PANE_TAB_AREA_ALIGN_FILL}
@@ -422,7 +423,7 @@ public interface FlatClientProperties
* Specifies how the tabs should be sized.
*
* Component {@link javax.swing.JTabbedPane}
- * Value type {@link java.lang.String}
+ * Value type {@link java.lang.String}
* Allowed Values {@link #TABBED_PANE_TAB_WIDTH_MODE_PREFERRED} (default),
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL} or {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT}
*/
@@ -450,6 +451,17 @@ public interface FlatClientProperties
*/
String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact";
+ /**
+ * Specifies the tab icon placement (relative to tab title).
+ *
+ * Component {@link javax.swing.JTabbedPane}
+ * Value type {@link java.lang.Integer}
+ * Allowed Values {@link SwingConstants#LEADING} (default),
+ * {@link SwingConstants#TRAILING}, {@link SwingConstants#TOP}
+ * or {@link SwingConstants#BOTTOM}
+ */
+ String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement";
+
/**
* Specifies a component that will be placed at the leading edge of the tabs area.
*
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java
index ff9baeb5..95023fad 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java
@@ -563,8 +563,29 @@ public class FlatTabbedPaneUI
{
Insets tabInsets = getTabInsets( tabPlacement, tabIndex );
tabWidth = icon.getIconWidth() + tabInsets.left + tabInsets.right;
- } else
- tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
+ } else {
+ int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING );
+ if( (iconPlacement == TOP || iconPlacement == BOTTOM) &&
+ tabPane.getTabComponentAt( tabIndex ) == null &&
+ (icon = getIconForTab( tabIndex )) != null )
+ {
+ // TOP and BOTTOM icon placement
+ tabWidth = icon.getIconWidth();
+
+ View view = getTextViewForTab( tabIndex );
+ if( view != null )
+ tabWidth = Math.max( tabWidth, (int) view.getPreferredSpan( View.X_AXIS ) );
+ else {
+ String title = tabPane.getTitleAt( tabIndex );
+ if( title != null )
+ tabWidth = Math.max( tabWidth, metrics.stringWidth( title ) );
+ }
+
+ Insets tabInsets = getTabInsets( tabPlacement, tabIndex );
+ tabWidth += tabInsets.left + tabInsets.right;
+ } else
+ tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
+ }
// make tab wider if closable
if( isTabClosable( tabIndex ) )
@@ -583,8 +604,29 @@ public class FlatTabbedPaneUI
@Override
protected int calculateTabHeight( int tabPlacement, int tabIndex, int fontHeight ) {
- int tabHeight = scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, this.tabHeight ) );
- return Math.max( tabHeight, super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */ );
+ int tabHeight;
+
+ Icon icon;
+ int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING );
+ if( (iconPlacement == TOP || iconPlacement == BOTTOM) &&
+ tabPane.getTabComponentAt( tabIndex ) == null &&
+ (icon = getIconForTab( tabIndex )) != null )
+ {
+ // TOP and BOTTOM icon placement
+ tabHeight = icon.getIconHeight();
+
+ View view = getTextViewForTab( tabIndex );
+ if( view != null )
+ tabHeight += (int) view.getPreferredSpan( View.Y_AXIS ) + scale( textIconGapUnscaled );
+ else if( tabPane.getTitleAt( tabIndex ) != null )
+ tabHeight += fontHeight + scale( textIconGapUnscaled );
+
+ Insets tabInsets = getTabInsets( tabPlacement, tabIndex );
+ tabHeight += tabInsets.top + tabInsets.bottom;
+ } else
+ tabHeight = super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */;
+
+ return Math.max( tabHeight, scale( clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, this.tabHeight ) ) );
}
@Override
@@ -988,6 +1030,16 @@ public class FlatTabbedPaneUI
tabRect.x += closeIcon.getIconWidth();
}
+ // icon placement
+ int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING );
+ int verticalTextPosition = CENTER;
+ int horizontalTextPosition = TRAILING;
+ switch( iconPlacement ) {
+ case TRAILING: horizontalTextPosition = LEADING; break;
+ case TOP: verticalTextPosition = BOTTOM; horizontalTextPosition = CENTER; break;
+ case BOTTOM: verticalTextPosition = TOP; horizontalTextPosition = CENTER; break;
+ }
+
// reset rectangles
textRect.setBounds( 0, 0, 0, 0 );
iconRect.setBounds( 0, 0, 0, 0 );
@@ -999,8 +1051,7 @@ public class FlatTabbedPaneUI
// layout label
String clippedTitle = SwingUtilities.layoutCompoundLabel( tabPane, metrics, title, icon,
- SwingUtilities.CENTER, SwingUtilities.CENTER,
- SwingUtilities.CENTER, SwingUtilities.TRAILING,
+ CENTER, CENTER, verticalTextPosition, horizontalTextPosition,
tabRect, iconRect, textRect, scale( textIconGapUnscaled ) );
// remove temporary client property
@@ -1945,6 +1996,7 @@ public class FlatTabbedPaneUI
case TABBED_PANE_HIDDEN_TABS_NAVIGATION:
case TABBED_PANE_TAB_AREA_ALIGNMENT:
case TABBED_PANE_TAB_WIDTH_MODE:
+ case TABBED_PANE_TAB_ICON_PLACEMENT:
case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate();
tabPane.repaint();
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java
index 80d935fd..9dcf9194 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java
@@ -176,6 +176,17 @@ public class FlatContainerTest
tabbedPane.setIconAt( i, icon );
}
+ private void iconPlacementChanged() {
+ Object iconPlacement = null;
+ switch( (String) iconPlacementField.getSelectedItem() ) {
+ case "leading": iconPlacement = SwingConstants.LEADING; break;
+ case "trailing": iconPlacement = SwingConstants.TRAILING; break;
+ case "top": iconPlacement = SwingConstants.TOP; break;
+ case "bottom": iconPlacement = SwingConstants.BOTTOM; break;
+ }
+ putTabbedPanesClientProperty( TABBED_PANE_TAB_ICON_PLACEMENT, iconPlacement );
+ }
+
private void customBorderChanged() {
Border border = customBorderCheckBox.isSelected()
? new MatteBorder( 10, 20, 25, 35, Color.green )
@@ -390,6 +401,7 @@ public class FlatContainerTest
tabPlacementField = new JComboBox<>();
tabIconsCheckBox = new JCheckBox();
tabIconSizeSpinner = new JSpinner();
+ iconPlacementField = new JComboBox<>();
JLabel tabAreaAlignmentLabel = new JLabel();
tabAreaAlignmentField = new JComboBox<>();
JLabel tabWidthModeLabel = new JLabel();
@@ -592,6 +604,16 @@ public class FlatContainerTest
tabIconSizeSpinner.addChangeListener(e -> tabIconsChanged());
tabbedPaneControlPanel.add(tabIconSizeSpinner, "cell 2 2");
+ //---- iconPlacementField ----
+ iconPlacementField.setModel(new DefaultComboBoxModel<>(new String[] {
+ "leading",
+ "trailing",
+ "top",
+ "bottom"
+ }));
+ iconPlacementField.addActionListener(e -> iconPlacementChanged());
+ tabbedPaneControlPanel.add(iconPlacementField, "cell 2 2");
+
//---- tabAreaAlignmentLabel ----
tabAreaAlignmentLabel.setText("Tab area alignment:");
tabbedPaneControlPanel.add(tabAreaAlignmentLabel, "cell 0 3");
@@ -710,6 +732,7 @@ public class FlatContainerTest
private JComboBox