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 542fe963..93e7c6b6 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -375,6 +375,35 @@ public interface FlatClientProperties //---- JTabbedPane -------------------------------------------------------- + /** + * Specifies type of the selected tab. + *
+ * Component {@link javax.swing.JTabbedPane}
+ * Value type {@link java.lang.String}
+ * Allowed Values
+ * {@link #TABBED_PANE_TAB_TYPE_UNDERLINED} or
+ * {@link #TABBED_PANE_TAB_TYPE_CARD}
+ *
+ * @since 2
+ */
+ String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType";
+
+ /**
+ * Paint the selected tab underlined.
+ *
+ * @see #TABBED_PANE_TAB_TYPE
+ * @since 2
+ */
+ String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined";
+
+ /**
+ * Paint the selected tab as card.
+ *
+ * @see #TABBED_PANE_TAB_TYPE
+ * @since 2
+ */
+ String TABBED_PANE_TAB_TYPE_CARD = "card";
+
/**
* Specifies whether separators are shown between tabs.
*
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 7b0aae0d..59525696 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
@@ -40,6 +40,8 @@ import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
@@ -134,12 +136,15 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.maximumTabWidth int optional
* @uiDefault TabbedPane.tabHeight int
* @uiDefault TabbedPane.tabSelectionHeight int
+ * @uiDefault TabbedPane.cardTabSelectionHeight int
* @uiDefault TabbedPane.contentSeparatorHeight int
* @uiDefault TabbedPane.showTabSeparators boolean
* @uiDefault TabbedPane.tabSeparatorsFullHeight boolean
* @uiDefault TabbedPane.hasFullBorder boolean
+ * @uiDefault TabbedPane.activeTabBorder boolean
*
* @uiDefault TabbedPane.tabLayoutPolicy String wrap (default) or scroll
+ * @uiDefault TabbedPane.tabType String underlined (default) or card
* @uiDefault TabbedPane.tabsPopupPolicy String never or asNeeded (default)
* @uiDefault TabbedPane.scrollButtonsPolicy String never, asNeeded or asNeededSingle (default)
* @uiDefault TabbedPane.scrollButtonsPlacement String both (default) or trailing
@@ -165,6 +170,10 @@ public class FlatTabbedPaneUI
extends BasicTabbedPaneUI
implements StyleableUI
{
+ // tab type
+ /** @since 2 */ protected static final int TAB_TYPE_UNDERLINED = 0;
+ /** @since 2 */ protected static final int TAB_TYPE_CARD = 1;
+
// tabs popup policy / scroll arrows policy
protected static final int NEVER = 0;
// protected static final int ALWAYS = 1;
@@ -200,12 +209,14 @@ public class FlatTabbedPaneUI
@Styleable protected int maximumTabWidth;
@Styleable protected int tabHeight;
@Styleable protected int tabSelectionHeight;
+ /** @since 2 */ protected int cardTabSelectionHeight;
@Styleable protected int contentSeparatorHeight;
@Styleable protected boolean showTabSeparators;
@Styleable protected boolean tabSeparatorsFullHeight;
@Styleable protected boolean hasFullBorder;
@Styleable protected boolean tabsOpaque = true;
+ private int tabType;
@Styleable(type=String.class) private int tabsPopupPolicy;
@Styleable(type=String.class) private int scrollButtonsPolicy;
@Styleable(type=String.class) private int scrollButtonsPlacement;
@@ -318,12 +329,14 @@ public class FlatTabbedPaneUI
maximumTabWidth = UIManager.getInt( "TabbedPane.maximumTabWidth" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
+ cardTabSelectionHeight = UIManager.getInt( "TabbedPane.cardTabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" );
+ tabType = parseTabType( UIManager.getString( "TabbedPane.tabType" ) );
tabsPopupPolicy = parseTabsPopupPolicy( UIManager.getString( "TabbedPane.tabsPopupPolicy" ) );
scrollButtonsPolicy = parseScrollButtonsPolicy( UIManager.getString( "TabbedPane.scrollButtonsPolicy" ) );
scrollButtonsPlacement = parseScrollButtonsPlacement( UIManager.getString( "TabbedPane.scrollButtonsPlacement" ) );
@@ -560,6 +573,13 @@ public class FlatTabbedPaneUI
return handler;
}
+ @Override
+ protected FocusListener createFocusListener() {
+ Handler handler = getHandler();
+ handler.focusDelegate = super.createFocusListener();
+ return handler;
+ }
+
@Override
protected LayoutManager createLayoutManager() {
if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT )
@@ -707,8 +727,25 @@ public class FlatTabbedPaneUI
return;
Rectangle r = getTabBounds( tabPane, tabIndex );
- if( r != null )
- tabPane.repaint( r );
+ if( r == null )
+ return;
+
+ // increase size of repaint region to include part of content border
+ if( contentSeparatorHeight > 0 &&
+ getTabType() == TAB_TYPE_CARD &&
+ clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) )
+ {
+ int sh = scale( contentSeparatorHeight );
+ switch( tabPane.getTabPlacement() ) {
+ default:
+ case TOP: r.height += sh; break;
+ case BOTTOM: r.height += sh; r.y -= sh; break;
+ case LEFT: r.width += sh; break;
+ case RIGHT: r.width += sh; r.x -= sh; break;
+ }
+ }
+
+ tabPane.repaint( r );
}
private boolean inCalculateEqual;
@@ -1045,16 +1082,21 @@ public class FlatTabbedPaneUI
int x, int y, int w, int h, boolean isSelected )
{
// paint tab background
+ Color background = getTabBackground( tabPlacement, tabIndex, isSelected );
+ g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) );
+ g.fillRect( x, y, w, h );
+ }
+
+ /** @since 2 */
+ protected Color getTabBackground( int tabPlacement, int tabIndex, boolean isSelected ) {
boolean enabled = tabPane.isEnabled();
- Color background = enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
+ return enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
? hoverColor
: (enabled && isSelected && FlatUIUtils.isPermanentFocusOwner( tabPane )
? focusColor
: (selectedBackground != null && enabled && isSelected
? selectedBackground
: tabPane.getBackgroundAt( tabIndex )));
- g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) );
- g.fillRect( x, y, w, h );
}
@Override
@@ -1064,7 +1106,62 @@ public class FlatTabbedPaneUI
// paint tab separators
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) )
- paintTabSeparator( g, tabPlacement, x, y, w, h );
+ {
+ if( getTabType() == TAB_TYPE_CARD ) {
+ // some separators need to be omitted if selected tab is painted as card
+ int selectedIndex = tabPane.getSelectedIndex();
+ if( tabIndex != selectedIndex - 1 && tabIndex != selectedIndex )
+ paintTabSeparator( g, tabPlacement, x, y, w, h );
+ } else
+ paintTabSeparator( g, tabPlacement, x, y, w, h );
+ }
+
+ // paint active tab border
+ if( isSelected && getTabType() == TAB_TYPE_CARD )
+ paintCardTabBorder( g, tabPlacement, tabIndex, x, y, w, h );
+ }
+
+ /** @since 2 */
+ protected void paintCardTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h ) {
+ Graphics2D g2 = (Graphics2D) g;
+
+ float borderWidth = scale( (float) contentSeparatorHeight );
+ g.setColor( (tabSeparatorColor != null) ? tabSeparatorColor : contentAreaColor );
+
+ switch( tabPlacement ) {
+ default:
+ case TOP:
+ case BOTTOM:
+ // paint left and right tab border
+ g2.fill( new Rectangle2D.Float( x, y, borderWidth, h ) );
+ g2.fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h ) );
+ break;
+ case LEFT:
+ case RIGHT:
+ // paint top and bottom tab border
+ g2.fill( new Rectangle2D.Float( x, y, w, borderWidth ) );
+ g2.fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth ) );
+ break;
+ }
+
+ if( cardTabSelectionHeight <= 0 ) {
+ // if there is no tab selection indicator, paint a top border as well
+ switch( tabPlacement ) {
+ default:
+ case TOP:
+ g2.fill( new Rectangle2D.Float( x, y, w, borderWidth ) );
+ break;
+ case BOTTOM:
+ g2.fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth ) );
+ break;
+ case LEFT:
+ g2.fill( new Rectangle2D.Float( x, y, borderWidth, h ) );
+ break;
+ case RIGHT:
+ g2.fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h ) );
+ break;
+ }
+ }
}
protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) {
@@ -1110,26 +1207,30 @@ public class FlatTabbedPaneUI
g.setColor( tabPane.isEnabled() ? underlineColor : disabledUnderlineColor );
// paint underline selection
+ boolean atBottom = (getTabType() != TAB_TYPE_CARD);
Insets contentInsets = getContentBorderInsets( tabPlacement );
- int tabSelectionHeight = scale( this.tabSelectionHeight );
+ int tabSelectionHeight = scale( atBottom ? this.tabSelectionHeight : cardTabSelectionHeight );
+ int sx, sy;
switch( tabPlacement ) {
case TOP:
default:
- int sy = y + h + contentInsets.top - tabSelectionHeight;
+ sy = atBottom ? (y + h + contentInsets.top - tabSelectionHeight) : y;
g.fillRect( x, sy, w, tabSelectionHeight );
break;
case BOTTOM:
- g.fillRect( x, y - contentInsets.bottom, w, tabSelectionHeight );
+ sy = atBottom ? (y - contentInsets.bottom) : (y + h - tabSelectionHeight);
+ g.fillRect( x, sy, w, tabSelectionHeight );
break;
case LEFT:
- int sx = x + w + contentInsets.left - tabSelectionHeight;
+ sx = atBottom ? (x + w + contentInsets.left - tabSelectionHeight) : x;
g.fillRect( sx, y, tabSelectionHeight, h );
break;
case RIGHT:
- g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h );
+ sx = atBottom ? (x - contentInsets.right) : (x + w - tabSelectionHeight);
+ g.fillRect( sx, y, tabSelectionHeight, h );
break;
}
}
@@ -1141,6 +1242,7 @@ public class FlatTabbedPaneUI
* - paint full border (if enabled)
* - not invoking paintContentBorder*Edge() methods
* - repaint selection
+ * - painting active tab border style
*/
@Override
protected void paintContentBorder( Graphics g, int tabPlacement, int selectedIndex ) {
@@ -1189,12 +1291,49 @@ public class FlatTabbedPaneUI
Insets ci = new Insets( 0, 0, 0, 0 );
rotateInsets( hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ), ci, tabPlacement );
- // paint content separator or full border
- g.setColor( contentAreaColor );
+ // create path for content separator or full border
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Rectangle2D.Float( x, y, w, h ), false );
path.append( new Rectangle2D.Float( x + (ci.left / 100f), y + (ci.top / 100f),
w - (ci.left / 100f) - (ci.right / 100f), h - (ci.top / 100f) - (ci.bottom / 100f) ), false );
+
+ // add gap for selected tab to path
+ if( getTabType() == TAB_TYPE_CARD ) {
+ float csh = scale( (float) contentSeparatorHeight );
+
+ Rectangle tabRect = getTabBounds( tabPane, selectedIndex );
+ Rectangle2D.Float innerTabRect = new Rectangle2D.Float( tabRect.x + csh, tabRect.y + csh,
+ tabRect.width - (csh * 2), tabRect.height - (csh * 2) );
+
+ // Ensure that the separator outside the tabViewport is present (doesn't get cutoff by the active tab)
+ // If left unsolved the active tab is "visible" in the separator (the gap) even when outside the viewport
+ if( tabViewport != null )
+ Rectangle2D.intersect( tabViewport.getBounds(), innerTabRect, innerTabRect );
+
+ Rectangle2D.Float gap = null;
+ if( isHorizontalTabPlacement() ) {
+ if( innerTabRect.width > 0 ) {
+ float y2 = (tabPlacement == TOP) ? y : y + h - csh;
+ gap = new Rectangle2D.Float( innerTabRect.x, y2, innerTabRect.width, csh );
+ }
+ } else {
+ if( innerTabRect.height > 0 ) {
+ float x2 = (tabPlacement == LEFT) ? x : x + w - csh;
+ gap = new Rectangle2D.Float( x2, innerTabRect.y, csh, innerTabRect.height );
+ }
+ }
+
+ if( gap != null ) {
+ path.append( gap, false );
+
+ // fill gap in case that the tab is colored (e.g. focused or hover)
+ g.setColor( getTabBackground( tabPlacement, selectedIndex, true ) );
+ ((Graphics2D)g).fill( gap );
+ }
+ }
+
+ // paint content separator or full border
+ g.setColor( contentAreaColor );
((Graphics2D)g).fill( path );
// repaint selection in scroll-tab-layout because it may be painted before
@@ -1399,6 +1538,15 @@ public class FlatTabbedPaneUI
clientPropertyBoolean( tabPane, TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB, hideTabAreaWithOneTab );
}
+ /** @since 2 */
+ protected int getTabType() {
+ Object value = tabPane.getClientProperty( TABBED_PANE_TAB_TYPE );
+
+ return (value instanceof String)
+ ? parseTabType( (String) value )
+ : tabType;
+ }
+
protected int getTabsPopupPolicy() {
Object value = tabPane.getClientProperty( TABBED_PANE_TABS_POPUP_POLICY );
@@ -1451,6 +1599,18 @@ public class FlatTabbedPaneUI
: tabWidthMode;
}
+ /** @since 2 */
+ protected static int parseTabType( String str ) {
+ if( str == null )
+ return TAB_TYPE_UNDERLINED;
+
+ switch( str ) {
+ default:
+ case TABBED_PANE_TAB_TYPE_UNDERLINED: return TAB_TYPE_UNDERLINED;
+ case TABBED_PANE_TAB_TYPE_CARD: return TAB_TYPE_CARD;
+ }
+ }
+
protected static int parseTabsPopupPolicy( String str ) {
if( str == null )
return AS_NEEDED;
@@ -2264,11 +2424,12 @@ public class FlatTabbedPaneUI
private class Handler
implements MouseListener, MouseMotionListener, PropertyChangeListener,
- ChangeListener, ComponentListener, ContainerListener
+ ChangeListener, ComponentListener, ContainerListener, FocusListener
{
MouseListener mouseDelegate;
PropertyChangeListener propertyChangeDelegate;
ChangeListener changeDelegate;
+ FocusListener focusDelegate;
private final PropertyChangeListener contentListener = this::contentPropertyChange;
@@ -2438,6 +2599,10 @@ public class FlatTabbedPaneUI
break;
case TABBED_PANE_SHOW_TAB_SEPARATORS:
+ case TABBED_PANE_TAB_TYPE:
+ tabPane.repaint();
+ break;
+
case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
case TABBED_PANE_HAS_FULL_BORDER:
case TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB:
@@ -2536,6 +2701,20 @@ public class FlatTabbedPaneUI
if( !(c instanceof UIResource) )
c.removePropertyChangeListener( contentListener );
}
+
+ //---- interface FocusListener ----
+
+ @Override
+ public void focusGained( FocusEvent e ) {
+ focusDelegate.focusGained( e );
+ repaintTab( tabPane.getSelectedIndex() );
+ }
+
+ @Override
+ public void focusLost( FocusEvent e ) {
+ focusDelegate.focusLost( e );
+ repaintTab( tabPane.getSelectedIndex() );
+ }
}
//---- class FlatTabbedPaneLayout -----------------------------------------
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 7d252e80..2d78caa9 100644
--- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
+++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties
@@ -587,6 +587,7 @@ SplitPaneDivider.gripGap = 2
TabbedPane.tabHeight = 32
TabbedPane.tabSelectionHeight = 3
+TabbedPane.cardTabSelectionHeight = 2
TabbedPane.contentSeparatorHeight = 1
TabbedPane.showTabSeparators = false
TabbedPane.tabSeparatorsFullHeight = false
@@ -608,6 +609,9 @@ TabbedPane.tabAlignment = center
# allowed values: preferred, equal or compact
TabbedPane.tabWidthMode = preferred
+# allowed values: underlined or card
+TabbedPane.tabType = underlined
+
# allowed values: chevron or triangle
TabbedPane.arrowType = chevron
TabbedPane.buttonInsets = 2,1,2,1
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.java
index 067a2cde..69331043 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.java
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.java
@@ -303,6 +303,11 @@ class TabsPanel
putTabbedPanesClientProperty( TABBED_PANE_SCROLL_BUTTONS_PLACEMENT, scrollButtonsPlacement );
}
+ private void tabTypeChanged() {
+ String tabType = cardTabTypeButton.isSelected() ? TABBED_PANE_TAB_TYPE_CARD : null;
+ putTabbedPanesClientProperty( TABBED_PANE_TAB_TYPE, tabType );
+ }
+
private void showTabSeparatorsChanged() {
Boolean showTabSeparators = showTabSeparatorsCheckBox.isSelected() ? true : null;
putTabbedPanesClientProperty( TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators );
@@ -396,11 +401,15 @@ class TabsPanel
scrollButtonsPlacementToolBar = new JToolBar();
scrollBothButton = new JToggleButton();
scrollTrailingButton = new JToggleButton();
+ showTabSeparatorsCheckBox = new JCheckBox();
tabsPopupPolicyLabel = new JLabel();
tabsPopupPolicyToolBar = new JToolBar();
popupAsNeededButton = new JToggleButton();
popupNeverButton = new JToggleButton();
- showTabSeparatorsCheckBox = new JCheckBox();
+ tabTypeLabel = new JLabel();
+ tabTypeToolBar = new JToolBar();
+ underlinedTabTypeButton = new JToggleButton();
+ cardTabTypeButton = new JToggleButton();
//======== this ========
setName("this");
@@ -873,7 +882,8 @@ class TabsPanel
"[]" +
"[fill]para" +
"[fill]" +
- "[fill]para",
+ "[fill]para" +
+ "[fill]",
// rows
"[]" +
"[center]"));
@@ -941,6 +951,12 @@ class TabsPanel
}
panel4.add(scrollButtonsPlacementToolBar, "cell 3 0");
+ //---- showTabSeparatorsCheckBox ----
+ showTabSeparatorsCheckBox.setText("Show tab separators");
+ showTabSeparatorsCheckBox.setName("showTabSeparatorsCheckBox");
+ showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged());
+ panel4.add(showTabSeparatorsCheckBox, "cell 4 0");
+
//---- tabsPopupPolicyLabel ----
tabsPopupPolicyLabel.setText("Tabs popup policy:");
tabsPopupPolicyLabel.setName("tabsPopupPolicyLabel");
@@ -969,11 +985,32 @@ class TabsPanel
}
panel4.add(tabsPopupPolicyToolBar, "cell 1 1");
- //---- showTabSeparatorsCheckBox ----
- showTabSeparatorsCheckBox.setText("Show tab separators");
- showTabSeparatorsCheckBox.setName("showTabSeparatorsCheckBox");
- showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged());
- panel4.add(showTabSeparatorsCheckBox, "cell 2 1 2 1");
+ //---- tabTypeLabel ----
+ tabTypeLabel.setText("Tab type:");
+ tabTypeLabel.setName("tabTypeLabel");
+ panel4.add(tabTypeLabel, "cell 2 1");
+
+ //======== tabTypeToolBar ========
+ {
+ tabTypeToolBar.setFloatable(false);
+ tabTypeToolBar.setName("tabTypeToolBar");
+
+ //---- underlinedTabTypeButton ----
+ underlinedTabTypeButton.setText("underlined");
+ underlinedTabTypeButton.setFont(underlinedTabTypeButton.getFont().deriveFont(underlinedTabTypeButton.getFont().getSize() - 2f));
+ underlinedTabTypeButton.setSelected(true);
+ underlinedTabTypeButton.setName("underlinedTabTypeButton");
+ underlinedTabTypeButton.addActionListener(e -> tabTypeChanged());
+ tabTypeToolBar.add(underlinedTabTypeButton);
+
+ //---- cardTabTypeButton ----
+ cardTabTypeButton.setText("card");
+ cardTabTypeButton.setFont(cardTabTypeButton.getFont().deriveFont(cardTabTypeButton.getFont().getSize() - 2f));
+ cardTabTypeButton.setName("cardTabTypeButton");
+ cardTabTypeButton.addActionListener(e -> tabTypeChanged());
+ tabTypeToolBar.add(cardTabTypeButton);
+ }
+ panel4.add(tabTypeToolBar, "cell 3 1");
}
add(panel4, "cell 0 2 3 1");
@@ -1010,6 +1047,11 @@ class TabsPanel
ButtonGroup tabsPopupPolicyButtonGroup = new ButtonGroup();
tabsPopupPolicyButtonGroup.add(popupAsNeededButton);
tabsPopupPolicyButtonGroup.add(popupNeverButton);
+
+ //---- tabTypeButtonGroup ----
+ ButtonGroup tabTypeButtonGroup = new ButtonGroup();
+ tabTypeButtonGroup.add(underlinedTabTypeButton);
+ tabTypeButtonGroup.add(cardTabTypeButton);
// JFormDesigner - End of component initialization //GEN-END:initComponents
if( FlatLafDemo.screenshotsMode ) {
@@ -1089,10 +1131,14 @@ class TabsPanel
private JToolBar scrollButtonsPlacementToolBar;
private JToggleButton scrollBothButton;
private JToggleButton scrollTrailingButton;
+ private JCheckBox showTabSeparatorsCheckBox;
private JLabel tabsPopupPolicyLabel;
private JToolBar tabsPopupPolicyToolBar;
private JToggleButton popupAsNeededButton;
private JToggleButton popupNeverButton;
- private JCheckBox showTabSeparatorsCheckBox;
+ private JLabel tabTypeLabel;
+ private JToolBar tabTypeToolBar;
+ private JToggleButton underlinedTabTypeButton;
+ private JToggleButton cardTabTypeButton;
// JFormDesigner - End of variables declaration //GEN-END:variables
}
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.jfd
index 3f634b86..2125677a 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.jfd
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/TabsPanel.jfd
@@ -457,7 +457,7 @@ new FormModel {
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
- "$columnConstraints": "[][fill]para[fill][fill]para"
+ "$columnConstraints": "[][fill]para[fill][fill]para[fill]"
"$rowConstraints": "[][center]"
} ) {
name: "panel4"
@@ -527,6 +527,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 0"
} )
+ add( new FormComponent( "javax.swing.JCheckBox" ) {
+ name: "showTabSeparatorsCheckBox"
+ "text": "Show tab separators"
+ auxiliary() {
+ "JavaCodeGenerator.variableLocal": false
+ }
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showTabSeparatorsChanged", false ) )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 4 0"
+ } )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "tabsPopupPolicyLabel"
"text": "Tabs popup policy:"
@@ -555,15 +565,32 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 1"
} )
- add( new FormComponent( "javax.swing.JCheckBox" ) {
- name: "showTabSeparatorsCheckBox"
- "text": "Show tab separators"
- auxiliary() {
- "JavaCodeGenerator.variableLocal": false
- }
- addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showTabSeparatorsChanged", false ) )
+ add( new FormComponent( "javax.swing.JLabel" ) {
+ name: "tabTypeLabel"
+ "text": "Tab type:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
- "value": "cell 2 1 2 1"
+ "value": "cell 2 1"
+ } )
+ add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) {
+ name: "tabTypeToolBar"
+ "floatable": false
+ add( new FormComponent( "javax.swing.JToggleButton" ) {
+ name: "underlinedTabTypeButton"
+ "text": "underlined"
+ "font": &SwingDerivedFont8 new com.jformdesigner.model.SwingDerivedFont( null, 0, -2, false )
+ "selected": true
+ "$buttonGroup": new FormReference( "tabTypeButtonGroup" )
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabTypeChanged", false ) )
+ } )
+ add( new FormComponent( "javax.swing.JToggleButton" ) {
+ name: "cardTabTypeButton"
+ "text": "card"
+ "font": #SwingDerivedFont8
+ "$buttonGroup": new FormReference( "tabTypeButtonGroup" )
+ addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabTypeChanged", false ) )
+ } )
+ }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
+ "value": "cell 3 1"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2 3 1"
@@ -602,5 +629,10 @@ new FormModel {
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 200, 1020 )
} )
+ add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
+ name: "tabTypeButtonGroup"
+ }, new FormLayoutConstraints( null ) {
+ "location": new java.awt.Point( 0, 1072 )
+ } )
}
}
diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java
index 22181f90..0b18bba2 100644
--- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java
+++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTabbedPane.java
@@ -363,6 +363,29 @@ public class FlatTabbedPane
}
+ // NOTE: enum names must be equal to allowed strings
+ /** @since 2 */ public enum TabType { underlined, card };
+
+ /**
+ * Returns type of selected tab.
+ *
+ * @since 2
+ */
+ public TabType getTabType() {
+ return getClientPropertyEnumString( TABBED_PANE_TAB_TYPE, TabType.class,
+ "TabbedPane.tabType", TabType.underlined );
+ }
+
+ /**
+ * Specifies type of selected tab.
+ *
+ * @since 2
+ */
+ public void setTabType( TabType tabType ) {
+ putClientPropertyEnumString( TABBED_PANE_TAB_TYPE, tabType );
+ }
+
+
// NOTE: enum names must be equal to allowed strings
public enum TabsPopupPolicy { never, asNeeded };
diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
index ebe78cbd..718a7c03 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt
@@ -1001,6 +1001,7 @@ TabbedPane.buttonArc 6
TabbedPane.buttonHoverBackground #303234 HSL 210 4 20 com.formdev.flatlaf.util.DerivedColor [UI] darken(5%)
TabbedPane.buttonInsets 2,1,2,1 javax.swing.plaf.InsetsUIResource [UI]
TabbedPane.buttonPressedBackground #282a2c HSL 210 5 16 com.formdev.flatlaf.util.DerivedColor [UI] darken(8%)
+TabbedPane.cardTabSelectionHeight 2
TabbedPane.closeArc 4
TabbedPane.closeCrossFilledSize 7.5
TabbedPane.closeCrossLineWidth 1
@@ -1043,6 +1044,7 @@ TabbedPane.tabInsets 4,12,4,12 javax.swing.plaf.InsetsUIResource [U
TabbedPane.tabRunOverlay 0
TabbedPane.tabSelectionHeight 3
TabbedPane.tabSeparatorsFullHeight false
+TabbedPane.tabType underlined
TabbedPane.tabWidthMode preferred
TabbedPane.tabsOpaque true
TabbedPane.tabsOverlapBorder false
diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
index ffe60224..d6055768 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt
@@ -1006,6 +1006,7 @@ TabbedPane.buttonArc 6
TabbedPane.buttonHoverBackground #e0e0e0 HSL 0 0 88 com.formdev.flatlaf.util.DerivedColor [UI] darken(7% autoInverse)
TabbedPane.buttonInsets 2,1,2,1 javax.swing.plaf.InsetsUIResource [UI]
TabbedPane.buttonPressedBackground #d9d9d9 HSL 0 0 85 com.formdev.flatlaf.util.DerivedColor [UI] darken(10% autoInverse)
+TabbedPane.cardTabSelectionHeight 2
TabbedPane.closeArc 4
TabbedPane.closeCrossFilledSize 7.5
TabbedPane.closeCrossLineWidth 1
@@ -1048,6 +1049,7 @@ TabbedPane.tabInsets 4,12,4,12 javax.swing.plaf.InsetsUIResource [U
TabbedPane.tabRunOverlay 0
TabbedPane.tabSelectionHeight 3
TabbedPane.tabSeparatorsFullHeight false
+TabbedPane.tabType underlined
TabbedPane.tabWidthMode preferred
TabbedPane.tabsOpaque true
TabbedPane.tabsOverlapBorder false
diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
index 6110addd..8aa2d85c 100644
--- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
+++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt
@@ -1017,6 +1017,7 @@ TabbedPane.buttonArc 6
TabbedPane.buttonHoverBackground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.buttonInsets 2,1,2,1 javax.swing.plaf.InsetsUIResource [UI]
TabbedPane.buttonPressedBackground #ffc800 HSL 47 100 50 javax.swing.plaf.ColorUIResource [UI]
+TabbedPane.cardTabSelectionHeight 2
TabbedPane.closeArc 999
TabbedPane.closeCrossFilledSize 6.5
TabbedPane.closeCrossLineWidth 2
@@ -1062,6 +1063,7 @@ TabbedPane.tabRunOverlay 0
TabbedPane.tabSelectionHeight 3
TabbedPane.tabSeparatorColor #0000ff HSL 240 100 50 javax.swing.plaf.ColorUIResource [UI]
TabbedPane.tabSeparatorsFullHeight false
+TabbedPane.tabType underlined
TabbedPane.tabWidthMode preferred
TabbedPane.tabsOpaque true
TabbedPane.tabsOverlapBorder false
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 d465a652..9fa10da9 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
@@ -50,6 +50,8 @@ public class FlatContainerTest
public FlatContainerTest() {
initComponents();
+ tabTypeComboBox.init( TabType.class, true );
+
tabPlacementField.init( TabPlacement.class, true );
iconPlacementField.init( TabIconPlacement.class, true );
tabsPopupPolicyField.init( TabsPopupPolicy.class, true );
@@ -310,6 +312,12 @@ public class FlatContainerTest
tabbedPane.setTabWidthMode( value );
}
+ private void tabTypeChanged() {
+ TabType value = tabTypeComboBox.getSelectedValue();
+ for( FlatTabbedPane tabbedPane : allTabbedPanes )
+ tabbedPane.setTabType( value );
+ }
+
private void tabBackForegroundChanged() {
for( JTabbedPane tabbedPane : allTabbedPanes )
tabBackForegroundChanged( tabbedPane );
@@ -491,6 +499,8 @@ public class FlatContainerTest
tabAlignmentField = new FlatTestEnumComboBox<>();
JLabel tabWidthModeLabel = new JLabel();
tabWidthModeField = new FlatTestEnumComboBox<>();
+ JLabel tabTypeLabel = new JLabel();
+ tabTypeComboBox = new FlatTestEnumComboBox<>();
leadingComponentCheckBox = new JCheckBox();
customBorderCheckBox = new JCheckBox();
tabAreaInsetsCheckBox = new JCheckBox();
@@ -619,6 +629,7 @@ public class FlatContainerTest
"[]" +
"[]" +
"[]" +
+ "[]" +
"[]para" +
"[]" +
"[]para" +
@@ -739,75 +750,83 @@ public class FlatContainerTest
tabWidthModeField.addActionListener(e -> tabWidthModeChanged());
tabbedPaneControlPanel.add(tabWidthModeField, "cell 2 5");
+ //---- tabTypeLabel ----
+ tabTypeLabel.setText("Tab type:");
+ tabbedPaneControlPanel.add(tabTypeLabel, "cell 0 6");
+
+ //---- tabTypeComboBox ----
+ tabTypeComboBox.addActionListener(e -> tabTypeChanged());
+ tabbedPaneControlPanel.add(tabTypeComboBox, "cell 1 6");
+
//---- leadingComponentCheckBox ----
leadingComponentCheckBox.setText("Leading component");
leadingComponentCheckBox.addActionListener(e -> leadingComponentChanged());
- tabbedPaneControlPanel.add(leadingComponentCheckBox, "cell 0 6");
+ tabbedPaneControlPanel.add(leadingComponentCheckBox, "cell 0 7");
//---- customBorderCheckBox ----
customBorderCheckBox.setText("Custom border");
customBorderCheckBox.addActionListener(e -> customBorderChanged());
- tabbedPaneControlPanel.add(customBorderCheckBox, "cell 1 6");
+ tabbedPaneControlPanel.add(customBorderCheckBox, "cell 1 7");
//---- tabAreaInsetsCheckBox ----
tabAreaInsetsCheckBox.setText("Tab area insets (5,5,10,10)");
tabAreaInsetsCheckBox.addActionListener(e -> tabAreaInsetsChanged());
- tabbedPaneControlPanel.add(tabAreaInsetsCheckBox, "cell 2 6");
+ tabbedPaneControlPanel.add(tabAreaInsetsCheckBox, "cell 2 7");
//---- trailingComponentCheckBox ----
trailingComponentCheckBox.setText("Trailing component");
trailingComponentCheckBox.addActionListener(e -> trailingComponentChanged());
- tabbedPaneControlPanel.add(trailingComponentCheckBox, "cell 0 7");
+ tabbedPaneControlPanel.add(trailingComponentCheckBox, "cell 0 8");
//---- hasFullBorderCheckBox ----
hasFullBorderCheckBox.setText("Show content border");
hasFullBorderCheckBox.addActionListener(e -> hasFullBorderChanged());
- tabbedPaneControlPanel.add(hasFullBorderCheckBox, "cell 1 7,alignx left,growx 0");
+ tabbedPaneControlPanel.add(hasFullBorderCheckBox, "cell 1 8,alignx left,growx 0");
//---- smallerTabHeightCheckBox ----
smallerTabHeightCheckBox.setText("Smaller tab height (26)");
smallerTabHeightCheckBox.addActionListener(e -> smallerTabHeightChanged());
- tabbedPaneControlPanel.add(smallerTabHeightCheckBox, "cell 2 7");
+ tabbedPaneControlPanel.add(smallerTabHeightCheckBox, "cell 2 8");
//---- minimumTabWidthCheckBox ----
minimumTabWidthCheckBox.setText("Minimum tab width (100)");
minimumTabWidthCheckBox.addActionListener(e -> minimumTabWidthChanged());
- tabbedPaneControlPanel.add(minimumTabWidthCheckBox, "cell 0 8");
+ tabbedPaneControlPanel.add(minimumTabWidthCheckBox, "cell 0 9");
//---- hideContentSeparatorCheckBox ----
hideContentSeparatorCheckBox.setText("Hide content separator");
hideContentSeparatorCheckBox.addActionListener(e -> hideContentSeparatorChanged());
- tabbedPaneControlPanel.add(hideContentSeparatorCheckBox, "cell 1 8");
+ tabbedPaneControlPanel.add(hideContentSeparatorCheckBox, "cell 1 9");
//---- smallerInsetsCheckBox ----
smallerInsetsCheckBox.setText("Smaller tab insets (2,2,2,2)");
smallerInsetsCheckBox.addActionListener(e -> smallerInsetsChanged());
- tabbedPaneControlPanel.add(smallerInsetsCheckBox, "cell 2 8");
+ tabbedPaneControlPanel.add(smallerInsetsCheckBox, "cell 2 9");
//---- maximumTabWidthCheckBox ----
maximumTabWidthCheckBox.setText("Maximum tab width (60)");
maximumTabWidthCheckBox.addActionListener(e -> maximumTabWidthChanged());
- tabbedPaneControlPanel.add(maximumTabWidthCheckBox, "cell 0 9");
+ tabbedPaneControlPanel.add(maximumTabWidthCheckBox, "cell 0 10");
//---- showTabSeparatorsCheckBox ----
showTabSeparatorsCheckBox.setText("Show tab separators");
showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged());
- tabbedPaneControlPanel.add(showTabSeparatorsCheckBox, "cell 1 9");
+ tabbedPaneControlPanel.add(showTabSeparatorsCheckBox, "cell 1 10");
//---- secondTabWiderCheckBox ----
secondTabWiderCheckBox.setText("Second Tab insets wider (4,20,4,20)");
secondTabWiderCheckBox.addActionListener(e -> secondTabWiderChanged());
- tabbedPaneControlPanel.add(secondTabWiderCheckBox, "cell 2 9");
+ tabbedPaneControlPanel.add(secondTabWiderCheckBox, "cell 2 10");
//---- hideTabAreaWithOneTabCheckBox ----
hideTabAreaWithOneTabCheckBox.setText("Hide tab area with one tab");
hideTabAreaWithOneTabCheckBox.addActionListener(e -> hideTabAreaWithOneTabChanged());
- tabbedPaneControlPanel.add(hideTabAreaWithOneTabCheckBox, "cell 1 10");
+ tabbedPaneControlPanel.add(hideTabAreaWithOneTabCheckBox, "cell 1 11");
//---- customWheelScrollingCheckBox ----
customWheelScrollingCheckBox.setText("Custom wheel scrolling");
customWheelScrollingCheckBox.addActionListener(e -> customWheelScrollingChanged());
- tabbedPaneControlPanel.add(customWheelScrollingCheckBox, "cell 2 10");
+ tabbedPaneControlPanel.add(customWheelScrollingCheckBox, "cell 2 11");
}
panel9.add(tabbedPaneControlPanel, cc.xywh(1, 11, 3, 1));
}
@@ -840,6 +859,7 @@ public class FlatContainerTest
private FlatTestEnumComboBox