From 700bb9b567fcb5bb6aab70a55e9c1cea0445dc90 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 20 Oct 2020 09:37:28 +0200 Subject: [PATCH] TabbedPane: support closable tabs (issue #40) --- CHANGELOG.md | 1 + .../formdev/flatlaf/FlatClientProperties.java | 74 +++++ .../icons/FlatTabbedPaneCloseIcon.java | 90 ++++++ .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 294 ++++++++++++++++-- .../formdev/flatlaf/FlatDarkLaf.properties | 7 + .../com/formdev/flatlaf/FlatLaf.properties | 7 + .../formdev/flatlaf/FlatLightLaf.properties | 7 + .../flatlaf/extras/TriStateCheckBox.java | 13 + .../uidefaults/FlatDarkLaf_1.8.0_202.txt | 11 + .../uidefaults/FlatLightLaf_1.8.0_202.txt | 11 + .../uidefaults/FlatTestLaf_1.8.0_202.txt | 11 + .../flatlaf/testing/FlatContainerTest.java | 51 ++- .../flatlaf/testing/FlatContainerTest.jfd | 20 ++ .../flatlaf/testing/FlatTestLaf.properties | 12 + .../flatlaf/themeeditor/FlatLafUIKeys.txt | 11 + 15 files changed, 583 insertions(+), 37 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 023c94a9..a0c58204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ FlatLaf Change Log - TabbedPane: Support adding custom components to left and right sides of tabs area. (set client property `JTabbedPane.leadingComponent` or `JTabbedPane.trailingComponent` to a `java.awt.Component`) (issue #40) +- TabbedPane: Support closable tabs. (issue #40) - Support painting separator line between window title and content (use UI value `TitlePane.borderColor`). (issue #184) 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 974d7a85..0c7c31a2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -25,6 +25,8 @@ import javax.swing.JComponent; */ public interface FlatClientProperties { + //---- JButton ------------------------------------------------------------ + /** * Specifies type of a button. *

@@ -104,6 +106,8 @@ public interface FlatClientProperties */ String SQUARE_SIZE = "JButton.squareSize"; + //---- JComponent --------------------------------------------------------- + /** * Specifies minimum width of a component. *

@@ -158,6 +162,8 @@ public interface FlatClientProperties */ String COMPONENT_ROUND_RECT = "JComponent.roundRect"; + //---- Popup -------------------------------------------------------------- + /** * Specifies whether a drop shadow is painted if the component is shown in a popup * or if the component is the owner of another component that is shown in a popup. @@ -167,6 +173,8 @@ public interface FlatClientProperties */ String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted"; + //---- JProgressBar ------------------------------------------------------- + /** * Specifies whether the progress bar has always the larger height even if no string is painted. *

@@ -183,6 +191,8 @@ public interface FlatClientProperties */ String PROGRESS_BAR_SQUARE = "JProgressBar.square"; + //---- JRootPane ---------------------------------------------------------- + /** * Specifies whether the menu bar is embedded into the title pane if custom * window decorations are enabled. Default is {@code true}. @@ -192,6 +202,8 @@ public interface FlatClientProperties */ String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"; + //---- JScrollBar --------------------------------------------------------- + /** * Specifies whether the decrease/increase arrow buttons of a scrollbar are shown. *

@@ -208,6 +220,8 @@ public interface FlatClientProperties */ String SCROLL_PANE_SMOOTH_SCROLLING = "JScrollPane.smoothScrolling"; + //---- JTabbedPane -------------------------------------------------------- + /** * Specifies whether separators are shown between tabs. *

@@ -240,6 +254,60 @@ public interface FlatClientProperties */ String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"; + /** + * Specifies whether tabs are closable. + * If set to {@code true} on a tabbed pane component, all tabs in that tabbed pane are closable. + * To make individual tabs closable, set it to {@code true} on a tab content component. + *

+ * Note that you have to specify a callback (see {@link #TABBED_PANE_TAB_CLOSABLE}) + * that is invoked when the user clicks a tab close button. + * The callback is responsible for closing the tab. + *

+ * Component {@link javax.swing.JTabbedPane} + * or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})
+ * Value type {@link java.lang.Boolean} + * + * @see #TABBED_PANE_TAB_CLOSE_CALLBACK + */ + String TABBED_PANE_TAB_CLOSABLE = "JTabbedPane.tabClosable"; + + /** + * Specifies the callback that is invoked when a tab close button is clicked. + * The callback is responsible for closing the tab. + *

+ * Either use a {@link java.util.function.IntConsumer} that received the tab index as parameter: + *

{@code
+	 * myTabbedPane.putClientProperty( "JTabbedPane.tabCloseCallback",
+	 *     (IntConsumer) tabIndex -> {
+	 *         // close tab here
+	 *     } );
+	 * }
+ * Or use a {@link java.util.function.BiConsumer}<javax.swing.JTabbedPane, Integer> + * that received the tabbed pane and the tab index as parameters: + *
{@code
+	 * myTabbedPane.putClientProperty( "JTabbedPane.tabCloseCallback",
+	 *     (BiConsumer) (tabbedPane, tabIndex) -> {
+	 *         // close tab here
+	 *     } );
+	 * }
+ * If you need to check whether a modifier key (e.g. Alt or Shift) was pressed + * while the user clicked the tab close button, use {@link java.awt.EventQueue#getCurrentEvent} + * to get current event, check whether it is a {@link java.awt.event.MouseEvent} + * and invoke its methods. E.g. + *
{@code
+	 * AWTEvent e = EventQueue.getCurrentEvent();
+	 * boolean shift = (e instanceof MouseEvent) ? ((MouseEvent)e).isShiftDown() : false;
+	 * }
+ *

+ * Component {@link javax.swing.JTabbedPane} + * or tab content components (see {@link javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)})
+ * Value type {@link java.util.function.IntConsumer} + * or {@link java.util.function.BiConsumer}<javax.swing.JTabbedPane, Integer> + * + * @see #TABBED_PANE_TAB_CLOSABLE + */ + String TABBED_PANE_TAB_CLOSE_CALLBACK = "JTabbedPane.tabCloseCallback"; + /** * Specifies how to navigate to hidden tabs. *

@@ -280,6 +348,8 @@ public interface FlatClientProperties */ String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent"; + //---- JTextField --------------------------------------------------------- + /** * Specifies whether all text is selected when the text component gains focus. *

@@ -322,6 +392,8 @@ public interface FlatClientProperties */ String PLACEHOLDER_TEXT = "JTextField.placeholderText"; + //---- JToggleButton ------------------------------------------------------ + /** * Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}. *

@@ -346,6 +418,8 @@ public interface FlatClientProperties */ String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground"; + //---- helper methods ----------------------------------------------------- + /** * Checks whether a client property of a component has the given value. */ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java new file mode 100644 index 00000000..fa5f5114 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatTabbedPaneCloseIcon.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.icons; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatButtonUI; +import com.formdev.flatlaf.ui.FlatUIUtils; + +/** + * "close" icon for closable tabs in {@link javax.swing.JTabbedPane}. + * + * @uiDefault TabbedPane.closeSize Dimension + * @uiDefault TabbedPane.closeArc int + * @uiDefault TabbedPane.closeCrossPlainSize float + * @uiDefault TabbedPane.closeCrossFilledSize float + * @uiDefault TabbedPane.closeCrossLineWidth float + * @uiDefault TabbedPane.closeBackground Color + * @uiDefault TabbedPane.closeForeground Color + * @uiDefault TabbedPane.closeHoverBackground Color + * @uiDefault TabbedPane.closeHoverForeground Color + * @uiDefault TabbedPane.closePressedBackground Color + * @uiDefault TabbedPane.closePressedForeground Color + * + * @author Karl Tauber + */ +public class FlatTabbedPaneCloseIcon + extends FlatAbstractIcon +{ + protected final Dimension size = UIManager.getDimension( "TabbedPane.closeSize" ); + protected final int arc = UIManager.getInt( "TabbedPane.closeArc" ); + protected final float crossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f ); + protected final float crossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", crossPlainSize ); + protected final float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f ); + protected final Color background = UIManager.getColor( "TabbedPane.closeBackground" ); + protected final Color foreground = UIManager.getColor( "TabbedPane.closeForeground" ); + protected final Color hoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" ); + protected final Color hoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" ); + protected final Color pressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" ); + protected final Color pressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" ); + + public FlatTabbedPaneCloseIcon() { + super( 16, 16, null ); + } + + @Override + protected void paintIcon( Component c, Graphics2D g ) { + // paint background + Color bg = FlatButtonUI.buttonStateColor( c, background, null, null, hoverBackground, pressedBackground ); + if( bg != null ) { + g.setColor( bg ); + g.fillRoundRect( (width - size.width) / 2, (height - size.height) / 2, + size.width, size.height, arc, arc ); + } + + // set cross color + g.setColor( FlatButtonUI.buttonStateColor( c, foreground, null, null, hoverForeground, pressedForeground ) ); + + float mx = width / 2; + float my = height / 2; + float r = ((bg != null) ? crossFilledSize : crossPlainSize) / 2; + + // paint cross + Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); + path.append( new Line2D.Float( mx - r, my - r, mx + r, my + r ), false ); + path.append( new Line2D.Float( mx - r, my + r, mx + r, my - r ), false ); + g.setStroke( new BasicStroke( closeCrossLineWidth ) ); + g.draw( path ); + } +} 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 1c26fc39..79cba85f 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 @@ -38,11 +38,14 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; @@ -51,6 +54,10 @@ import java.beans.PropertyChangeListener; import java.util.Collections; import java.util.Locale; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.IntConsumer; +import javax.swing.ButtonModel; +import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JMenuItem; @@ -119,6 +126,7 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault TabbedPane.hasFullBorder boolean * @uiDefault TabbedPane.hiddenTabsNavigation String moreTabsButton (default) or arrowButtons * @uiDefault ScrollPane.smoothScrolling boolean + * @uiDefault TabbedPane.closeIcon Icon * * @uiDefault TabbedPane.moreTabsButtonToolTipText String * @@ -152,18 +160,22 @@ public class FlatTabbedPaneUI protected boolean hasFullBorder; private String hiddenTabsNavigationStr; + protected Icon closeIcon; protected String moreTabsButtonToolTipText; protected JViewport tabViewport; protected FlatWheelTabScroller wheelTabScroller; + private JButton tabCloseButton; private JButton moreTabsButton; private Container leadingComponent; private Container trailingComponent; private Handler handler; private boolean blockRollover; + private boolean rolloverTabClose; + private boolean pressedTabClose; public static ComponentUI createUI( JComponent c ) { return new FlatTabbedPaneUI(); @@ -210,6 +222,7 @@ public class FlatTabbedPaneUI tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" ); hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); hiddenTabsNavigationStr = UIManager.getString( "TabbedPane.hiddenTabsNavigation" ); + closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" ); Locale l = tabPane.getLocale(); moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l ); @@ -255,6 +268,8 @@ public class FlatTabbedPaneUI tabSeparatorColor = null; contentAreaColor = null; + closeIcon = null; + MigLayoutVisualPadding.uninstall( tabPane ); } @@ -262,6 +277,11 @@ public class FlatTabbedPaneUI protected void installComponents() { super.installComponents(); + // create tab close button + tabCloseButton = new TabCloseButton(); + tabCloseButton.setVisible( false ); + tabPane.add( tabCloseButton ); + // find scrollable tab viewport tabViewport = null; if( isScrollTabLayout() ) { @@ -288,6 +308,11 @@ public class FlatTabbedPaneUI super.uninstallComponents(); + if( tabCloseButton != null ) { + tabPane.remove( tabCloseButton ); + tabCloseButton = null; + } + tabViewport = null; } @@ -354,9 +379,7 @@ public class FlatTabbedPaneUI protected void installListeners() { super.installListeners(); - tabPane.addMouseListener( getHandler() ); - tabPane.addMouseMotionListener( getHandler() ); - tabPane.addComponentListener( getHandler() ); + getHandler().installListeners(); if( tabViewport != null && (wheelTabScroller = createWheelTabScroller()) != null ) { // ideally we would add the mouse listeners to the viewport, but then the @@ -373,9 +396,7 @@ public class FlatTabbedPaneUI super.uninstallListeners(); if( handler != null ) { - tabPane.removeMouseListener( handler ); - tabPane.removeMouseMotionListener( handler ); - tabPane.removeComponentListener( handler ); + handler.uninstallListeners(); handler = null; } @@ -399,6 +420,13 @@ public class FlatTabbedPaneUI return new FlatWheelTabScroller(); } + @Override + protected MouseListener createMouseListener() { + Handler handler = getHandler(); + handler.mouseDelegate = super.createMouseListener(); + return handler; + } + @Override protected PropertyChangeListener createPropertyChangeListener() { Handler handler = getHandler(); @@ -454,6 +482,30 @@ public class FlatTabbedPaneUI repaintTab( index ); } + protected boolean isRolloverTabClose() { + return rolloverTabClose; + } + + protected void setRolloverTabClose( boolean rollover ) { + if( rolloverTabClose == rollover ) + return; + + rolloverTabClose = rollover; + repaintTab( getRolloverTab() ); + } + + protected boolean isPressedTabClose() { + return pressedTabClose; + } + + protected void setPressedTabClose( boolean pressed ) { + if( pressedTabClose == pressed ) + return; + + pressedTabClose = pressed; + repaintTab( getRolloverTab() ); + } + private void repaintTab( int tabIndex ) { if( tabIndex < 0 || tabIndex >= tabPane.getTabCount() ) return; @@ -465,7 +517,10 @@ public class FlatTabbedPaneUI @Override protected int calculateTabWidth( int tabPlacement, int tabIndex, FontMetrics metrics ) { - return super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */; + int tabWidth = super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */; + if( isTabClosable( tabIndex ) ) + tabWidth += closeIcon.getIconWidth(); + return tabWidth; } @Override @@ -528,6 +583,10 @@ public class FlatTabbedPaneUI @Override protected int getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected ) { + if( isTabClosable( tabIndex ) ) { + int shift = closeIcon.getIconWidth() / 2; + return isLeftToRight() ? -shift : shift; + } return 0; } @@ -619,6 +678,10 @@ public class FlatTabbedPaneUI protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected ) { + // paint tab close button + if( isTabClosable( tabIndex ) ) + paintTabCloseButton( g, tabIndex, x, y, w, h ); + // paint tab separators if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) && !isLastInRun( tabIndex ) ) @@ -628,6 +691,18 @@ public class FlatTabbedPaneUI paintTabSelection( g, tabPlacement, x, y, w, h ); } + protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) { + // update state of tab close button + boolean rollover = (tabIndex == getRolloverTab()); + ButtonModel bm = tabCloseButton.getModel(); + bm.setRollover( rollover && isRolloverTabClose() ); + bm.setPressed( rollover && isPressedTabClose() ); + + // paint tab close icon + Rectangle tabCloseRect = getTabCloseBounds( x, y, w, h, calcRect ); + closeIcon.paintIcon( tabCloseButton, g, tabCloseRect.x, tabCloseRect.y ); + } + protected void paintTabSeparator( Graphics g, int tabPlacement, int x, int y, int w, int h ) { float sepWidth = UIScale.scale( 1f ); float offset = tabSeparatorsFullHeight ? 0 : UIScale.scale( 5f ); @@ -636,7 +711,7 @@ public class FlatTabbedPaneUI if( tabPlacement == LEFT || tabPlacement == RIGHT ) { // paint tab separator at bottom side ((Graphics2D)g).fill( new Rectangle2D.Float( x + offset, y + h - sepWidth, w - (offset * 2), sepWidth ) ); - } else if( tabPane.getComponentOrientation().isLeftToRight() ) { + } else if( isLeftToRight() ) { // paint tab separator at right side ((Graphics2D)g).fill( new Rectangle2D.Float( x + w - sepWidth, y + offset, sepWidth, h - (offset * 2) ) ); } else { @@ -819,6 +894,55 @@ public class FlatTabbedPaneUI return super.getTabBounds( tabIndex, dest ); } + protected Rectangle getTabCloseBounds( int x, int y, int w, int h, Rectangle dest ) { + dest.width = closeIcon.getIconWidth(); + dest.height = closeIcon.getIconHeight(); + int xoffset = (h - dest.width) / 2; + int yoffset = (h - dest.height) / 2; + dest.x = isLeftToRight() ? (x + w - xoffset - dest.width) : (x + xoffset); + dest.y = y + yoffset; + return dest; + } + + protected Rectangle getTabCloseHitArea( int tabIndex ) { + Rectangle tabRect = getTabBounds( tabPane, tabIndex ); + Rectangle tabCloseRect = getTabCloseBounds( tabRect.x, tabRect.y, tabRect.width, tabRect.height, calcRect ); + return new Rectangle( tabCloseRect.x, tabRect.y, tabCloseRect.width, tabRect.height ); + } + + protected boolean isTabClosable( int tabIndex ) { + Object value = getTabClientProperty( tabIndex, TABBED_PANE_TAB_CLOSABLE ); + return (value instanceof Boolean) ? (boolean) value : false; + } + + @SuppressWarnings( { "unchecked" } ) + protected void closeTab( int tabIndex ) { + Object callback = getTabClientProperty( tabIndex, TABBED_PANE_TAB_CLOSE_CALLBACK ); + if( callback instanceof IntConsumer ) + ((IntConsumer)callback).accept( tabIndex ); + else if( callback instanceof BiConsumer ) + ((BiConsumer)callback).accept( tabPane, tabIndex ); + else { + throw new RuntimeException( "Missing tab close callback. " + + "Set client property 'JTabbedPane.tabCloseCallback' " + + "to a 'java.util.function.IntConsumer' " + + "or 'java.util.function.BiConsumer'" ); + } + } + + protected Object getTabClientProperty( int tabIndex, String key ) { + if( tabIndex < 0 ) + return null; + + Component c = tabPane.getComponentAt( tabIndex ); + if( c instanceof JComponent ) { + Object value = ((JComponent)c).getClientProperty( key ); + if( value != null ) + return value; + } + return tabPane.getClientProperty( key ); + } + protected void ensureCurrentLayout() { // since super.ensureCurrentLayout() is private, // use super.getTabRunCount() as workaround @@ -834,6 +958,10 @@ public class FlatTabbedPaneUI return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; } + private boolean isLeftToRight() { + return tabPane.getComponentOrientation().isLeftToRight(); + } + protected boolean isHorizontalTabPlacement() { int tabPlacement = tabPane.getTabPlacement(); return tabPlacement == TOP || tabPlacement == BOTTOM; @@ -914,6 +1042,16 @@ public class FlatTabbedPaneUI } } + //---- class TabCloseButton ----------------------------------------------- + + private class TabCloseButton + extends JButton + implements UIResource + { + private TabCloseButton() { + } + } + //---- class ContainerUIResource ------------------------------------------ private class ContainerUIResource @@ -1011,9 +1149,8 @@ public class FlatTabbedPaneUI int buttonWidth = getWidth(); int buttonHeight = getHeight(); Dimension popupSize = popupMenu.getPreferredSize(); - boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight(); - int x = leftToRight ? buttonWidth - popupSize.width : 0; + int x = isLeftToRight() ? buttonWidth - popupSize.width : 0; int y = buttonHeight - popupSize.height; switch( tabPane.getTabPlacement() ) { default: @@ -1185,8 +1322,7 @@ public class FlatTabbedPaneUI int y = viewPosition.y; int tabPlacement = tabPane.getTabPlacement(); if( tabPlacement == TOP || tabPlacement == BOTTOM ) { - boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight(); - x += leftToRight ? amount : -amount; + x += isLeftToRight() ? amount : -amount; x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() ); } else { y += amount; @@ -1360,18 +1496,69 @@ public class FlatTabbedPaneUI //---- class Handler ------------------------------------------------------ private class Handler - extends MouseAdapter - implements PropertyChangeListener, ChangeListener, ComponentListener + implements MouseListener, MouseMotionListener, PropertyChangeListener, + ChangeListener, ComponentListener, ContainerListener { + MouseListener mouseDelegate; PropertyChangeListener propertyChangeDelegate; ChangeListener changeDelegate; + private int pressedTabIndex = -1; + + void installListeners() { + tabPane.addMouseMotionListener( this ); + tabPane.addComponentListener( this ); + tabPane.addContainerListener( this ); + + for( Component c : tabPane.getComponents() ) { + if( !(c instanceof UIResource) ) + c.addPropertyChangeListener( TABBED_PANE_TAB_CLOSABLE, this ); + } + } + + void uninstallListeners() { + tabPane.removeMouseMotionListener( this ); + tabPane.removeComponentListener( this ); + tabPane.removeContainerListener( this ); + + for( Component c : tabPane.getComponents() ) { + if( !(c instanceof UIResource) ) + c.removePropertyChangeListener( TABBED_PANE_TAB_CLOSABLE, this ); + } + } + //---- interface MouseListener ---- + @Override + public void mouseClicked( MouseEvent e ) { + mouseDelegate.mouseClicked( e ); + } + + @Override + public void mousePressed( MouseEvent e ) { + updateRollover( e, true ); + + if( !isPressedTabClose() ) + mouseDelegate.mousePressed( e ); + } + + @Override + public void mouseReleased( MouseEvent e ) { + if( isPressedTabClose() ) { + updateRollover( e, false ); + if( pressedTabIndex >= 0 && pressedTabIndex == getRolloverTab() ) + closeTab( pressedTabIndex ); + } else + mouseDelegate.mouseReleased( e ); + + pressedTabIndex = -1; + updateRollover( e, false ); + } + @Override public void mouseEntered( MouseEvent e ) { // this is necessary for "more tabs" button - setRolloverTab( e.getX(), e.getY() ); + updateRollover( e, false ); } @Override @@ -1379,15 +1566,37 @@ public class FlatTabbedPaneUI // this event occurs also if mouse is moved to a custom tab component // that handles mouse events (e.g. a close button) // --> make sure that the tab stays highlighted - setRolloverTab( e.getX(), e.getY() ); + updateRollover( e, false ); } //---- interface MouseMotionListener ---- + @Override + public void mouseDragged( MouseEvent e ) { + updateRollover( e, false ); + } + @Override public void mouseMoved( MouseEvent e ) { - // this is necessary for "more tabs" button - setRolloverTab( e.getX(), e.getY() ); + updateRollover( e, false ); + } + + private void updateRollover( MouseEvent e, boolean pressed ) { + int x = e.getX(); + int y = e.getY(); + + int tabIndex = tabForCoordinate( tabPane, x, y ); + + setRolloverTab( tabIndex ); + + // check whether mouse hit tab close area + boolean hitClose = isTabClosable( tabIndex ) + ? getTabCloseHitArea( tabIndex ).contains( x, y ) + : false; + if( pressed ) + pressedTabIndex = hitClose ? tabIndex : -1; + setRolloverTabClose( hitClose ); + setPressedTabClose( hitClose && tabIndex == pressedTabIndex ); } //---- interface PropertyChangeListener ---- @@ -1395,19 +1604,21 @@ public class FlatTabbedPaneUI @Override public void propertyChange( PropertyChangeEvent e ) { // invoke delegate listener - switch( e.getPropertyName() ) { - case "tabPlacement": - case "opaque": - case "background": - case "indexForTabComponent": - runWithOriginalLayoutManager( () -> { - propertyChangeDelegate.propertyChange( e ); - } ); - break; + if( e.getSource() instanceof JTabbedPane ) { + switch( e.getPropertyName() ) { + case "tabPlacement": + case "opaque": + case "background": + case "indexForTabComponent": + runWithOriginalLayoutManager( () -> { + propertyChangeDelegate.propertyChange( e ); + } ); + break; - default: - propertyChangeDelegate.propertyChange( e ); - break; + default: + propertyChangeDelegate.propertyChange( e ); + break; + } } // handle event @@ -1426,6 +1637,7 @@ public class FlatTabbedPaneUI case TABBED_PANE_HAS_FULL_BORDER: case TABBED_PANE_TAB_HEIGHT: case TABBED_PANE_HIDDEN_TABS_NAVIGATION: + case TABBED_PANE_TAB_CLOSABLE: tabPane.revalidate(); tabPane.repaint(); break; @@ -1470,6 +1682,22 @@ public class FlatTabbedPaneUI @Override public void componentMoved( ComponentEvent e ) {} @Override public void componentShown( ComponentEvent e ) {} @Override public void componentHidden( ComponentEvent e ) {} + + //---- interface ContainerListener ---- + + @Override + public void componentAdded( ContainerEvent e ) { + Component c = e.getChild(); + if( !(c instanceof UIResource) ) + c.addPropertyChangeListener( TABBED_PANE_TAB_CLOSABLE, this ); + } + + @Override + public void componentRemoved( ContainerEvent e ) { + Component c = e.getChild(); + if( !(c instanceof UIResource) ) + c.removePropertyChangeListener( TABBED_PANE_TAB_CLOSABLE, this ); + } } //---- class FlatTabbedPaneLayout ----------------------------------------- @@ -1485,7 +1713,7 @@ public class FlatTabbedPaneUI Insets insets = tabPane.getInsets(); int tabPlacement = tabPane.getTabPlacement(); Insets tabAreaInsets = getTabAreaInsets( tabPlacement ); - boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight(); + boolean leftToRight = isLeftToRight(); // layout leading and trailing components in tab area if( tabPlacement == TOP || tabPlacement == BOTTOM ) { @@ -1556,7 +1784,7 @@ public class FlatTabbedPaneUI * Layout manager used for scroll tab layout policy. *

* Although this class delegates all methods to the original layout manager - * {@link BasicTabbedPaneUI.TabbedPaneScrollLayout}, which extends + * {@code BasicTabbedPaneUI.TabbedPaneScrollLayout}, which extends * {@link BasicTabbedPaneUI.TabbedPaneLayout}, it is necessary that this class * also extends {@link TabbedPaneLayout} to avoid a {@code ClassCastException} * in {@link BasicTabbedPaneUI}.ensureCurrentLayout(). @@ -1624,7 +1852,7 @@ public class FlatTabbedPaneUI Dimension moreButtonSize = useMoreButton ? moreTabsButton.getPreferredSize() : null; Dimension backwardButtonSize = useMoreButton ? null : backwardButton.getPreferredSize(); Dimension forwardButtonSize = useMoreButton ? null : forwardButton.getPreferredSize(); - boolean leftToRight = tabPane.getComponentOrientation().isLeftToRight(); + boolean leftToRight = isLeftToRight(); boolean buttonsVisible = false; // TabbedPaneScrollLayout adds tabAreaInsets to tab coordinates, diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties index a334cd0c..85e6d7c2 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties @@ -258,6 +258,13 @@ TabbedPane.hoverColor=#2e3133 TabbedPane.focusColor=#3d4b5c TabbedPane.contentAreaColor=#646464 +TabbedPane.closeBackground=null +TabbedPane.closeForeground=@disabledText +TabbedPane.closeHoverBackground=lighten($TabbedPane.hoverColor,10%) +TabbedPane.closeHoverForeground=@foreground +TabbedPane.closePressedBackground=lighten($TabbedPane.hoverColor,15%) +TabbedPane.closePressedForeground=$TabbedPane.closeHoverForeground + #---- Table ---- 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 8559cc12..6a05180c 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -552,6 +552,13 @@ TabbedPane.shadow=@background TabbedPane.contentBorderInsets=null TabbedPane.hiddenTabsNavigation=moreTabsButton +TabbedPane.closeIcon=com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon +TabbedPane.closeSize=16,16 +TabbedPane.closeArc=4 +TabbedPane.closeCrossPlainSize={float}7.5 +TabbedPane.closeCrossFilledSize=$TabbedPane.closeCrossPlainSize +TabbedPane.closeCrossLineWidth={float}1 + #---- Table ---- diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties index 8870bdb8..9bc0e28c 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties @@ -270,6 +270,13 @@ TabbedPane.hoverColor=#d9d9d9 TabbedPane.focusColor=#dae4ed TabbedPane.contentAreaColor=#bfbfbf +TabbedPane.closeBackground=null +TabbedPane.closeForeground=@disabledText +TabbedPane.closeHoverBackground=darken($TabbedPane.hoverColor,10%) +TabbedPane.closeHoverForeground=@foreground +TabbedPane.closePressedBackground=darken($TabbedPane.hoverColor,15%) +TabbedPane.closePressedForeground=$TabbedPane.closeHoverForeground + #---- Table ---- diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java index 4a9d28d2..a60c45ef 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java @@ -95,6 +95,19 @@ public class TriStateCheckBox repaint(); } + public Boolean getValue() { + switch( state ) { + default: + case INDETERMINATE: return null; + case SELECTED: return true; + case UNSELECTED: return false; + } + } + + public void setValue( Boolean value ) { + setState( value == null ? State.INDETERMINATE : (value ? State.SELECTED : State.UNSELECTED) ); + } + public boolean isThirdStateEnabled() { return thirdStateEnabled; } 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 cd66c85b..5308a7a0 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt @@ -905,6 +905,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI #---- TabbedPane ---- TabbedPane.background #3c3f41 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeArc 4 +TabbedPane.closeCrossFilledSize 7.5 +TabbedPane.closeCrossLineWidth 1.0 +TabbedPane.closeCrossPlainSize 7.5 +TabbedPane.closeForeground #888888 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeHoverBackground #464b4e javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeHoverForeground #bbbbbb javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon [UI] +TabbedPane.closePressedBackground #52585b javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closePressedForeground #bbbbbb javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeSize 16,16 javax.swing.plaf.DimensionUIResource [UI] TabbedPane.contentAreaColor #646464 javax.swing.plaf.ColorUIResource [UI] TabbedPane.contentOpaque true TabbedPane.contentSeparatorHeight 1 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 e179a14e..df5e628a 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt @@ -910,6 +910,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI #---- TabbedPane ---- TabbedPane.background #f2f2f2 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeArc 4 +TabbedPane.closeCrossFilledSize 7.5 +TabbedPane.closeCrossLineWidth 1.0 +TabbedPane.closeCrossPlainSize 7.5 +TabbedPane.closeForeground #8c8c8c javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeHoverBackground #c0c0c0 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeHoverForeground #000000 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon [UI] +TabbedPane.closePressedBackground #b3b3b3 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closePressedForeground #000000 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeSize 16,16 javax.swing.plaf.DimensionUIResource [UI] TabbedPane.contentAreaColor #bfbfbf javax.swing.plaf.ColorUIResource [UI] TabbedPane.contentOpaque true TabbedPane.contentSeparatorHeight 1 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 d49664a1..aab155aa 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt @@ -898,6 +898,17 @@ SplitPaneUI com.formdev.flatlaf.ui.FlatSplitPaneUI #---- TabbedPane ---- TabbedPane.background #ccffcc javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeArc 999 +TabbedPane.closeCrossFilledSize 6.5 +TabbedPane.closeCrossLineWidth 2.0 +TabbedPane.closeCrossPlainSize 12.0 +TabbedPane.closeForeground #ff0000 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeHoverBackground #aa0000 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeHoverForeground #ffffff javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon [UI] +TabbedPane.closePressedBackground #00ff00 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closePressedForeground #000000 javax.swing.plaf.ColorUIResource [UI] +TabbedPane.closeSize 16,16 javax.swing.plaf.DimensionUIResource [UI] TabbedPane.contentAreaColor #bbbbbb javax.swing.plaf.ColorUIResource [UI] TabbedPane.contentOpaque true TabbedPane.contentSeparatorHeight 1 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 d7386af4..b6da5b97 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 @@ -18,9 +18,12 @@ package com.formdev.flatlaf.testing; import static com.formdev.flatlaf.FlatClientProperties.*; import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.function.BiConsumer; import javax.swing.*; import javax.swing.border.*; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.extras.TriStateCheckBox; import com.formdev.flatlaf.icons.FlatInternalFrameCloseIcon; import com.formdev.flatlaf.util.ScaledImageIcon; import com.jgoodies.forms.layout.*; @@ -49,6 +52,9 @@ public class FlatContainerTest addInitialTabs( tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 ); initialTabCount = tabbedPane1.getTabCount(); + tabsClosableCheckBox.setSelected( true ); + tabsClosableChanged(); + tabScrollCheckBox.setSelected( true ); tabScrollChanged(); } @@ -236,10 +242,7 @@ public class FlatContainerTest case "arrowButtons": value = TABBED_PANE_HIDDEN_TABS_NAVIGATION_ARROW_BUTTONS; break; } - tabbedPane1.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value ); - tabbedPane2.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value ); - tabbedPane3.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value ); - tabbedPane4.putClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value ); + putTabbedPanesClientProperty( TABBED_PANE_HIDDEN_TABS_NAVIGATION, value ); } private void tabBackForegroundChanged() { @@ -270,6 +273,32 @@ public class FlatContainerTest } } + private void tabsClosableChanged() { + boolean closable = tabsClosableCheckBox.isSelected(); + putTabbedPanesClientProperty( TABBED_PANE_TAB_CLOSABLE, closable ? true : null ); + + if( closable ) { + putTabbedPanesClientProperty( TABBED_PANE_TAB_CLOSE_CALLBACK, + (BiConsumer) (tabbedPane, tabIndex) -> { + AWTEvent e = EventQueue.getCurrentEvent(); + int modifiers = (e instanceof MouseEvent) ? ((MouseEvent)e).getModifiers() : 0; + JOptionPane.showMessageDialog( this, "Closed tab '" + tabbedPane.getTitleAt( tabIndex ) + "'." + + "\n\n(modifiers: " + MouseEvent.getMouseModifiersText( modifiers ) + ")", + "Tab Closed", JOptionPane.PLAIN_MESSAGE ); + } ); + } + } + + private void secondTabClosableChanged() { + Boolean value = secondTabClosableCheckBox.getValue(); + + JTabbedPane[] tabbedPanes = new JTabbedPane[] { tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 }; + for( JTabbedPane tabbedPane : tabbedPanes ) { + Component c = tabbedPane.getComponentAt( 1 ); + ((JComponent)c).putClientProperty( TABBED_PANE_TAB_CLOSABLE, value ); + } + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents JPanel panel9 = new JPanel(); @@ -306,6 +335,8 @@ public class FlatContainerTest tabBackForegroundCheckBox = new JCheckBox(); leadingComponentCheckBox = new JCheckBox(); trailingComponentCheckBox = new JCheckBox(); + tabsClosableCheckBox = new JCheckBox(); + secondTabClosableCheckBox = new TriStateCheckBox(); CellConstraints cc = new CellConstraints(); //======== this ======== @@ -518,6 +549,16 @@ public class FlatContainerTest trailingComponentCheckBox.setText("Trailing"); trailingComponentCheckBox.addActionListener(e -> trailingComponentChanged()); panel14.add(trailingComponentCheckBox, "cell 1 3"); + + //---- tabsClosableCheckBox ---- + tabsClosableCheckBox.setText("Tabs closable"); + tabsClosableCheckBox.addActionListener(e -> tabsClosableChanged()); + panel14.add(tabsClosableCheckBox, "cell 2 3"); + + //---- secondTabClosableCheckBox ---- + secondTabClosableCheckBox.setText("Second Tab closable"); + secondTabClosableCheckBox.addActionListener(e -> secondTabClosableChanged()); + panel14.add(secondTabClosableCheckBox, "cell 3 3"); } panel9.add(panel14, cc.xywh(1, 11, 3, 1)); } @@ -545,6 +586,8 @@ public class FlatContainerTest private JCheckBox tabBackForegroundCheckBox; private JCheckBox leadingComponentCheckBox; private JCheckBox trailingComponentCheckBox; + private JCheckBox tabsClosableCheckBox; + private TriStateCheckBox secondTabClosableCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables //---- class Tab1Panel ---------------------------------------------------- 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 c9e16cf6..9e64f1f6 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 @@ -327,6 +327,26 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 3" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "tabsClosableCheckBox" + "text": "Tabs closable" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabsClosableChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 3" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.TriStateCheckBox" ) { + name: "secondTabClosableCheckBox" + "text": "Second Tab closable" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "secondTabClosableChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 3 3" + } ) }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) { "gridY": 11 "gridWidth": 3 diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties index e8db2877..e508b905 100644 --- a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties +++ b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties @@ -284,6 +284,18 @@ TabbedPane.focusColor=#ddd TabbedPane.tabSeparatorColor=#00f TabbedPane.contentAreaColor=#bbb +TabbedPane.closeSize=16,16 +TabbedPane.closeArc=999 +TabbedPane.closeCrossPlainSize={float}12 +TabbedPane.closeCrossFilledSize={float}6.5 +TabbedPane.closeCrossLineWidth={float}2 +#TabbedPane.closeBackground=#faa +TabbedPane.closeForeground=#f00 +TabbedPane.closeHoverBackground=#a00 +TabbedPane.closeHoverForeground=#fff +TabbedPane.closePressedBackground=#0f0 +TabbedPane.closePressedForeground=#000 + #---- Table ---- 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 a8742276..c6c01066 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 @@ -634,6 +634,17 @@ SplitPaneDivider.oneTouchHoverArrowColor SplitPaneUI TabbedPane.ancestorInputMap TabbedPane.background +TabbedPane.closeArc +TabbedPane.closeCrossFilledSize +TabbedPane.closeCrossLineWidth +TabbedPane.closeCrossPlainSize +TabbedPane.closeForeground +TabbedPane.closeHoverBackground +TabbedPane.closeHoverForeground +TabbedPane.closeIcon +TabbedPane.closePressedBackground +TabbedPane.closePressedForeground +TabbedPane.closeSize TabbedPane.contentAreaColor TabbedPane.contentOpaque TabbedPane.contentSeparatorHeight