From 4c13271a5b0d660c800b39f3076efb8c35faef9e Mon Sep 17 00:00:00 2001 From: DUDSS Date: Sun, 20 Jun 2021 02:13:54 +0200 Subject: [PATCH 1/7] TabbedPane: Added activeTabBorder option to tabbed panes. Changes the active tab design to more closely resemble classic tabbed pane designs. --- .../formdev/flatlaf/FlatClientProperties.java | 11 +- .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 255 ++++++++++++++++-- .../com/formdev/flatlaf/FlatLaf.properties | 1 + 3 files changed, 238 insertions(+), 29 deletions(-) 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 ff6a5aa5..de15ec2d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -14,8 +14,8 @@ * limitations under the License. */ -package com.formdev.flatlaf; +package com.formdev.flatlaf; import java.awt.Color; import java.util.Objects; import javax.swing.JComponent; @@ -332,6 +332,15 @@ public interface FlatClientProperties */ String TABBED_PANE_SHOW_TAB_SEPARATORS = "JTabbedPane.showTabSeparators"; + /** + * Specifies whether a border is painted around the active tab. + * This also changes position of the active tab indicator. + *

+ * Component {@link javax.swing.JTabbedPane}
+ * Value type {@link java.lang.Boolean} + */ + String TABBED_PANE_ACTIVE_TAB_BORDER = "JTabbedPane.activeTabBorder"; + /** * Specifies whether the separator between tabs area and content area should be shown. *

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 c4f79435..c3a99e9f 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; @@ -47,6 +49,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; +import java.awt.geom.Area; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; @@ -197,6 +200,7 @@ public class FlatTabbedPaneUI protected boolean tabSeparatorsFullHeight; protected boolean hasFullBorder; protected boolean tabsOpaque = true; + protected boolean activeTabBorder; private int tabsPopupPolicy; private int scrollButtonsPolicy; @@ -230,6 +234,8 @@ public class FlatTabbedPaneUI private boolean rolloverTabClose; private boolean pressedTabClose; + private FocusListener contentBorderFocusListener; + private Object[] oldRenderingHints; public static ComponentUI createUI( JComponent c ) { @@ -304,6 +310,7 @@ public class FlatTabbedPaneUI tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" ); hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" ); + activeTabBorder = UIManager.getBoolean( "TabbedPane.activeTabBorder" ); tabsPopupPolicy = parseTabsPopupPolicy( UIManager.getString( "TabbedPane.tabsPopupPolicy" ) ); scrollButtonsPolicy = parseScrollButtonsPolicy( UIManager.getString( "TabbedPane.scrollButtonsPolicy" ) ); @@ -471,6 +478,23 @@ public class FlatTabbedPaneUI tabPane.addMouseMotionListener( wheelTabScroller ); tabPane.addMouseListener( wheelTabScroller ); } + + // Fix for content border repainting issue (when activeTabBorder is on) //TODO: Find a better solution perhaps + /* Basically the content separator paints part of itself with the color of the active tab. + * When the active tab has focus, and focus is lost to a different component (outside the tabbed pane) + * the content separator does not get repainted, but the active tab background does. I don't know why. + */ + contentBorderFocusListener = new FocusListener() + { + @Override + public void focusGained( FocusEvent e ) {} + + @Override + public void focusLost( FocusEvent e ) { + repaintContentBorder(); + } + }; + tabPane.addFocusListener( contentBorderFocusListener ); } @Override @@ -490,6 +514,8 @@ public class FlatTabbedPaneUI tabPane.removeMouseListener( wheelTabScroller ); wheelTabScroller = null; } + + tabPane.removeFocusListener( contentBorderFocusListener ); } @Override @@ -602,6 +628,35 @@ public class FlatTabbedPaneUI repaintTab( getRolloverTab() ); } + /** + * Repaints parts of the content border if necessary. + * @see com.formdev.flatlaf.FlatClientProperties#TABBED_PANE_ACTIVE_TAB_BORDER + */ + private void repaintContentBorder() { + if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && + clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true )) + { + Rectangle bounds = getContentBorderBounds(tabPane.getTabPlacement()); + Rectangle r; + switch (tabPane.getTabPlacement()) { + default: + case TOP: + r = new Rectangle( bounds.x, bounds.y, bounds.width, contentSeparatorHeight); + break; + case BOTTOM: + r = new Rectangle( bounds.x, bounds.y + bounds.height - contentSeparatorHeight, bounds.width, contentSeparatorHeight); + break; + case LEFT: + r = new Rectangle( bounds.x, bounds.y, contentSeparatorHeight, bounds.height); + break; + case RIGHT: + r = new Rectangle( bounds.x + bounds.width - contentSeparatorHeight, bounds.y, contentSeparatorHeight, bounds.height); + break; + } + tabPane.repaint(r); + } + } + private void repaintTab( int tabIndex ) { if( tabIndex < 0 || tabIndex >= tabPane.getTabCount() ) return; @@ -609,6 +664,8 @@ public class FlatTabbedPaneUI Rectangle r = getTabBounds( tabPane, tabIndex ); if( r != null ) tabPane.repaint( r ); + + repaintContentBorder(); } private boolean inCalculateEqual; @@ -825,7 +882,7 @@ public class FlatTabbedPaneUI FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); oldRenderingHints = null; } - + static int cnt = 0; @Override public void paint( Graphics g, JComponent c ) { if( hideTabArea() ) @@ -934,6 +991,12 @@ public class FlatTabbedPaneUI int x, int y, int w, int h, boolean isSelected ) { // paint tab background + Color background = determineTabBackgroundColor(tabPlacement, tabIndex, isSelected); + g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) ); + g.fillRect( x, y, w, h ); + } + + protected Color determineTabBackgroundColor(int tabPlacement, int tabIndex, boolean isSelected) { boolean enabled = tabPane.isEnabled(); Color background = enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex ? hoverColor @@ -942,8 +1005,7 @@ public class FlatTabbedPaneUI : (selectedBackground != null && enabled && isSelected ? selectedBackground : tabPane.getBackgroundAt( tabIndex ))); - g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) ); - g.fillRect( x, y, w, h ); + return background; } @Override @@ -953,7 +1015,31 @@ public class FlatTabbedPaneUI // paint tab separators if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) && !isLastInRun( tabIndex ) ) - paintTabSeparator( g, tabPlacement, x, y, w, h ); + paintTabSeparator( g, tabPlacement, x, y, w, h ); + + // paint tab border + if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && isSelected) { + paintActiveTabBorder(g, tabPlacement, tabIndex, x, y, w, h); + } + } + + protected void paintActiveTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h) { + float borderWidth = UIScale.scale( 1f ); + g.setColor( (tabSeparatorColor != null) ? tabSeparatorColor : contentAreaColor ); + + switch( tabPlacement ) { + default: + case TOP: + case BOTTOM: + ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, borderWidth, h) ); + ((Graphics2D)g).fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h) ); + break; + case LEFT: + case RIGHT: + ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, w, borderWidth) ); + ((Graphics2D)g).fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth) ); + break; + } } protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) { @@ -1004,40 +1090,44 @@ public class FlatTabbedPaneUI switch( tabPlacement ) { case TOP: default: - int sy = y + h + contentInsets.top - tabSelectionHeight; + int sy; + if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) + sy = y; + else + sy = y + h + contentInsets.top - tabSelectionHeight; + g.fillRect( x, sy, w, tabSelectionHeight ); break; case BOTTOM: - g.fillRect( x, y - contentInsets.bottom, w, tabSelectionHeight ); + if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { + g.fillRect( x, y + h + contentInsets.top - tabSelectionHeight, w, tabSelectionHeight ); + } else { + g.fillRect( x, y - contentInsets.bottom, w, tabSelectionHeight ); + } break; case LEFT: - int sx = x + w + contentInsets.left - tabSelectionHeight; + int sx; + if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { + sx = x; + } else { + sx = x + w + contentInsets.left - tabSelectionHeight; + } g.fillRect( sx, y, tabSelectionHeight, h ); break; case RIGHT: - g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h ); + if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { + g.fillRect( x + w + contentInsets.left - tabSelectionHeight, y, tabSelectionHeight, h ); + } else { + g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h ); + } break; } } - /** - * Actually does nearly the same as super.paintContentBorder() but - * - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly - * - tabsOverlapBorder is always true - * - paint full border (if enabled) - * - not invoking paintContentBorder*Edge() methods - * - repaint selection - */ - @Override - protected void paintContentBorder( Graphics g, int tabPlacement, int selectedIndex ) { - if( tabPane.getTabCount() <= 0 || - contentSeparatorHeight == 0 || - !clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) ) - return; - + protected Rectangle getContentBorderBounds(int tabPlacement) { Insets insets = tabPane.getInsets(); Insets tabAreaInsets = getTabAreaInsets( tabPlacement ); @@ -1072,6 +1162,31 @@ public class FlatTabbedPaneUI break; } + return new Rectangle(x, y, w, h); + } + + /** + * Actually does nearly the same as super.paintContentBorder() but + * - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly + * - tabsOverlapBorder is always true + * - 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 ) { + if( tabPane.getTabCount() <= 0 || + contentSeparatorHeight == 0 || + !clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) ) + return; + + Rectangle contentBorderBounds = getContentBorderBounds(tabPlacement); + int x = contentBorderBounds.x; + int y = contentBorderBounds.y; + int w = contentBorderBounds.width; + int h = contentBorderBounds.height; + // compute insets for separator or full border boolean hasFullBorder = clientPropertyBoolean( tabPane, TABBED_PANE_HAS_FULL_BORDER, this.hasFullBorder ); int sh = scale( contentSeparatorHeight * 100 ); // multiply by 100 because rotateInsets() does not use floats @@ -1079,12 +1194,95 @@ public class FlatTabbedPaneUI rotateInsets( hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ), ci, tabPlacement ); // paint content separator or full border + Area area = new Area(); + + if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) { + // Active tab border style + int ax = rects[selectedIndex].x; + int ay = rects[selectedIndex].y; + int aw = rects[selectedIndex].width; + int ah = rects[selectedIndex].height; + + int scrollOffsetX = 0; + int scrollOffsetY = 0; + int tabViewportOffsetX = 0; + int tabViewportOffsetY = 0; + if (tabViewport != null && isScrollTabLayout()) { + scrollOffsetX = tabViewport.getViewRect().x; + scrollOffsetY = tabViewport.getViewRect().y; + tabViewportOffsetX = tabViewport.getX(); + tabViewportOffsetY = tabViewport.getY(); + } + + int gapX; + int gapY; + int gapW; + int gapH; + switch( tabPlacement ) { + default: + case TOP: + gapX = x + ax + 1 + (-scrollOffsetX + tabViewportOffsetX); + gapY = y; + gapW = aw - 2; + gapH = contentSeparatorHeight; + break; + case BOTTOM: + gapX = x + ax + 1 + (-scrollOffsetX + tabViewportOffsetX); + gapY = h - contentSeparatorHeight; + gapW = aw - 2; + gapH = contentSeparatorHeight; + break; + case LEFT: + gapX = x; + gapY = y + ay + 1 + (-scrollOffsetY + tabViewportOffsetY); + gapW = contentSeparatorHeight; + gapH = ah - 2; + break; + case RIGHT: + gapX = w - contentSeparatorHeight; + gapY = y + ay + 1 + (-scrollOffsetY + tabViewportOffsetY); + gapW = contentSeparatorHeight; + gapH = ah - 2; + break; + } + + area.add( new Area( new Rectangle2D.Float( x, y, w, h ) ) ); + area.subtract( new Area( new Rectangle2D.Float( gapX, gapY, gapW, gapH) ) ); + + Color activeTabAreaBackground = determineTabBackgroundColor(tabPlacement, selectedIndex, true); + g.setColor( activeTabAreaBackground ); + ((Graphics2D)g).fill( new Rectangle2D.Float( gapX, gapY, gapW, gapH) ); + + // 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 && isScrollTabLayout()) { + switch( tabPlacement ) { + default: + case TOP: + case BOTTOM: + area.add( new Area( new Rectangle2D.Float( x, y, tabViewport.getX() - x, h ) ) ); + area.add( new Area( new Rectangle2D.Float( tabViewport.getX() + tabViewport.getWidth(), y, w - tabViewport.getX() - x, h ) ) ); + break; + case LEFT: + case RIGHT: + area.add( new Area( new Rectangle2D.Float( x, y, w, tabViewport.getY() - y ) ) ); + area.add( new Area( new Rectangle2D.Float( x, tabViewport.getY() + tabViewport.getHeight(), w, h - tabViewport.getY() - y ) ) ); + break; + } + } + +// ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, tabViewport.getX() - x, contentSeparatorHeight) ); + + } else { + area.add( new Area( new Rectangle2D.Float(x, y, w, h) ) ); + } + + Rectangle2D.Float r = 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) ); + area.subtract( new Area( r ) ); + g.setColor( contentAreaColor ); - 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 ); - ((Graphics2D)g).fill( path ); + ((Graphics2D) g).fill( area ); // repaint selection in scroll-tab-layout because it may be painted before // the content border was painted (from BasicTabbedPaneUI$ScrollableTabPanel) @@ -2303,6 +2501,7 @@ public class FlatTabbedPaneUI break; case TABBED_PANE_SHOW_TAB_SEPARATORS: + case TABBED_PANE_ACTIVE_TAB_BORDER: case TABBED_PANE_SHOW_CONTENT_SEPARATOR: case TABBED_PANE_HAS_FULL_BORDER: case TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB: 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 52e8bff9..6de2de23 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -571,6 +571,7 @@ TabbedPane.tabHeight = 32 TabbedPane.tabSelectionHeight = 3 TabbedPane.contentSeparatorHeight = 1 TabbedPane.showTabSeparators = false +TabbedPane.activeTabBorder = false TabbedPane.tabSeparatorsFullHeight = false TabbedPane.hasFullBorder = false TabbedPane.tabInsets = 4,12,4,12 From d3c917eac17a6c8feaa74ec8fd288b4b887f6480 Mon Sep 17 00:00:00 2001 From: DUDSS Date: Sun, 20 Jun 2021 02:14:27 +0200 Subject: [PATCH 2/7] TabbedPane: Added the new style option to the flatlaf demo. --- .../com/formdev/flatlaf/demo/FlatLafDemo.java | 4 +++- .../com/formdev/flatlaf/demo/TabsPanel.java | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java index b6e7c186..48394158 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java @@ -17,11 +17,13 @@ package com.formdev.flatlaf.demo; import java.awt.Dimension; -import javax.swing.SwingUtilities; +import javax.swing.*; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.extras.FlatInspector; import com.formdev.flatlaf.extras.FlatUIDefaultsInspector; import com.formdev.flatlaf.util.SystemInfo; +import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_ACTIVE_TAB_BORDER; +import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_HAS_FULL_BORDER; /** * @author Karl Tauber 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..2dec7342 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 @@ -308,6 +308,12 @@ class TabsPanel putTabbedPanesClientProperty( TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ); } + private void activeTabBorderChanged() { + Boolean activeBorderTab = activeTabBorderCheckBox.isSelected() ? true : false; + System.out.println(TABBED_PANE_ACTIVE_TAB_BORDER + ": " + activeBorderTab); + putTabbedPanesClientProperty( TABBED_PANE_ACTIVE_TAB_BORDER, activeBorderTab ); + } + private void putTabbedPanesClientProperty( String key, Object value ) { updateTabbedPanesRecur( this, tabbedPane -> tabbedPane.putClientProperty( key, value ) ); } @@ -401,6 +407,7 @@ class TabsPanel popupAsNeededButton = new JToggleButton(); popupNeverButton = new JToggleButton(); showTabSeparatorsCheckBox = new JCheckBox(); + activeTabBorderCheckBox = new JCheckBox(); //======== this ======== setName("this"); @@ -495,7 +502,7 @@ class TabsPanel { tabPlacementTabbedPane.setName("tabPlacementTabbedPane"); } - panel1.add(tabPlacementTabbedPane, "cell 0 1,width 300:300,height 100:100"); + panel1.add(tabPlacementTabbedPane, "cell 0 1, width 300:300, height 100:100"); //---- tabLayoutLabel ---- tabLayoutLabel.setText("Tab layout"); @@ -973,7 +980,13 @@ class TabsPanel showTabSeparatorsCheckBox.setText("Show tab separators"); showTabSeparatorsCheckBox.setName("showTabSeparatorsCheckBox"); showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged()); - panel4.add(showTabSeparatorsCheckBox, "cell 2 1 2 1"); + panel4.add(showTabSeparatorsCheckBox, "cell 2 1"); + + //---- activeTabBorderCheckBox ---- + activeTabBorderCheckBox.setText("Active tab border"); + activeTabBorderCheckBox.setName("activeTabBorderCheckBox"); + activeTabBorderCheckBox.addActionListener(e -> activeTabBorderChanged()); + panel4.add(activeTabBorderCheckBox, "cell 3 1"); } add(panel4, "cell 0 2 3 1"); @@ -1094,5 +1107,6 @@ class TabsPanel private JToggleButton popupAsNeededButton; private JToggleButton popupNeverButton; private JCheckBox showTabSeparatorsCheckBox; + private JCheckBox activeTabBorderCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables } From 04bb6a5275f82b3aeae4d6b761ebc99903340312 Mon Sep 17 00:00:00 2001 From: DUDSS Date: Sun, 20 Jun 2021 02:10:51 +0200 Subject: [PATCH 3/7] TabbedPane: Adjustements --- .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) 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 c3a99e9f..37010b1e 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 @@ -98,6 +98,7 @@ import com.formdev.flatlaf.util.UIScale; * * @clientProperty JTabbedPane.showTabSeparators boolean * @clientProperty JTabbedPane.hasFullBorder boolean + * @clientProperty JTabbedPane.activeTabBorder boolean * * * @@ -135,6 +136,7 @@ import com.formdev.flatlaf.util.UIScale; * @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.tabsPopupPolicy String never or asNeeded (default) @@ -1014,10 +1016,19 @@ public class FlatTabbedPaneUI { // paint tab separators if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) && - !isLastInRun( tabIndex ) ) - paintTabSeparator( g, tabPlacement, x, y, w, h ); + !isLastInRun( tabIndex ) ) { + if( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) { + // Some separators need to be omitted when activeTabBorder is enabled + 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 tab border + // paint active tab border if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && isSelected) { paintActiveTabBorder(g, tabPlacement, tabIndex, x, y, w, h); } @@ -1101,7 +1112,7 @@ public class FlatTabbedPaneUI case BOTTOM: if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { - g.fillRect( x, y + h + contentInsets.top - tabSelectionHeight, w, tabSelectionHeight ); + g.fillRect( x, y + h - tabSelectionHeight, w, tabSelectionHeight ); } else { g.fillRect( x, y - contentInsets.bottom, w, tabSelectionHeight ); } @@ -1119,7 +1130,7 @@ public class FlatTabbedPaneUI case RIGHT: if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { - g.fillRect( x + w + contentInsets.left - tabSelectionHeight, y, tabSelectionHeight, h ); + g.fillRect( x + w - tabSelectionHeight, y, tabSelectionHeight, h ); } else { g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h ); } From ad0a13004e8019c2d4c6b302f9f4dfecd1c288c9 Mon Sep 17 00:00:00 2001 From: DUDSS Date: Sun, 20 Jun 2021 02:56:19 +0200 Subject: [PATCH 4/7] TabbedPane: Changed name in demo and added separator repaint on focus gained. --- .../main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java | 4 +++- .../src/main/java/com/formdev/flatlaf/demo/TabsPanel.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) 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 37010b1e..f66005aa 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 @@ -489,7 +489,9 @@ public class FlatTabbedPaneUI contentBorderFocusListener = new FocusListener() { @Override - public void focusGained( FocusEvent e ) {} + public void focusGained( FocusEvent e ) { + repaintContentBorder(); + } @Override public void focusLost( FocusEvent e ) { 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 2dec7342..42cb124a 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 @@ -983,7 +983,7 @@ class TabsPanel panel4.add(showTabSeparatorsCheckBox, "cell 2 1"); //---- activeTabBorderCheckBox ---- - activeTabBorderCheckBox.setText("Active tab border"); + activeTabBorderCheckBox.setText("Paint border around active tab"); activeTabBorderCheckBox.setName("activeTabBorderCheckBox"); activeTabBorderCheckBox.addActionListener(e -> activeTabBorderChanged()); panel4.add(activeTabBorderCheckBox, "cell 3 1"); From 943e211cf1674e92973db12f5a98c04281197862 Mon Sep 17 00:00:00 2001 From: DUDSS Date: Sun, 20 Jun 2021 14:01:01 +0200 Subject: [PATCH 5/7] TabbedPane: ActiveTabBorder: Added handling for thick content separators and 0 height selection indicator. Fixed scaling issues (presumably). --- .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) 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 f66005aa..4b14026c 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 @@ -640,21 +640,22 @@ public class FlatTabbedPaneUI if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true )) { + int csh = scale(contentSeparatorHeight); Rectangle bounds = getContentBorderBounds(tabPane.getTabPlacement()); Rectangle r; switch (tabPane.getTabPlacement()) { default: case TOP: - r = new Rectangle( bounds.x, bounds.y, bounds.width, contentSeparatorHeight); + r = new Rectangle( bounds.x, bounds.y, bounds.width, csh); break; case BOTTOM: - r = new Rectangle( bounds.x, bounds.y + bounds.height - contentSeparatorHeight, bounds.width, contentSeparatorHeight); + r = new Rectangle( bounds.x, bounds.y + bounds.height - csh, bounds.width, csh); break; case LEFT: - r = new Rectangle( bounds.x, bounds.y, contentSeparatorHeight, bounds.height); + r = new Rectangle( bounds.x, bounds.y, csh, bounds.height); break; case RIGHT: - r = new Rectangle( bounds.x + bounds.width - contentSeparatorHeight, bounds.y, contentSeparatorHeight, bounds.height); + r = new Rectangle( bounds.x + bounds.width - csh, bounds.y, csh, bounds.height); break; } tabPane.repaint(r); @@ -1037,7 +1038,7 @@ public class FlatTabbedPaneUI } protected void paintActiveTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h) { - float borderWidth = UIScale.scale( 1f ); + float borderWidth = scale(contentSeparatorHeight); g.setColor( (tabSeparatorColor != null) ? tabSeparatorColor : contentAreaColor ); switch( tabPlacement ) { @@ -1053,6 +1054,25 @@ public class FlatTabbedPaneUI ((Graphics2D)g).fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth) ); break; } + + if (tabSelectionHeight <= 0) { + //If there is no tab selection indicator, paint a top border as well + switch( tabPlacement ) { + default: + case TOP: + ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, w, borderWidth) ); + break; + case BOTTOM: + ((Graphics2D)g).fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth) ); + break; + case LEFT: + ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, borderWidth, h) ); + break; + case RIGHT: + ((Graphics2D)g).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 ) { @@ -1200,6 +1220,8 @@ public class FlatTabbedPaneUI int w = contentBorderBounds.width; int h = contentBorderBounds.height; + int csh = scale( contentSeparatorHeight ); + // compute insets for separator or full border boolean hasFullBorder = clientPropertyBoolean( tabPane, TABBED_PANE_HAS_FULL_BORDER, this.hasFullBorder ); int sh = scale( contentSeparatorHeight * 100 ); // multiply by 100 because rotateInsets() does not use floats @@ -1234,28 +1256,28 @@ public class FlatTabbedPaneUI switch( tabPlacement ) { default: case TOP: - gapX = x + ax + 1 + (-scrollOffsetX + tabViewportOffsetX); + gapX = x + ax + csh + (-scrollOffsetX + tabViewportOffsetX); gapY = y; - gapW = aw - 2; - gapH = contentSeparatorHeight; + gapW = aw - csh*2; + gapH = csh; break; case BOTTOM: - gapX = x + ax + 1 + (-scrollOffsetX + tabViewportOffsetX); - gapY = h - contentSeparatorHeight; - gapW = aw - 2; - gapH = contentSeparatorHeight; + gapX = x + ax + csh + (-scrollOffsetX + tabViewportOffsetX); + gapY = h - csh; + gapW = aw - csh*2; + gapH = csh; break; case LEFT: gapX = x; - gapY = y + ay + 1 + (-scrollOffsetY + tabViewportOffsetY); - gapW = contentSeparatorHeight; - gapH = ah - 2; + gapY = y + ay + csh + (-scrollOffsetY + tabViewportOffsetY); + gapW = csh; + gapH = ah - csh*2; break; case RIGHT: - gapX = w - contentSeparatorHeight; - gapY = y + ay + 1 + (-scrollOffsetY + tabViewportOffsetY); - gapW = contentSeparatorHeight; - gapH = ah - 2; + gapX = w - csh; + gapY = y + ay + csh + (-scrollOffsetY + tabViewportOffsetY); + gapW = csh; + gapH = ah - csh*2; break; } @@ -1283,9 +1305,6 @@ public class FlatTabbedPaneUI break; } } - -// ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, tabViewport.getX() - x, contentSeparatorHeight) ); - } else { area.add( new Area( new Rectangle2D.Float(x, y, w, h) ) ); } From 435cf05f9fc14ad6e7addf74798dfd88d0190739 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 12 Jul 2021 13:37:24 +0200 Subject: [PATCH 6/7] TabbedPane: reviewed PR #343 (active tab border painting style) - moved focus listener to class `Handler` - instead of repainting content border in `repaintContentBorder()`, increase size of repaint region in `repaintTab()` to include part of content border - simplified `paintContentBorder()` by using `getTabBounds()` and `Rectangle2D.intersect()` to compute gap rectangle - slightly make code smaller - minor formatting, renaming, ... --- .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 347 +++++++----------- .../com/formdev/flatlaf/demo/FlatLafDemo.java | 4 +- 2 files changed, 139 insertions(+), 212 deletions(-) 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 4b14026c..23d20688 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 @@ -49,7 +49,6 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; -import java.awt.geom.Area; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; @@ -236,8 +235,6 @@ public class FlatTabbedPaneUI private boolean rolloverTabClose; private boolean pressedTabClose; - private FocusListener contentBorderFocusListener; - private Object[] oldRenderingHints; public static ComponentUI createUI( JComponent c ) { @@ -480,25 +477,6 @@ public class FlatTabbedPaneUI tabPane.addMouseMotionListener( wheelTabScroller ); tabPane.addMouseListener( wheelTabScroller ); } - - // Fix for content border repainting issue (when activeTabBorder is on) //TODO: Find a better solution perhaps - /* Basically the content separator paints part of itself with the color of the active tab. - * When the active tab has focus, and focus is lost to a different component (outside the tabbed pane) - * the content separator does not get repainted, but the active tab background does. I don't know why. - */ - contentBorderFocusListener = new FocusListener() - { - @Override - public void focusGained( FocusEvent e ) { - repaintContentBorder(); - } - - @Override - public void focusLost( FocusEvent e ) { - repaintContentBorder(); - } - }; - tabPane.addFocusListener( contentBorderFocusListener ); } @Override @@ -518,8 +496,6 @@ public class FlatTabbedPaneUI tabPane.removeMouseListener( wheelTabScroller ); wheelTabScroller = null; } - - tabPane.removeFocusListener( contentBorderFocusListener ); } @Override @@ -567,6 +543,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 ) @@ -632,45 +615,30 @@ public class FlatTabbedPaneUI repaintTab( getRolloverTab() ); } - /** - * Repaints parts of the content border if necessary. - * @see com.formdev.flatlaf.FlatClientProperties#TABBED_PANE_ACTIVE_TAB_BORDER - */ - private void repaintContentBorder() { - if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && - clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true )) - { - int csh = scale(contentSeparatorHeight); - Rectangle bounds = getContentBorderBounds(tabPane.getTabPlacement()); - Rectangle r; - switch (tabPane.getTabPlacement()) { - default: - case TOP: - r = new Rectangle( bounds.x, bounds.y, bounds.width, csh); - break; - case BOTTOM: - r = new Rectangle( bounds.x, bounds.y + bounds.height - csh, bounds.width, csh); - break; - case LEFT: - r = new Rectangle( bounds.x, bounds.y, csh, bounds.height); - break; - case RIGHT: - r = new Rectangle( bounds.x + bounds.width - csh, bounds.y, csh, bounds.height); - break; - } - tabPane.repaint(r); - } - } - private void repaintTab( int tabIndex ) { if( tabIndex < 0 || tabIndex >= tabPane.getTabCount() ) return; Rectangle r = getTabBounds( tabPane, tabIndex ); - if( r != null ) - tabPane.repaint( r ); + if( r == null ) + return; - repaintContentBorder(); + // increase size of repaint region to include part of content border + if( contentSeparatorHeight > 0 && + clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && + 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; @@ -887,7 +855,7 @@ public class FlatTabbedPaneUI FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); oldRenderingHints = null; } - static int cnt = 0; + @Override public void paint( Graphics g, JComponent c ) { if( hideTabArea() ) @@ -904,6 +872,17 @@ public class FlatTabbedPaneUI paintTabArea( g, tabPlacement, selectedIndex ); } + @Override + protected void paintTabArea( Graphics g, int tabPlacement, int selectedIndex ) { + // need to set rendering hints here too because this method is also invoked + // from BasicTabbedPaneUI.ScrollableTabPanel.paintComponent() + Object[] oldHints = FlatUIUtils.setRenderingHints( g ); + + super.paintTabArea( g, tabPlacement, selectedIndex ); + + FlatUIUtils.resetRenderingHints( g, oldHints ); + } + @Override protected void paintTab( Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect ) @@ -996,21 +975,20 @@ public class FlatTabbedPaneUI int x, int y, int w, int h, boolean isSelected ) { // paint tab background - Color background = determineTabBackgroundColor(tabPlacement, tabIndex, isSelected); + Color background = getTabBackground( tabPlacement, tabIndex, isSelected ); g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) ); g.fillRect( x, y, w, h ); } - protected Color determineTabBackgroundColor(int tabPlacement, int tabIndex, boolean isSelected) { + 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 ))); - return background; } @Override @@ -1019,57 +997,59 @@ public class FlatTabbedPaneUI { // paint tab separators if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) && - !isLastInRun( tabIndex ) ) { + !isLastInRun( tabIndex ) ) + { if( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) { // Some separators need to be omitted when activeTabBorder is enabled int selectedIndex = tabPane.getSelectedIndex(); - if (tabIndex != selectedIndex - 1 && tabIndex != selectedIndex) { + if( tabIndex != selectedIndex - 1 && tabIndex != selectedIndex ) paintTabSeparator( g, tabPlacement, x, y, w, h ); - } - } else { + } else paintTabSeparator( g, tabPlacement, x, y, w, h ); - } } // paint active tab border - if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && isSelected) { - paintActiveTabBorder(g, tabPlacement, tabIndex, x, y, w, h); - } + if( isSelected && clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) + paintSelectedTabBorder( g, tabPlacement, tabIndex, x, y, w, h ); } - protected void paintActiveTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h) { - float borderWidth = scale(contentSeparatorHeight); + protected void paintSelectedTabBorder( 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: - ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, borderWidth, h) ); - ((Graphics2D)g).fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h) ); + // 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: - ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, w, borderWidth) ); - ((Graphics2D)g).fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth) ); + // 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 (tabSelectionHeight <= 0) { + if( tabSelectionHeight <= 0 ) { //If there is no tab selection indicator, paint a top border as well switch( tabPlacement ) { default: case TOP: - ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, w, borderWidth) ); + g2.fill( new Rectangle2D.Float( x, y, w, borderWidth ) ); break; case BOTTOM: - ((Graphics2D)g).fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth) ); + g2.fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth ) ); break; case LEFT: - ((Graphics2D)g).fill( new Rectangle2D.Float( x, y, borderWidth, h) ); + g2.fill( new Rectangle2D.Float( x, y, borderWidth, h ) ); break; case RIGHT: - ((Graphics2D)g).fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h) ); + g2.fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h ) ); break; } } @@ -1118,49 +1098,50 @@ public class FlatTabbedPaneUI g.setColor( tabPane.isEnabled() ? underlineColor : disabledUnderlineColor ); // paint underline selection + boolean atBottom = !clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ); Insets contentInsets = getContentBorderInsets( tabPlacement ); int tabSelectionHeight = scale( this.tabSelectionHeight ); + int sx, sy; switch( tabPlacement ) { case TOP: default: - int sy; - if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) - sy = y; - else - sy = y + h + contentInsets.top - tabSelectionHeight; - + sy = atBottom ? (y + h + contentInsets.top - tabSelectionHeight) : y; g.fillRect( x, sy, w, tabSelectionHeight ); break; case BOTTOM: - if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { - g.fillRect( x, y + h - tabSelectionHeight, w, tabSelectionHeight ); - } else { - 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; - if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { - sx = x; - } else { - sx = x + w + contentInsets.left - tabSelectionHeight; - } + sx = atBottom ? (x + w + contentInsets.left - tabSelectionHeight) : x; g.fillRect( sx, y, tabSelectionHeight, h ); break; case RIGHT: - if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder )) { - g.fillRect( x + w - tabSelectionHeight, y, tabSelectionHeight, h ); - } else { - g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h ); - } + sx = atBottom ? (x - contentInsets.right) : (x + w - tabSelectionHeight); + g.fillRect( sx, y, tabSelectionHeight, h ); break; } } - protected Rectangle getContentBorderBounds(int tabPlacement) { + /** + * Actually does nearly the same as super.paintContentBorder() but + * - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly + * - tabsOverlapBorder is always true + * - 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 ) { + if( tabPane.getTabCount() <= 0 || + contentSeparatorHeight == 0 || + !clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) ) + return; + Insets insets = tabPane.getInsets(); Insets tabAreaInsets = getTabAreaInsets( tabPlacement ); @@ -1195,126 +1176,56 @@ public class FlatTabbedPaneUI break; } - return new Rectangle(x, y, w, h); - } - - /** - * Actually does nearly the same as super.paintContentBorder() but - * - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly - * - tabsOverlapBorder is always true - * - 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 ) { - if( tabPane.getTabCount() <= 0 || - contentSeparatorHeight == 0 || - !clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) ) - return; - - Rectangle contentBorderBounds = getContentBorderBounds(tabPlacement); - int x = contentBorderBounds.x; - int y = contentBorderBounds.y; - int w = contentBorderBounds.width; - int h = contentBorderBounds.height; - - int csh = scale( contentSeparatorHeight ); - // compute insets for separator or full border boolean hasFullBorder = clientPropertyBoolean( tabPane, TABBED_PANE_HAS_FULL_BORDER, this.hasFullBorder ); int sh = scale( contentSeparatorHeight * 100 ); // multiply by 100 because rotateInsets() does not use floats 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 - Area area = new Area(); + // 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 ); - if ( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) { - // Active tab border style - int ax = rects[selectedIndex].x; - int ay = rects[selectedIndex].y; - int aw = rects[selectedIndex].width; - int ah = rects[selectedIndex].height; + // add gap for selected tab to path + if( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) { + float csh = scale( (float) contentSeparatorHeight ); - int scrollOffsetX = 0; - int scrollOffsetY = 0; - int tabViewportOffsetX = 0; - int tabViewportOffsetY = 0; - if (tabViewport != null && isScrollTabLayout()) { - scrollOffsetX = tabViewport.getViewRect().x; - scrollOffsetY = tabViewport.getViewRect().y; - tabViewportOffsetX = tabViewport.getX(); - tabViewportOffsetY = tabViewport.getY(); - } - - int gapX; - int gapY; - int gapW; - int gapH; - switch( tabPlacement ) { - default: - case TOP: - gapX = x + ax + csh + (-scrollOffsetX + tabViewportOffsetX); - gapY = y; - gapW = aw - csh*2; - gapH = csh; - break; - case BOTTOM: - gapX = x + ax + csh + (-scrollOffsetX + tabViewportOffsetX); - gapY = h - csh; - gapW = aw - csh*2; - gapH = csh; - break; - case LEFT: - gapX = x; - gapY = y + ay + csh + (-scrollOffsetY + tabViewportOffsetY); - gapW = csh; - gapH = ah - csh*2; - break; - case RIGHT: - gapX = w - csh; - gapY = y + ay + csh + (-scrollOffsetY + tabViewportOffsetY); - gapW = csh; - gapH = ah - csh*2; - break; - } - - area.add( new Area( new Rectangle2D.Float( x, y, w, h ) ) ); - area.subtract( new Area( new Rectangle2D.Float( gapX, gapY, gapW, gapH) ) ); - - Color activeTabAreaBackground = determineTabBackgroundColor(tabPlacement, selectedIndex, true); - g.setColor( activeTabAreaBackground ); - ((Graphics2D)g).fill( new Rectangle2D.Float( gapX, gapY, gapW, gapH) ); + 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 && isScrollTabLayout()) { - switch( tabPlacement ) { - default: - case TOP: - case BOTTOM: - area.add( new Area( new Rectangle2D.Float( x, y, tabViewport.getX() - x, h ) ) ); - area.add( new Area( new Rectangle2D.Float( tabViewport.getX() + tabViewport.getWidth(), y, w - tabViewport.getX() - x, h ) ) ); - break; - case LEFT: - case RIGHT: - area.add( new Area( new Rectangle2D.Float( x, y, w, tabViewport.getY() - y ) ) ); - area.add( new Area( new Rectangle2D.Float( x, tabViewport.getY() + tabViewport.getHeight(), w, h - tabViewport.getY() - y ) ) ); - break; + 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 ); } } - } else { - area.add( new Area( new Rectangle2D.Float(x, y, w, h) ) ); + + 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 ); + } } - Rectangle2D.Float r = 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) ); - area.subtract( new Area( r ) ); - + // paint content separator or full border g.setColor( contentAreaColor ); - ((Graphics2D) g).fill( area ); + ((Graphics2D)g).fill( path ); // repaint selection in scroll-tab-layout because it may be painted before // the content border was painted (from BasicTabbedPaneUI$ScrollableTabPanel) @@ -2361,11 +2272,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; @@ -2534,6 +2446,9 @@ public class FlatTabbedPaneUI case TABBED_PANE_SHOW_TAB_SEPARATORS: case TABBED_PANE_ACTIVE_TAB_BORDER: + tabPane.repaint(); + break; + case TABBED_PANE_SHOW_CONTENT_SEPARATOR: case TABBED_PANE_HAS_FULL_BORDER: case TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB: @@ -2625,6 +2540,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-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java index 48394158..b6e7c186 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java @@ -17,13 +17,11 @@ package com.formdev.flatlaf.demo; import java.awt.Dimension; -import javax.swing.*; +import javax.swing.SwingUtilities; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.extras.FlatInspector; import com.formdev.flatlaf.extras.FlatUIDefaultsInspector; import com.formdev.flatlaf.util.SystemInfo; -import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_ACTIVE_TAB_BORDER; -import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_HAS_FULL_BORDER; /** * @author Karl Tauber From 3cfa16b8b735173d794d7236d8370513a171a8b7 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 18 Nov 2021 16:47:21 +0100 Subject: [PATCH 7/7] TabbedPane: completed review of PR #343 (active tab border painting style) - replaced `activeTabBorder` with `tabType` - added `TabbedPane.cardTabSelectionHeight` --- .../formdev/flatlaf/FlatClientProperties.java | 40 +++++++--- .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 60 +++++++++++---- .../com/formdev/flatlaf/FlatLaf.properties | 5 +- .../com/formdev/flatlaf/demo/TabsPanel.java | 76 +++++++++++++------ .../com/formdev/flatlaf/demo/TabsPanel.jfd | 50 +++++++++--- .../extras/components/FlatTabbedPane.java | 23 ++++++ .../uidefaults/FlatDarkLaf_1.8.0_202.txt | 2 + .../uidefaults/FlatLightLaf_1.8.0_202.txt | 2 + .../uidefaults/FlatTestLaf_1.8.0_202.txt | 2 + .../flatlaf/testing/FlatContainerTest.java | 48 ++++++++---- .../flatlaf/testing/FlatContainerTest.jfd | 46 +++++++---- .../flatlaf/themeeditor/FlatLafUIKeys.txt | 2 + 12 files changed, 270 insertions(+), 86 deletions(-) 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 de15ec2d..a4b6bb3c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -14,8 +14,8 @@ * limitations under the License. */ - package com.formdev.flatlaf; + import java.awt.Color; import java.util.Objects; import javax.swing.JComponent; @@ -324,6 +324,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. *

@@ -332,15 +361,6 @@ public interface FlatClientProperties */ String TABBED_PANE_SHOW_TAB_SEPARATORS = "JTabbedPane.showTabSeparators"; - /** - * Specifies whether a border is painted around the active tab. - * This also changes position of the active tab indicator. - *

- * Component {@link javax.swing.JTabbedPane}
- * Value type {@link java.lang.Boolean} - */ - String TABBED_PANE_ACTIVE_TAB_BORDER = "JTabbedPane.activeTabBorder"; - /** * Specifies whether the separator between tabs area and content area should be shown. *

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 23d20688..88b97604 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 @@ -97,7 +97,6 @@ import com.formdev.flatlaf.util.UIScale; * * @clientProperty JTabbedPane.showTabSeparators boolean * @clientProperty JTabbedPane.hasFullBorder boolean - * @clientProperty JTabbedPane.activeTabBorder boolean * * * @@ -131,6 +130,7 @@ 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 @@ -138,6 +138,7 @@ import com.formdev.flatlaf.util.UIScale; * @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 @@ -161,6 +162,10 @@ import com.formdev.flatlaf.util.UIScale; public class FlatTabbedPaneUI extends BasicTabbedPaneUI { + // 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; @@ -196,13 +201,14 @@ public class FlatTabbedPaneUI protected int maximumTabWidth; protected int tabHeight; protected int tabSelectionHeight; + /** @since 2 */ protected int cardTabSelectionHeight; protected int contentSeparatorHeight; protected boolean showTabSeparators; protected boolean tabSeparatorsFullHeight; protected boolean hasFullBorder; protected boolean tabsOpaque = true; - protected boolean activeTabBorder; + private int tabType; private int tabsPopupPolicy; private int scrollButtonsPolicy; private int scrollButtonsPlacement; @@ -304,13 +310,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" ); - activeTabBorder = UIManager.getBoolean( "TabbedPane.activeTabBorder" ); + tabType = parseTabType( UIManager.getString( "TabbedPane.tabType" ) ); tabsPopupPolicy = parseTabsPopupPolicy( UIManager.getString( "TabbedPane.tabsPopupPolicy" ) ); scrollButtonsPolicy = parseScrollButtonsPolicy( UIManager.getString( "TabbedPane.scrollButtonsPolicy" ) ); scrollButtonsPlacement = parseScrollButtonsPlacement( UIManager.getString( "TabbedPane.scrollButtonsPlacement" ) ); @@ -625,7 +632,7 @@ public class FlatTabbedPaneUI // increase size of repaint region to include part of content border if( contentSeparatorHeight > 0 && - clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) && + getTabType() == TAB_TYPE_CARD && clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) ) { int sh = scale( contentSeparatorHeight ); @@ -980,6 +987,7 @@ public class FlatTabbedPaneUI g.fillRect( x, y, w, h ); } + /** @since 2 */ protected Color getTabBackground( int tabPlacement, int tabIndex, boolean isSelected ) { boolean enabled = tabPane.isEnabled(); return enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex @@ -999,8 +1007,8 @@ public class FlatTabbedPaneUI if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) && !isLastInRun( tabIndex ) ) { - if( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) { - // Some separators need to be omitted when activeTabBorder is enabled + 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 ); @@ -1009,11 +1017,12 @@ public class FlatTabbedPaneUI } // paint active tab border - if( isSelected && clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) - paintSelectedTabBorder( g, tabPlacement, tabIndex, x, y, w, h ); + if( isSelected && getTabType() == TAB_TYPE_CARD ) + paintCardTabBorder( g, tabPlacement, tabIndex, x, y, w, h ); } - protected void paintSelectedTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int 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 ); @@ -1035,8 +1044,8 @@ public class FlatTabbedPaneUI break; } - if( tabSelectionHeight <= 0 ) { - //If there is no tab selection indicator, paint a top border as well + if( cardTabSelectionHeight <= 0 ) { + // if there is no tab selection indicator, paint a top border as well switch( tabPlacement ) { default: case TOP: @@ -1098,9 +1107,9 @@ public class FlatTabbedPaneUI g.setColor( tabPane.isEnabled() ? underlineColor : disabledUnderlineColor ); // paint underline selection - boolean atBottom = !clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ); + 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: @@ -1189,7 +1198,7 @@ public class FlatTabbedPaneUI w - (ci.left / 100f) - (ci.right / 100f), h - (ci.top / 100f) - (ci.bottom / 100f) ), false ); // add gap for selected tab to path - if( clientPropertyBoolean( tabPane, TABBED_PANE_ACTIVE_TAB_BORDER, activeTabBorder ) ) { + if( getTabType() == TAB_TYPE_CARD ) { float csh = scale( (float) contentSeparatorHeight ); Rectangle tabRect = getTabBounds( tabPane, selectedIndex ); @@ -1426,6 +1435,15 @@ public class FlatTabbedPaneUI clientPropertyBoolean( tabPane, TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB, false ); } + /** @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 ); @@ -1478,6 +1496,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; @@ -2445,7 +2475,7 @@ public class FlatTabbedPaneUI break; case TABBED_PANE_SHOW_TAB_SEPARATORS: - case TABBED_PANE_ACTIVE_TAB_BORDER: + case TABBED_PANE_TAB_TYPE: tabPane.repaint(); break; 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 6de2de23..18d206c2 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -569,9 +569,9 @@ SplitPaneDivider.gripGap = 2 TabbedPane.tabHeight = 32 TabbedPane.tabSelectionHeight = 3 +TabbedPane.cardTabSelectionHeight = 2 TabbedPane.contentSeparatorHeight = 1 TabbedPane.showTabSeparators = false -TabbedPane.activeTabBorder = false TabbedPane.tabSeparatorsFullHeight = false TabbedPane.hasFullBorder = false TabbedPane.tabInsets = 4,12,4,12 @@ -591,6 +591,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 42cb124a..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,17 +303,16 @@ 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 ); } - private void activeTabBorderChanged() { - Boolean activeBorderTab = activeTabBorderCheckBox.isSelected() ? true : false; - System.out.println(TABBED_PANE_ACTIVE_TAB_BORDER + ": " + activeBorderTab); - putTabbedPanesClientProperty( TABBED_PANE_ACTIVE_TAB_BORDER, activeBorderTab ); - } - private void putTabbedPanesClientProperty( String key, Object value ) { updateTabbedPanesRecur( this, tabbedPane -> tabbedPane.putClientProperty( key, value ) ); } @@ -402,12 +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(); - activeTabBorderCheckBox = new JCheckBox(); + tabTypeLabel = new JLabel(); + tabTypeToolBar = new JToolBar(); + underlinedTabTypeButton = new JToggleButton(); + cardTabTypeButton = new JToggleButton(); //======== this ======== setName("this"); @@ -502,7 +504,7 @@ class TabsPanel { tabPlacementTabbedPane.setName("tabPlacementTabbedPane"); } - panel1.add(tabPlacementTabbedPane, "cell 0 1, width 300:300, height 100:100"); + panel1.add(tabPlacementTabbedPane, "cell 0 1,width 300:300,height 100:100"); //---- tabLayoutLabel ---- tabLayoutLabel.setText("Tab layout"); @@ -880,7 +882,8 @@ class TabsPanel "[]" + "[fill]para" + "[fill]" + - "[fill]para", + "[fill]para" + + "[fill]", // rows "[]" + "[center]")); @@ -948,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"); @@ -976,17 +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"); + //---- tabTypeLabel ---- + tabTypeLabel.setText("Tab type:"); + tabTypeLabel.setName("tabTypeLabel"); + panel4.add(tabTypeLabel, "cell 2 1"); - //---- activeTabBorderCheckBox ---- - activeTabBorderCheckBox.setText("Paint border around active tab"); - activeTabBorderCheckBox.setName("activeTabBorderCheckBox"); - activeTabBorderCheckBox.addActionListener(e -> activeTabBorderChanged()); - panel4.add(activeTabBorderCheckBox, "cell 3 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"); @@ -1023,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 ) { @@ -1102,11 +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 JCheckBox activeTabBorderCheckBox; + 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 bd04f9b4..02a45e34 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_202.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt index 69102d69..655c1055 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt @@ -980,6 +980,7 @@ TabbedPane.buttonArc 6 TabbedPane.buttonHoverBackground #303234 com.formdev.flatlaf.util.DerivedColor [UI] darken(5%) TabbedPane.buttonInsets 2,1,2,1 javax.swing.plaf.InsetsUIResource [UI] TabbedPane.buttonPressedBackground #282a2c com.formdev.flatlaf.util.DerivedColor [UI] darken(8%) +TabbedPane.cardTabSelectionHeight 2 TabbedPane.closeArc 4 TabbedPane.closeCrossFilledSize 7.5 TabbedPane.closeCrossLineWidth 1.0 @@ -1022,6 +1023,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_202.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt index 272cc8a7..3ecc3f58 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt @@ -985,6 +985,7 @@ TabbedPane.buttonArc 6 TabbedPane.buttonHoverBackground #e0e0e0 com.formdev.flatlaf.util.DerivedColor [UI] darken(7% autoInverse) TabbedPane.buttonInsets 2,1,2,1 javax.swing.plaf.InsetsUIResource [UI] TabbedPane.buttonPressedBackground #d9d9d9 com.formdev.flatlaf.util.DerivedColor [UI] darken(10% autoInverse) +TabbedPane.cardTabSelectionHeight 2 TabbedPane.closeArc 4 TabbedPane.closeCrossFilledSize 7.5 TabbedPane.closeCrossLineWidth 1.0 @@ -1027,6 +1028,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_202.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt index 0ab6c35f..9eff94f2 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt @@ -980,6 +980,7 @@ TabbedPane.buttonArc 6 TabbedPane.buttonHoverBackground #ffff00 javax.swing.plaf.ColorUIResource [UI] TabbedPane.buttonInsets 2,1,2,1 javax.swing.plaf.InsetsUIResource [UI] TabbedPane.buttonPressedBackground #ffc800 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.cardTabSelectionHeight 2 TabbedPane.closeArc 999 TabbedPane.closeCrossFilledSize 6.5 TabbedPane.closeCrossLineWidth 2.0 @@ -1025,6 +1026,7 @@ TabbedPane.tabRunOverlay 0 TabbedPane.tabSelectionHeight 3 TabbedPane.tabSeparatorColor #0000ff 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 tabAreaAlignmentField; private FlatTestEnumComboBox tabAlignmentField; private FlatTestEnumComboBox tabWidthModeField; + private FlatTestEnumComboBox tabTypeComboBox; private JCheckBox leadingComponentCheckBox; private JCheckBox customBorderCheckBox; private JCheckBox tabAreaInsetsCheckBox; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd index 2fe1d8b9..cecff88e 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd @@ -132,7 +132,7 @@ new FormModel { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestFrame$NoRightToLeftPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 0,hidemode 3" "$columnConstraints": "[][fill][]" - "$rowConstraints": "[center][][][][][]para[][]para[][][]" + "$rowConstraints": "[center][][][][][][]para[][]para[][][]" } ) { name: "tabbedPaneControlPanel" "opaque": false @@ -372,6 +372,22 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 2 5" } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "tabTypeLabel" + "text": "Tab type:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 6" + } ) + add( new FormComponent( "com.formdev.flatlaf.testing.FlatTestEnumComboBox" ) { + name: "tabTypeComboBox" + auxiliary() { + "JavaCodeGenerator.typeParameters": "TabType" + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabTypeChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 6" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "leadingComponentCheckBox" "text": "Leading component" @@ -380,7 +396,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "leadingComponentChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6" + "value": "cell 0 7" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "customBorderCheckBox" @@ -390,7 +406,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "customBorderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 6" + "value": "cell 1 7" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "tabAreaInsetsCheckBox" @@ -400,7 +416,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabAreaInsetsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 6" + "value": "cell 2 7" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "trailingComponentCheckBox" @@ -410,7 +426,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "trailingComponentChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 7" + "value": "cell 0 8" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "hasFullBorderCheckBox" @@ -420,7 +436,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hasFullBorderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 7,alignx left,growx 0" + "value": "cell 1 8,alignx left,growx 0" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "smallerTabHeightCheckBox" @@ -430,7 +446,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smallerTabHeightChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 7" + "value": "cell 2 8" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "minimumTabWidthCheckBox" @@ -440,7 +456,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "minimumTabWidthChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 8" + "value": "cell 0 9" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "hideContentSeparatorCheckBox" @@ -450,7 +466,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hideContentSeparatorChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 8" + "value": "cell 1 9" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "smallerInsetsCheckBox" @@ -460,7 +476,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smallerInsetsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 8" + "value": "cell 2 9" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "maximumTabWidthCheckBox" @@ -470,7 +486,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "maximumTabWidthChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 9" + "value": "cell 0 10" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "showTabSeparatorsCheckBox" @@ -480,7 +496,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showTabSeparatorsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9" + "value": "cell 1 10" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "secondTabWiderCheckBox" @@ -490,7 +506,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "secondTabWiderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 9" + "value": "cell 2 10" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "hideTabAreaWithOneTabCheckBox" @@ -500,7 +516,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hideTabAreaWithOneTabChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 10" + "value": "cell 1 11" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "customWheelScrollingCheckBox" @@ -510,7 +526,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "customWheelScrollingChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 10" + "value": "cell 2 11" } ) }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) { "gridY": 11 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 72ccd0c1..4e0b63bb 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 @@ -709,6 +709,7 @@ TabbedPane.buttonArc TabbedPane.buttonHoverBackground TabbedPane.buttonInsets TabbedPane.buttonPressedBackground +TabbedPane.cardTabSelectionHeight TabbedPane.closeArc TabbedPane.closeCrossFilledSize TabbedPane.closeCrossLineWidth @@ -754,6 +755,7 @@ TabbedPane.tabInsets TabbedPane.tabRunOverlay TabbedPane.tabSelectionHeight TabbedPane.tabSeparatorsFullHeight +TabbedPane.tabType TabbedPane.tabWidthMode TabbedPane.tabsOpaque TabbedPane.tabsOverlapBorder