From 7bec5ec6dc81d10f0d82ff5774c7e60d51006f18 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 13 Aug 2023 14:01:09 +0200 Subject: [PATCH 1/5] ScrollPane: support rounded border --- .../flatlaf/ui/FlatScrollPaneBorder.java | 63 +++ .../formdev/flatlaf/ui/FlatScrollPaneUI.java | 93 +++- .../com/formdev/flatlaf/FlatLaf.properties | 5 +- .../flatlaf/ui/TestFlatStyleableInfo.java | 12 +- .../flatlaf/ui/TestFlatStyleableValue.java | 22 +- .../formdev/flatlaf/ui/TestFlatStyling.java | 10 +- .../dumps/uidefaults/FlatDarculaLaf_1.8.0.txt | 8 +- .../dumps/uidefaults/FlatDarkLaf_1.8.0.txt | 5 +- .../uidefaults/FlatIntelliJLaf_1.8.0.txt | 8 +- .../dumps/uidefaults/FlatLightLaf_1.8.0.txt | 5 +- .../dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt | 5 +- .../uidefaults/FlatMacLightLaf_1.8.0.txt | 5 +- .../dumps/uidefaults/FlatTestLaf_1.8.0.txt | 5 +- .../testing/FlatRoundedScrollPaneTest.java | 488 ++++++++++++++++++ .../testing/FlatRoundedScrollPaneTest.jfd | 230 +++++++++ .../flatlaf/testing/FlatTestFrame.java | 8 + .../flatlaf/testing/FlatTestLaf.properties | 5 + .../flatlaf/themeeditor/FlatLafUIKeys.txt | 1 + 18 files changed, 942 insertions(+), 36 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java create mode 100644 flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java new file mode 100644 index 000000000..845ea12ff --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 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 + * + * http://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.ui; + +import java.awt.Component; +import java.awt.Insets; +import javax.swing.UIManager; +import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.util.UIScale; + +/** + * Border for {@link javax.swing.JScrollPane}. + * + * @uiDefault ScrollPane.arc int + + * @author Karl Tauber + * @since 3.3 + */ +public class FlatScrollPaneBorder + extends FlatBorder +{ + @Styleable protected int arc = UIManager.getInt( "ScrollPane.arc" ); + + @Override + public Insets getBorderInsets( Component c, Insets insets ) { + insets = super.getBorderInsets( c, insets ); + + // if view is rounded, increase left and right insets to avoid that the viewport + // is painted over the rounded border on the corners + int arc = getArc( c ); + if( arc > 0 ) { + // increase insets by radius minus lineWidth because radius is measured + // from the outside of the line, but insets from super include lineWidth + int padding = UIScale.scale( (arc / 2) - getLineWidth( c ) ); + insets.left += padding; + insets.right += padding; + } + + return insets; + } + + @Override + protected int getArc( Component c ) { + if( isCellEditor( c ) ) + return 0; + + return arc; + } +} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index a62e83698..bb65fac89 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -17,9 +17,12 @@ package com.formdev.flatlaf.ui; import java.awt.Component; +import java.awt.Container; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Insets; import java.awt.KeyboardFocusManager; +import java.awt.LayoutManager; import java.awt.Rectangle; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; @@ -41,16 +44,19 @@ import javax.swing.JViewport; import javax.swing.LookAndFeel; import javax.swing.ScrollPaneConstants; +import javax.swing.ScrollPaneLayout; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicScrollPaneUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; +import com.formdev.flatlaf.util.UIScale; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}. @@ -97,7 +103,13 @@ private void installUIImpl( JComponent c ) { super.installUI( c ); int focusWidth = UIManager.getInt( "Component.focusWidth" ); - LookAndFeel.installProperty( c, "opaque", focusWidth == 0 ); + int arc = UIManager.getInt( "ScrollPane.arc" ); + LookAndFeel.installProperty( c, "opaque", focusWidth == 0 && arc == 0 ); + + // install layout manager + LayoutManager layout = c.getLayout(); + if( layout != null && layout.getClass() == ScrollPaneLayout.UIResource.class ) + c.setLayout( createScrollPaneLayout() ); installStyle(); @@ -108,6 +120,10 @@ private void installUIImpl( JComponent c ) { public void uninstallUI( JComponent c ) { MigLayoutVisualPadding.uninstall( scrollpane ); + // uninstall layout manager + if( c.getLayout() instanceof FlatScrollPaneLayout ) + c.setLayout( new ScrollPaneLayout.UIResource() ); + super.uninstallUI( c ); oldStyleValues = null; @@ -130,6 +146,13 @@ protected void uninstallListeners( JComponent c ) { handler = null; } + /** + * @since 3.3 + */ + protected FlatScrollPaneLayout createScrollPaneLayout() { + return new FlatScrollPaneLayout(); + } + @Override protected MouseWheelListener createMouseWheelListener() { MouseWheelListener superListener = super.createMouseWheelListener(); @@ -290,8 +313,7 @@ protected PropertyChangeListener createPropertyChangeListener() { Object corner = e.getNewValue(); if( corner instanceof JButton && ((JButton)corner).getBorder() instanceof FlatButtonBorder && - scrollpane.getViewport() != null && - scrollpane.getViewport().getView() instanceof JTable ) + getView( scrollpane ) instanceof JTable ) { ((JButton)corner).setBorder( BorderFactory.createEmptyBorder() ); ((JButton)corner).setFocusable( false ); @@ -334,9 +356,10 @@ protected void applyStyle( Object style ) { /** @since 2 */ protected Object applyStyleProperty( String key, Object value ) { - if( key.equals( "focusWidth" ) ) { + if( key.equals( "focusWidth" ) || key.equals( "arc" ) ) { int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" ); - LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 ); + int arc = (value instanceof Integer) ? (int) value : UIManager.getInt( "ScrollPane.arc" ); + LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 && arc == 0 ); } if( borderShared == null ) @@ -402,13 +425,26 @@ public void update( Graphics g, JComponent c ) { c.getHeight() - insets.top - insets.bottom ); } + // if view is rounded, paint rounded background with view background color + // to ensure that free areas at left and right have same color as view + Component view; + float arc = getBorderArc( scrollpane ); + if( arc > 0 && (view = getView( scrollpane )) != null ) { + float focusWidth = FlatUIUtils.getBorderFocusWidth( c ); + + g.setColor( view.getBackground() ); + + Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g ); + FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc ); + FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); + } + paint( g, c ); } /** @since 1.3 */ public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) { - JViewport viewport = scrollPane.getViewport(); - Component view = (viewport != null) ? viewport.getView() : null; + Component view = getView( scrollPane ); if( view == null ) return false; @@ -428,6 +464,18 @@ public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) { return false; } + private static Component getView( JScrollPane scrollPane ) { + JViewport viewport = scrollPane.getViewport(); + return (viewport != null) ? viewport.getView() : null; + } + + private static float getBorderArc( JScrollPane scrollPane ) { + Border border = scrollPane.getBorder(); + return (border instanceof FlatScrollPaneBorder) + ? UIScale.scale( (float) ((FlatScrollPaneBorder)border).getArc( scrollPane ) ) + : 0; + } + //---- class Handler ------------------------------------------------------ /** @@ -459,4 +507,35 @@ public void focusLost( FocusEvent e ) { scrollpane.repaint(); } } + + //---- class FlatScrollPaneLayout ----------------------------------------- + + /** + * @since 3.3 + */ + protected static class FlatScrollPaneLayout + extends ScrollPaneLayout.UIResource + { + @Override + public void layoutContainer( Container parent ) { + super.layoutContainer( parent ); + + JScrollPane scrollPane = (JScrollPane) parent; + float arc = getBorderArc( scrollPane ); + if( arc > 0 ) { + JScrollBar vsb = getVerticalScrollBar(); + if( vsb != null && vsb.isVisible() ) { + // move vertical scrollbar to trailing edge + int padding = Math.round( (arc / 2) - FlatUIUtils.getBorderLineWidth( scrollPane ) ); + Insets insets = parent.getInsets(); + Rectangle r = vsb.getBounds(); + int y = Math.max( r.y, insets.top + padding ); + int y2 = Math.min( r.y + r.height, parent.getHeight() - insets.bottom - padding ); + boolean ltr = parent.getComponentOrientation().isLeftToRight(); + + vsb.setBounds( r.x + (ltr ? padding : -padding), y, r.width, y2 - y ); + } + } + } + } } 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 367637140..a620989f4 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -597,10 +597,11 @@ ScrollBar.allowsAbsolutePositioning = true #---- ScrollPane ---- -ScrollPane.border = com.formdev.flatlaf.ui.FlatBorder +ScrollPane.border = com.formdev.flatlaf.ui.FlatScrollPaneBorder ScrollPane.background = $ScrollBar.track ScrollPane.fillUpperCorner = true ScrollPane.smoothScrolling = true +ScrollPane.arc = 0 #---- SearchField ---- @@ -731,7 +732,7 @@ Table.showVerticalLines = false Table.showTrailingVerticalLine = false Table.consistentHomeEndKeyBehavior = true Table.intercellSpacing = 0,0 -Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatBorder +Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatScrollPaneBorder Table.ascendingSortIcon = com.formdev.flatlaf.icons.FlatAscendingSortIcon Table.descendingSortIcon = com.formdev.flatlaf.icons.FlatDescendingSortIcon Table.sortIconColor = @icon diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java index 028030d81..948fb7715 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java @@ -601,7 +601,7 @@ void scrollPane() { ); // border - flatBorder( expected ); + flatScrollPaneBorder( expected ); assertMapEquals( expected, ui.getStyleableInfos( c ) ); } @@ -1005,17 +1005,23 @@ private void flatRoundBorder( Map> expected ) { expectedMap( expected, "arc", int.class, - "roundRect", Boolean.class ); } + private void flatScrollPaneBorder( Map> expected ) { + flatBorder( expected ); + + expectedMap( expected, + "arc", int.class + ); + } + private void flatTextBorder( Map> expected ) { flatBorder( expected ); expectedMap( expected, "arc", int.class, - "roundRect", Boolean.class ); } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java index 7e5b11277..586366944 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java @@ -625,7 +625,7 @@ void scrollPane() { FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI(); // border - flatBorder( c, ui ); + flatScrollPaneBorder( c, ui ); testBoolean( c, ui, "showButtons", true ); } @@ -965,15 +965,19 @@ private void flatRoundBorder( JComponent c, StyleableUI ui ) { flatBorder( c, ui ); testInteger( c, ui, "arc", 123 ); - testBoolean( c, ui, "roundRect", true ); } - private void flatTextBorder( JComponent c, StyleableUI ui ) { + private void flatScrollPaneBorder( JComponent c, StyleableUI ui ) { flatBorder( c, ui ); testInteger( c, ui, "arc", 123 ); + } + private void flatTextBorder( JComponent c, StyleableUI ui ) { + flatBorder( c, ui ); + + testInteger( c, ui, "arc", 123 ); testBoolean( c, ui, "roundRect", true ); } @@ -1034,6 +1038,17 @@ void flatRoundBorder() { // FlatRoundBorder extends FlatBorder flatBorder( border ); + testValue( border, "arc", 6 ); + testValue( border, "roundRect", true ); + } + + @Test + void flatScrollPaneBorder() { + FlatScrollPaneBorder border = new FlatScrollPaneBorder(); + + // FlatScrollPaneBorder extends FlatBorder + flatBorder( border ); + testValue( border, "arc", 6 ); } @@ -1045,6 +1060,7 @@ void flatTextBorder() { flatBorder( border ); testValue( border, "arc", 6 ); + testValue( border, "roundRect", true ); } @Test diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java index a336b3800..116376f0b 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java @@ -760,7 +760,7 @@ void scrollPane() { FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI(); // border - flatBorder( style -> ui.applyStyle( style ) ); + flatScrollPaneBorder( style -> ui.applyStyle( style ) ); ui.applyStyle( "showButtons: true" ); @@ -1234,15 +1234,19 @@ private void flatRoundBorder( Consumer applyStyle ) { flatBorder( applyStyle ); applyStyle.accept( "arc: 6" ); - applyStyle.accept( "roundRect: true" ); } - private void flatTextBorder( Consumer applyStyle ) { + private void flatScrollPaneBorder( Consumer applyStyle ) { flatBorder( applyStyle ); applyStyle.accept( "arc: 6" ); + } + private void flatTextBorder( Consumer applyStyle ) { + flatBorder( applyStyle ); + + applyStyle.accept( "arc: 6" ); applyStyle.accept( "roundRect: true" ); } diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarculaLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarculaLaf_1.8.0.txt index 8291260af..cba7171fc 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarculaLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarculaLaf_1.8.0.txt @@ -87,8 +87,8 @@ #---- ScrollPane ---- -- ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] -+ ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +- ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ++ ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] #---- Spinner ---- @@ -99,8 +99,8 @@ #---- Table ---- -- Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] -+ Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +- Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ++ Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] #---- TextField ---- diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt index d2dcd9c6f..2aa1aa492 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt @@ -906,8 +906,9 @@ ScrollBarUI com.formdev.flatlaf.ui.FlatScrollBarUI #---- ScrollPane ---- +ScrollPane.arc 0 ScrollPane.background #3e4244 HSL 200 5 25 com.formdev.flatlaf.util.DerivedColor [UI] lighten(1%) -ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] +ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ScrollPane.fillUpperCorner true ScrollPane.font [active] $defaultFont [UI] ScrollPane.foreground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] @@ -1118,7 +1119,7 @@ Table.foreground #bbbbbb HSL 0 0 73 javax.swing.plaf.Colo Table.gridColor #5a5e60 HSL 200 3 36 javax.swing.plaf.ColorUIResource [UI] Table.intercellSpacing 0,0 javax.swing.plaf.DimensionUIResource [UI] Table.rowHeight 20 -Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] +Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] Table.selectionBackground #4b6eaf HSL 219 40 49 javax.swing.plaf.ColorUIResource [UI] Table.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] Table.selectionInactiveBackground #0f2a3d HSL 205 61 15 javax.swing.plaf.ColorUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatIntelliJLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatIntelliJLaf_1.8.0.txt index f579ab893..7b2d2fd25 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatIntelliJLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatIntelliJLaf_1.8.0.txt @@ -107,8 +107,8 @@ #---- ScrollPane ---- -- ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] -+ ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +- ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ++ ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] #---- Spinner ---- @@ -119,8 +119,8 @@ #---- Table ---- -- Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] -+ Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +- Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ++ Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] #---- TextField ---- diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt index 7871f3ee7..33980206c 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt @@ -911,8 +911,9 @@ ScrollBarUI com.formdev.flatlaf.ui.FlatScrollBarUI #---- ScrollPane ---- +ScrollPane.arc 0 ScrollPane.background #f5f5f5 HSL 0 0 96 com.formdev.flatlaf.util.DerivedColor [UI] lighten(1%) -ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] +ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ScrollPane.fillUpperCorner true ScrollPane.font [active] $defaultFont [UI] ScrollPane.foreground #000000 HSL 0 0 0 javax.swing.plaf.ColorUIResource [UI] @@ -1123,7 +1124,7 @@ Table.foreground #000000 HSL 0 0 0 javax.swing.plaf.Colo Table.gridColor #ebebeb HSL 0 0 92 javax.swing.plaf.ColorUIResource [UI] Table.intercellSpacing 0,0 javax.swing.plaf.DimensionUIResource [UI] Table.rowHeight 20 -Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] +Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] Table.selectionBackground #2675bf HSL 209 67 45 javax.swing.plaf.ColorUIResource [UI] Table.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] Table.selectionInactiveBackground #d3d3d3 HSL 0 0 83 javax.swing.plaf.ColorUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt index 41e289f92..3f7fd9b9f 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt @@ -915,8 +915,9 @@ ScrollBarUI com.formdev.flatlaf.ui.FlatScrollBarUI #---- ScrollPane ---- +ScrollPane.arc 0 ScrollPane.background #282828 HSL 0 0 16 javax.swing.plaf.ColorUIResource [UI] -ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ScrollPane.fillUpperCorner true ScrollPane.font [active] $defaultFont [UI] ScrollPane.foreground #dddddd HSL 0 0 87 javax.swing.plaf.ColorUIResource [UI] @@ -1128,7 +1129,7 @@ Table.foreground #dddddd HSL 0 0 87 javax.swing.plaf.Colo Table.gridColor #3c3c3c HSL 0 0 24 javax.swing.plaf.ColorUIResource [UI] Table.intercellSpacing 0,0 javax.swing.plaf.DimensionUIResource [UI] Table.rowHeight 20 -Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] Table.selectionBackground #0054cc HSL 215 100 40 javax.swing.plaf.ColorUIResource [UI] Table.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] Table.selectionInactiveBackground #464646 HSL 0 0 27 javax.swing.plaf.ColorUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt index e735c0915..be81c6560 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatMacLightLaf_1.8.0.txt @@ -919,8 +919,9 @@ ScrollBarUI com.formdev.flatlaf.ui.FlatScrollBarUI #---- ScrollPane ---- +ScrollPane.arc 0 ScrollPane.background #fafafa HSL 0 0 98 javax.swing.plaf.ColorUIResource [UI] -ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +ScrollPane.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ScrollPane.fillUpperCorner true ScrollPane.font [active] $defaultFont [UI] ScrollPane.foreground #262626 HSL 0 0 15 javax.swing.plaf.ColorUIResource [UI] @@ -1132,7 +1133,7 @@ Table.foreground #262626 HSL 0 0 15 javax.swing.plaf.Colo Table.gridColor #ebebeb HSL 0 0 92 javax.swing.plaf.ColorUIResource [UI] Table.intercellSpacing 0,0 javax.swing.plaf.DimensionUIResource [UI] Table.rowHeight 20 -Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatBorder [UI] +Table.scrollPaneBorder [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] Table.selectionBackground #005fe6 HSL 215 100 45 javax.swing.plaf.ColorUIResource [UI] Table.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] Table.selectionInactiveBackground #dcdcdc HSL 0 0 86 javax.swing.plaf.ColorUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt index 3ff67dd1c..d15bd88dd 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt @@ -932,8 +932,9 @@ ScrollBarUI com.formdev.flatlaf.ui.FlatScrollBarUI #---- ScrollPane ---- +ScrollPane.arc 20 ScrollPane.background #88ff88 HSL 120 100 77 javax.swing.plaf.ColorUIResource [UI] -ScrollPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] +ScrollPane.border [lazy] 1,10,1,10 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ScrollPane.fillUpperCorner true ScrollPane.font [active] $defaultFont [UI] ScrollPane.foreground #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI] @@ -1154,7 +1155,7 @@ Table.foreground #ff0000 HSL 0 100 50 javax.swing.plaf.Colo Table.gridColor #00ffff HSL 180 100 50 javax.swing.plaf.ColorUIResource [UI] Table.intercellSpacing 0,0 javax.swing.plaf.DimensionUIResource [UI] Table.rowHeight 25 -Table.scrollPaneBorder [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatBorder [UI] +Table.scrollPaneBorder [lazy] 1,10,1,10 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] Table.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI] Table.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI] Table.selectionInactiveBackground #888888 HSL 0 0 53 javax.swing.plaf.ColorUIResource [UI] diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java new file mode 100644 index 000000000..967b13582 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java @@ -0,0 +1,488 @@ +/* + * Copyright 2023 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.testing; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.tree.*; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.util.UIScale; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatRoundedScrollPaneTest + extends FlatTestPanel +{ + private JScrollPane[] allJScrollPanes; + + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, "FlatRoundedScrollPaneTest" ); + frame.useApplyComponentOrientation = true; + frame.showFrame( FlatRoundedScrollPaneTest::new ); + } ); + } + + FlatRoundedScrollPaneTest() { + initComponents(); + + allJScrollPanes = new JScrollPane[] { + listScrollPane, treeScrollPane, tableScrollPane, + textAreaScrollPane, textPaneScrollPane, editorPaneScrollPane, customScrollPane + }; + + ArrayList items = new ArrayList<>(); + for( char ch = '0'; ch < 'z'; ch++ ) { + if( (ch > '9' && ch < 'A') || (ch > 'Z' && ch < 'a') ) + continue; + + char[] chars = new char[ch - '0' + 1]; + Arrays.fill( chars, ch ); + items.add( new String( chars ) ); + } + + // list model + list.setModel( new AbstractListModel() { + @Override + public int getSize() { + return items.size(); + } + @Override + public String getElementAt( int index ) { + return items.get( index ); + } + } ); + + // tree model + DefaultMutableTreeNode root = new DefaultMutableTreeNode( items.get( 0 ) ); + DefaultMutableTreeNode last = null; + for( int i = 1; i < items.size(); i++ ) { + DefaultMutableTreeNode node = new DefaultMutableTreeNode( items.get( i ) ); + if( i % 5 == 1 ) { + root.add( node ); + last = node; + } else + last.add( node ); + } + tree.setModel( new DefaultTreeModel( root ) ); + for( int row = tree.getRowCount() - 1; row >= 0; row-- ) + tree.expandRow( row ); + + // table model + table.setModel( new AbstractTableModel() { + @Override + public int getRowCount() { + return items.size(); + } + @Override + public int getColumnCount() { + return 4; + } + @Override + public Object getValueAt( int rowIndex, int columnIndex ) { + if( columnIndex > 0 ) + rowIndex = (items.size() + rowIndex - ((items.size() / 4) * columnIndex)) % items.size(); + return items.get( rowIndex ); + } + } ); + + // select some rows to better see smooth scrolling issues + for( int i = 5; i < items.size(); i += 10 ) { + list.addSelectionInterval( i, i ); + tree.addSelectionInterval( i, i ); + table.addRowSelectionInterval( i, i ); + } + + // text components + String longText = items.stream().collect( Collectors.joining( " " ) ) + ' ' + + items.stream().limit( 20 ).collect( Collectors.joining( " " ) ); + String text = items.stream().collect( Collectors.joining( "\n" ) ) + '\n'; + textArea.setText( longText + '\n' + text ); + textPane.setText( text ); + editorPane.setText( text ); + + textArea.select( 0, 0 ); + textPane.select( 0, 0 ); + editorPane.select( 0, 0 ); + + arcSliderChanged(); + + EventQueue.invokeLater( () -> { + EventQueue.invokeLater( () -> { + list.requestFocusInWindow(); + } ); + } ); + } + + private void autoResizeModeChanged() { + table.setAutoResizeMode( autoResizeModeCheckBox.isSelected() ? JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS : JTable.AUTO_RESIZE_OFF ); + } + + private void cornersChanged() { + boolean sel = cornersCheckBox.isSelected(); + for( JScrollPane scrollPane : allJScrollPanes ) { + scrollPane.setCorner( JScrollPane.UPPER_LEADING_CORNER, sel ? new Corner( Color.magenta ) : null ); + scrollPane.setCorner( JScrollPane.UPPER_TRAILING_CORNER, sel ? new Corner( Color.red ) : null ); + scrollPane.setCorner( JScrollPane.LOWER_LEADING_CORNER, sel ? new Corner( Color.cyan ) : null ); + scrollPane.setCorner( JScrollPane.LOWER_TRAILING_CORNER, sel ? new Corner( Color.pink ) : null ); + } + } + + private void columnHeaderChanged() { + boolean sel = columnHeaderCheckBox.isSelected(); + for( JScrollPane scrollPane : allJScrollPanes ) { + if( scrollPane == tableScrollPane ) + continue; + + JViewport header = null; + if( sel ) { + header = new JViewport(); + Corner view = new Corner( Color.cyan ); + view.setPreferredSize( new Dimension( 0, UIScale.scale( 20 ) ) ); + header.setView( view ); + } + scrollPane.setColumnHeader( header ); + } + } + + private void rowHeaderChanged() { + boolean sel = rowHeaderCheckBox.isSelected(); + for( JScrollPane scrollPane : allJScrollPanes ) { + JViewport header = null; + if( sel ) { + header = new JViewport(); + Corner view = new Corner( Color.yellow ); + view.setPreferredSize( new Dimension( UIScale.scale( 20 ), 0 ) ); + header.setView( view ); + } + scrollPane.setRowHeader( header ); + } + } + + private void horizontalScrollBarChanged() { + int policy = horizontalScrollBarCheckBox.isSelected() + ? JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED + : JScrollPane.HORIZONTAL_SCROLLBAR_NEVER; + + for( JScrollPane scrollPane : allJScrollPanes ) + scrollPane.setHorizontalScrollBarPolicy( policy ); + } + + private void verticalScrollBarChanged() { + int policy = verticalScrollBarCheckBox.isSelected() + ? JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED + : JScrollPane.VERTICAL_SCROLLBAR_NEVER; + + for( JScrollPane scrollPane : allJScrollPanes ) + scrollPane.setVerticalScrollBarPolicy( policy ); + } + + private void arcSliderChanged() { + int arc = arcSlider.getValue(); + for( JScrollPane scrollPane : allJScrollPanes ) + scrollPane.putClientProperty( FlatClientProperties.STYLE, "arc: " + arc ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + splitPane2 = new JSplitPane(); + panel1 = new FlatTestPanel(); + listLabel = new JLabel(); + treeLabel = new JLabel(); + tableLabel = new JLabel(); + autoResizeModeCheckBox = new JCheckBox(); + listScrollPane = new JScrollPane(); + list = new JList<>(); + treeScrollPane = new JScrollPane(); + tree = new JTree(); + tableScrollPane = new JScrollPane(); + table = new JTable(); + panel2 = new FlatTestPanel(); + textAreaLabel = new JLabel(); + textPaneLabel = new JLabel(); + editorPaneLabel = new JLabel(); + customLabel = new JLabel(); + textAreaScrollPane = new JScrollPane(); + textArea = new JTextArea(); + textPaneScrollPane = new JScrollPane(); + textPane = new JTextPane(); + editorPaneScrollPane = new JScrollPane(); + editorPane = new JEditorPane(); + customScrollPane = new JScrollPane(); + button1 = new JButton(); + panel3 = new JPanel(); + JLabel arcLabel = new JLabel(); + arcSlider = new JSlider(); + cornersCheckBox = new JCheckBox(); + columnHeaderCheckBox = new JCheckBox(); + rowHeaderCheckBox = new JCheckBox(); + horizontalScrollBarCheckBox = new JCheckBox(); + verticalScrollBarCheckBox = new JCheckBox(); + + //======== this ======== + setLayout(new MigLayout( + "ltr,insets dialog,hidemode 3", + // columns + "[200,grow,fill]", + // rows + "[grow,fill]" + + "[]")); + + //======== splitPane2 ======== + { + splitPane2.setOrientation(JSplitPane.VERTICAL_SPLIT); + splitPane2.setResizeWeight(0.5); + + //======== panel1 ======== + { + panel1.setLayout(new MigLayout( + "ltr,insets 3,hidemode 3", + // columns + "[200,grow,fill]" + + "[200,grow,fill]" + + "[200,grow,fill]" + + "[200,grow,fill]", + // rows + "[]0" + + "[200,grow,fill]")); + + //---- listLabel ---- + listLabel.setText("JList:"); + listLabel.setHorizontalTextPosition(SwingConstants.LEADING); + panel1.add(listLabel, "cell 0 0,aligny top,growy 0"); + + //---- treeLabel ---- + treeLabel.setText("JTree:"); + treeLabel.setHorizontalTextPosition(SwingConstants.LEADING); + panel1.add(treeLabel, "cell 1 0"); + + //---- tableLabel ---- + tableLabel.setText("JTable:"); + tableLabel.setHorizontalTextPosition(SwingConstants.LEADING); + panel1.add(tableLabel, "cell 2 0 2 1"); + + //---- autoResizeModeCheckBox ---- + autoResizeModeCheckBox.setText("Auto-resize mode"); + autoResizeModeCheckBox.setSelected(true); + autoResizeModeCheckBox.addActionListener(e -> autoResizeModeChanged()); + panel1.add(autoResizeModeCheckBox, "cell 2 0 2 1,alignx right,growx 0"); + + //======== listScrollPane ======== + { + listScrollPane.setViewportView(list); + } + panel1.add(listScrollPane, "cell 0 1,growx"); + + //======== treeScrollPane ======== + { + treeScrollPane.setViewportView(tree); + } + panel1.add(treeScrollPane, "cell 1 1"); + + //======== tableScrollPane ======== + { + tableScrollPane.setViewportView(table); + } + panel1.add(tableScrollPane, "cell 2 1 2 1,width 100,height 100"); + } + splitPane2.setTopComponent(panel1); + + //======== panel2 ======== + { + panel2.setLayout(new MigLayout( + "ltr,insets 3,hidemode 3", + // columns + "[200,grow,fill]" + + "[200,grow,fill]" + + "[200,grow,fill]" + + "[200,grow,fill]", + // rows + "[]0" + + "[200,grow,fill]")); + + //---- textAreaLabel ---- + textAreaLabel.setText("JTextArea:"); + textAreaLabel.setHorizontalTextPosition(SwingConstants.LEADING); + panel2.add(textAreaLabel, "cell 0 0"); + + //---- textPaneLabel ---- + textPaneLabel.setText("JTextPane:"); + textPaneLabel.setHorizontalTextPosition(SwingConstants.LEADING); + panel2.add(textPaneLabel, "cell 1 0"); + + //---- editorPaneLabel ---- + editorPaneLabel.setText("JEditorPane:"); + editorPaneLabel.setHorizontalTextPosition(SwingConstants.LEADING); + panel2.add(editorPaneLabel, "cell 2 0"); + + //---- customLabel ---- + customLabel.setText("Custom:"); + panel2.add(customLabel, "cell 3 0"); + + //======== textAreaScrollPane ======== + { + textAreaScrollPane.setViewportView(textArea); + } + panel2.add(textAreaScrollPane, "cell 0 1"); + + //======== textPaneScrollPane ======== + { + textPaneScrollPane.setViewportView(textPane); + } + panel2.add(textPaneScrollPane, "cell 1 1"); + + //======== editorPaneScrollPane ======== + { + editorPaneScrollPane.setViewportView(editorPane); + } + panel2.add(editorPaneScrollPane, "cell 2 1"); + + //======== customScrollPane ======== + { + + //---- button1 ---- + button1.setText("I'm a large button, but do not implement Scrollable interface"); + button1.setPreferredSize(new Dimension(800, 800)); + button1.setHorizontalAlignment(SwingConstants.LEADING); + button1.setVerticalAlignment(SwingConstants.TOP); + customScrollPane.setViewportView(button1); + } + panel2.add(customScrollPane, "cell 3 1"); + } + splitPane2.setBottomComponent(panel2); + } + add(splitPane2, "cell 0 0"); + + //======== panel3 ======== + { + panel3.setLayout(new MigLayout( + "hidemode 3", + // columns + "[fill]" + + "[grow,fill]para" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]" + + "[fill]", + // rows + "[]")); + + //---- arcLabel ---- + arcLabel.setText("Arc:"); + panel3.add(arcLabel, "cell 0 0"); + + //---- arcSlider ---- + arcSlider.setMaximum(40); + arcSlider.setValue(20); + arcSlider.setSnapToTicks(true); + arcSlider.setMajorTickSpacing(10); + arcSlider.setMinorTickSpacing(1); + arcSlider.setPaintTicks(true); + arcSlider.setPaintLabels(true); + arcSlider.addChangeListener(e -> arcSliderChanged()); + panel3.add(arcSlider, "cell 1 0"); + + //---- cornersCheckBox ---- + cornersCheckBox.setText("Corners"); + cornersCheckBox.addActionListener(e -> cornersChanged()); + panel3.add(cornersCheckBox, "cell 2 0"); + + //---- columnHeaderCheckBox ---- + columnHeaderCheckBox.setText("Column Header"); + columnHeaderCheckBox.addActionListener(e -> columnHeaderChanged()); + panel3.add(columnHeaderCheckBox, "cell 3 0"); + + //---- rowHeaderCheckBox ---- + rowHeaderCheckBox.setText("Row Header"); + rowHeaderCheckBox.addActionListener(e -> rowHeaderChanged()); + panel3.add(rowHeaderCheckBox, "cell 4 0"); + + //---- horizontalScrollBarCheckBox ---- + horizontalScrollBarCheckBox.setText("Horizontal ScrollBar"); + horizontalScrollBarCheckBox.setSelected(true); + horizontalScrollBarCheckBox.addActionListener(e -> horizontalScrollBarChanged()); + panel3.add(horizontalScrollBarCheckBox, "cell 5 0"); + + //---- verticalScrollBarCheckBox ---- + verticalScrollBarCheckBox.setText("Vertical ScrollBar"); + verticalScrollBarCheckBox.setSelected(true); + verticalScrollBarCheckBox.addActionListener(e -> verticalScrollBarChanged()); + panel3.add(verticalScrollBarCheckBox, "cell 6 0"); + } + add(panel3, "cell 0 1"); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JSplitPane splitPane2; + private FlatTestPanel panel1; + private JLabel listLabel; + private JLabel treeLabel; + private JLabel tableLabel; + private JCheckBox autoResizeModeCheckBox; + private JScrollPane listScrollPane; + private JList list; + private JScrollPane treeScrollPane; + private JTree tree; + private JScrollPane tableScrollPane; + private JTable table; + private FlatTestPanel panel2; + private JLabel textAreaLabel; + private JLabel textPaneLabel; + private JLabel editorPaneLabel; + private JLabel customLabel; + private JScrollPane textAreaScrollPane; + private JTextArea textArea; + private JScrollPane textPaneScrollPane; + private JTextPane textPane; + private JScrollPane editorPaneScrollPane; + private JEditorPane editorPane; + private JScrollPane customScrollPane; + private JButton button1; + private JPanel panel3; + private JSlider arcSlider; + private JCheckBox cornersCheckBox; + private JCheckBox columnHeaderCheckBox; + private JCheckBox rowHeaderCheckBox; + private JCheckBox horizontalScrollBarCheckBox; + private JCheckBox verticalScrollBarCheckBox; + // JFormDesigner - End of variables declaration //GEN-END:variables + + //---- class Corner ------------------------------------------------------- + + private static class Corner + extends JPanel + { + Corner( Color color ) { + super.setBackground( color ); + } + + @Override + public void setBackground( Color bg ) { + // do not change background when checkbox "explicit colors" is selected + } + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd new file mode 100644 index 000000000..bb602b71b --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd @@ -0,0 +1,230 @@ +JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "ltr,insets dialog,hidemode 3" + "$columnConstraints": "[200,grow,fill]" + "$rowConstraints": "[grow,fill][]" + } ) { + name: "this" + add( new FormContainer( "javax.swing.JSplitPane", new FormLayoutManager( class javax.swing.JSplitPane ) ) { + name: "splitPane2" + "orientation": 0 + "resizeWeight": 0.5 + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$columnConstraints": "[200,grow,fill][200,grow,fill][200,grow,fill][200,grow,fill]" + "$rowConstraints": "[]0[200,grow,fill]" + "$layoutConstraints": "ltr,insets 3,hidemode 3" + } ) { + name: "panel1" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "listLabel" + "text": "JList:" + "horizontalTextPosition": 10 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0,aligny top,growy 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "treeLabel" + "text": "JTree:" + "horizontalTextPosition": 10 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "tableLabel" + "text": "JTable:" + "horizontalTextPosition": 10 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0 2 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "autoResizeModeCheckBox" + "text": "Auto-resize mode" + "selected": true + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "autoResizeModeChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0 2 1,alignx right,growx 0" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "listScrollPane" + add( new FormComponent( "javax.swing.JList" ) { + name: "list" + auxiliary() { + "JavaCodeGenerator.typeParameters": "String" + "JavaCodeGenerator.variableLocal": false + } + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1,growx" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "treeScrollPane" + add( new FormComponent( "javax.swing.JTree" ) { + name: "tree" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "tableScrollPane" + add( new FormComponent( "javax.swing.JTable" ) { + name: "table" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 1 2 1,width 100,height 100" + } ) + }, new FormLayoutConstraints( class java.lang.String ) { + "value": "left" + } ) + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$columnConstraints": "[200,grow,fill][200,grow,fill][200,grow,fill][200,grow,fill]" + "$rowConstraints": "[]0[200,grow,fill]" + "$layoutConstraints": "ltr,insets 3,hidemode 3" + } ) { + name: "panel2" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "textAreaLabel" + "text": "JTextArea:" + "horizontalTextPosition": 10 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "textPaneLabel" + "text": "JTextPane:" + "horizontalTextPosition": 10 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "editorPaneLabel" + "text": "JEditorPane:" + "horizontalTextPosition": 10 + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "customLabel" + "text": "Custom:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 3 0" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "textAreaScrollPane" + add( new FormComponent( "javax.swing.JTextArea" ) { + name: "textArea" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "textPaneScrollPane" + add( new FormComponent( "javax.swing.JTextPane" ) { + name: "textPane" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "editorPaneScrollPane" + add( new FormComponent( "javax.swing.JEditorPane" ) { + name: "editorPane" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 1" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "customScrollPane" + add( new FormComponent( "javax.swing.JButton" ) { + name: "button1" + "text": "I'm a large button, but do not implement Scrollable interface" + "preferredSize": new java.awt.Dimension( 800, 800 ) + "horizontalAlignment": 10 + "verticalAlignment": 1 + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 3 1" + } ) + }, new FormLayoutConstraints( class java.lang.String ) { + "value": "right" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "hidemode 3" + "$columnConstraints": "[fill][grow,fill]para[fill][fill][fill][fill][fill]" + "$rowConstraints": "[]" + } ) { + name: "panel3" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "arcLabel" + "text": "Arc:" + auxiliary() { + "JavaCodeGenerator.variableLocal": true + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JSlider" ) { + name: "arcSlider" + "maximum": 40 + "value": 20 + "snapToTicks": true + "majorTickSpacing": 10 + "minorTickSpacing": 1 + "paintTicks": true + "paintLabels": true + addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arcSliderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "cornersCheckBox" + "text": "Corners" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "cornersChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "columnHeaderCheckBox" + "text": "Column Header" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "columnHeaderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 3 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "rowHeaderCheckBox" + "text": "Row Header" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowHeaderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "horizontalScrollBarCheckBox" + "text": "Horizontal ScrollBar" + "selected": true + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "horizontalScrollBarChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 5 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "verticalScrollBarCheckBox" + "text": "Vertical ScrollBar" + "selected": true + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "verticalScrollBarChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 6 0" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 875, 715 ) + } ) + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java index 84e794739..ecd968f18 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestFrame.java @@ -651,6 +651,14 @@ public static void updateComponentsRecur( Container container, BiConsumer Date: Sun, 13 Aug 2023 17:01:56 +0200 Subject: [PATCH 2/5] ScrollPane: improved/fixed calculation of left/right padding for rounded border --- .../flatlaf/ui/FlatScrollPaneBorder.java | 25 ++++++++-- .../formdev/flatlaf/ui/FlatScrollPaneUI.java | 14 +++--- .../testing/FlatRoundedScrollPaneTest.java | 48 +++++++++++++------ .../testing/FlatRoundedScrollPaneTest.jfd | 36 +++++++++----- 4 files changed, 85 insertions(+), 38 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java index 845ea12ff..ba4f785c7 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java @@ -41,11 +41,8 @@ public Insets getBorderInsets( Component c, Insets insets ) { // if view is rounded, increase left and right insets to avoid that the viewport // is painted over the rounded border on the corners - int arc = getArc( c ); - if( arc > 0 ) { - // increase insets by radius minus lineWidth because radius is measured - // from the outside of the line, but insets from super include lineWidth - int padding = UIScale.scale( (arc / 2) - getLineWidth( c ) ); + int padding = getLeftRightPadding( c ); + if( padding > 0 ) { insets.left += padding; insets.right += padding; } @@ -60,4 +57,22 @@ protected int getArc( Component c ) { return arc; } + + /** + * Returns the scaled left/right padding used when arc is larger than zero. + *

+ * This is the distance from the inside of the left border to the left side of the view component. + * On the right side, this is the distance between the right side of the view component and + * the vertical scrollbar. Or the inside of the right border if the scrollbar is hidden. + */ + public int getLeftRightPadding( Component c ) { + // Subtract lineWidth from radius because radius is given for the outside + // of the painted line, but insets from super already include lineWidth. + // Reduce padding by 10% to make padding slightly smaller because it is not recognizable + // when the view is minimally painted over the beginning of the border curve. + int arc = getArc( c ); + return (arc > 0) + ? Math.max( Math.round( UIScale.scale( ((arc / 2f) - getLineWidth( c )) * 0.9f ) ), 0 ) + : 0; + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index bb65fac89..c785698cb 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -521,17 +521,19 @@ public void layoutContainer( Container parent ) { super.layoutContainer( parent ); JScrollPane scrollPane = (JScrollPane) parent; - float arc = getBorderArc( scrollPane ); - if( arc > 0 ) { + Border border = scrollPane.getBorder(); + int padding; + if( border instanceof FlatScrollPaneBorder && + (padding = ((FlatScrollPaneBorder)border).getLeftRightPadding( scrollPane )) > 0 ) + { JScrollBar vsb = getVerticalScrollBar(); if( vsb != null && vsb.isVisible() ) { // move vertical scrollbar to trailing edge - int padding = Math.round( (arc / 2) - FlatUIUtils.getBorderLineWidth( scrollPane ) ); - Insets insets = parent.getInsets(); + Insets insets = scrollPane.getInsets(); Rectangle r = vsb.getBounds(); int y = Math.max( r.y, insets.top + padding ); - int y2 = Math.min( r.y + r.height, parent.getHeight() - insets.bottom - padding ); - boolean ltr = parent.getComponentOrientation().isLeftToRight(); + int y2 = Math.min( r.y + r.height, scrollPane.getHeight() - insets.bottom - padding ); + boolean ltr = scrollPane.getComponentOrientation().isLeftToRight(); vsb.setBounds( r.x + (ltr ? padding : -padding), y, r.width, y2 - y ); } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java index 967b13582..c347c18ad 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java @@ -23,9 +23,11 @@ import java.util.Arrays; import java.util.stream.Collectors; import javax.swing.*; +import javax.swing.border.Border; import javax.swing.table.AbstractTableModel; import javax.swing.tree.*; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatScrollPaneBorder; import com.formdev.flatlaf.util.UIScale; import net.miginfocom.swing.*; @@ -203,6 +205,11 @@ private void arcSliderChanged() { int arc = arcSlider.getValue(); for( JScrollPane scrollPane : allJScrollPanes ) scrollPane.putClientProperty( FlatClientProperties.STYLE, "arc: " + arc ); + + Border border = allJScrollPanes[0].getBorder(); + paddingField.setText( border instanceof FlatScrollPaneBorder + ? Integer.toString( ((FlatScrollPaneBorder)border).getLeftRightPadding( allJScrollPanes[0] ) ) + : "?" ); } private void initComponents() { @@ -210,6 +217,8 @@ private void initComponents() { splitPane2 = new JSplitPane(); panel1 = new FlatTestPanel(); listLabel = new JLabel(); + paddingLabel = new JLabel(); + paddingField = new JLabel(); treeLabel = new JLabel(); tableLabel = new JLabel(); autoResizeModeCheckBox = new JCheckBox(); @@ -237,8 +246,8 @@ private void initComponents() { arcSlider = new JSlider(); cornersCheckBox = new JCheckBox(); columnHeaderCheckBox = new JCheckBox(); - rowHeaderCheckBox = new JCheckBox(); horizontalScrollBarCheckBox = new JCheckBox(); + rowHeaderCheckBox = new JCheckBox(); verticalScrollBarCheckBox = new JCheckBox(); //======== this ======== @@ -273,6 +282,14 @@ private void initComponents() { listLabel.setHorizontalTextPosition(SwingConstants.LEADING); panel1.add(listLabel, "cell 0 0,aligny top,growy 0"); + //---- paddingLabel ---- + paddingLabel.setText("Padding:"); + panel1.add(paddingLabel, "cell 0 0,alignx trailing,growx 0"); + + //---- paddingField ---- + paddingField.setText("0"); + panel1.add(paddingField, "cell 0 0,alignx trailing,growx 0"); + //---- treeLabel ---- treeLabel.setText("JTree:"); treeLabel.setHorizontalTextPosition(SwingConstants.LEADING); @@ -382,12 +399,11 @@ private void initComponents() { // columns "[fill]" + "[grow,fill]para" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]" + - "[fill]", + "[]" + + "[]" + + "[]", // rows + "[]" + "[]")); //---- arcLabel ---- @@ -403,7 +419,7 @@ private void initComponents() { arcSlider.setPaintTicks(true); arcSlider.setPaintLabels(true); arcSlider.addChangeListener(e -> arcSliderChanged()); - panel3.add(arcSlider, "cell 1 0"); + panel3.add(arcSlider, "cell 1 0 1 2"); //---- cornersCheckBox ---- cornersCheckBox.setText("Corners"); @@ -415,22 +431,22 @@ private void initComponents() { columnHeaderCheckBox.addActionListener(e -> columnHeaderChanged()); panel3.add(columnHeaderCheckBox, "cell 3 0"); - //---- rowHeaderCheckBox ---- - rowHeaderCheckBox.setText("Row Header"); - rowHeaderCheckBox.addActionListener(e -> rowHeaderChanged()); - panel3.add(rowHeaderCheckBox, "cell 4 0"); - //---- horizontalScrollBarCheckBox ---- horizontalScrollBarCheckBox.setText("Horizontal ScrollBar"); horizontalScrollBarCheckBox.setSelected(true); horizontalScrollBarCheckBox.addActionListener(e -> horizontalScrollBarChanged()); - panel3.add(horizontalScrollBarCheckBox, "cell 5 0"); + panel3.add(horizontalScrollBarCheckBox, "cell 4 0"); + + //---- rowHeaderCheckBox ---- + rowHeaderCheckBox.setText("Row Header"); + rowHeaderCheckBox.addActionListener(e -> rowHeaderChanged()); + panel3.add(rowHeaderCheckBox, "cell 3 1"); //---- verticalScrollBarCheckBox ---- verticalScrollBarCheckBox.setText("Vertical ScrollBar"); verticalScrollBarCheckBox.setSelected(true); verticalScrollBarCheckBox.addActionListener(e -> verticalScrollBarChanged()); - panel3.add(verticalScrollBarCheckBox, "cell 6 0"); + panel3.add(verticalScrollBarCheckBox, "cell 4 1"); } add(panel3, "cell 0 1"); // JFormDesigner - End of component initialization //GEN-END:initComponents @@ -440,6 +456,8 @@ private void initComponents() { private JSplitPane splitPane2; private FlatTestPanel panel1; private JLabel listLabel; + private JLabel paddingLabel; + private JLabel paddingField; private JLabel treeLabel; private JLabel tableLabel; private JCheckBox autoResizeModeCheckBox; @@ -466,8 +484,8 @@ private void initComponents() { private JSlider arcSlider; private JCheckBox cornersCheckBox; private JCheckBox columnHeaderCheckBox; - private JCheckBox rowHeaderCheckBox; private JCheckBox horizontalScrollBarCheckBox; + private JCheckBox rowHeaderCheckBox; private JCheckBox verticalScrollBarCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd index bb602b71b..a6af6ad50 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd @@ -26,6 +26,18 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0,aligny top,growy 0" } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "paddingLabel" + "text": "Padding:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0,alignx trailing,growx 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "paddingField" + "text": "0" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0,alignx trailing,growx 0" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "treeLabel" "text": "JTree:" @@ -156,8 +168,8 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" - "$columnConstraints": "[fill][grow,fill]para[fill][fill][fill][fill][fill]" - "$rowConstraints": "[]" + "$columnConstraints": "[fill][grow,fill]para[][][]" + "$rowConstraints": "[][]" } ) { name: "panel3" add( new FormComponent( "javax.swing.JLabel" ) { @@ -180,7 +192,7 @@ new FormModel { "paintLabels": true addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "arcSliderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 0" + "value": "cell 1 0 1 2" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "cornersCheckBox" @@ -196,20 +208,20 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 3 0" } ) - add( new FormComponent( "javax.swing.JCheckBox" ) { - name: "rowHeaderCheckBox" - "text": "Row Header" - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowHeaderChanged", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 4 0" - } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "horizontalScrollBarCheckBox" "text": "Horizontal ScrollBar" "selected": true addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "horizontalScrollBarChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 5 0" + "value": "cell 4 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "rowHeaderCheckBox" + "text": "Row Header" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowHeaderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 3 1" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "verticalScrollBarCheckBox" @@ -217,7 +229,7 @@ new FormModel { "selected": true addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "verticalScrollBarChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 6 0" + "value": "cell 4 1" } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 1" From 40418607e5dee5135b9361527bdb778dadd61cef Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 13 Aug 2023 23:23:03 +0200 Subject: [PATCH 3/5] ScrollPane: fixed lost styling on ScrollPane border if using Table as view component --- CHANGELOG.md | 8 ++++++++ .../com/formdev/flatlaf/ui/FlatScrollPaneUI.java | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c617175f..2466b1134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ FlatLaf Change Log ================== +## 3.3-SNAPSHOT + +#### Fixed bugs + +- ScrollPane: Styling ScrollPane border properties did not work if view + component is a Table. + + ## 3.2 #### New features and improvements diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index c785698cb..b164973d0 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -330,6 +330,18 @@ protected PropertyChangeListener createPropertyChangeListener() { scrollpane.revalidate(); scrollpane.repaint(); break; + + case "border": + Object newBorder = e.getNewValue(); + if( newBorder != null && newBorder == UIManager.getBorder( "Table.scrollPaneBorder" ) ) { + // JTable.configureEnclosingScrollPaneUI() replaces the scrollpane border + // with another one --> re-apply style on new border + borderShared = null; + installStyle(); + scrollpane.revalidate(); + scrollpane.repaint(); + } + break; } }; } From 0c604b1023c18211d36ad7d733fbebb42abd44ad Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 27 Aug 2023 18:14:03 +0200 Subject: [PATCH 4/5] ScrollPane: increase viewport width for rounded border to remove/reduce gap between view and vertical scrollbar --- .../formdev/flatlaf/ui/FlatScrollPaneUI.java | 80 +++++++++++++++---- .../testing/FlatRoundedScrollPaneTest.java | 22 +++++ .../testing/FlatRoundedScrollPaneTest.jfd | 9 ++- 3 files changed, 95 insertions(+), 16 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index b164973d0..54f2f3cde 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -454,6 +454,26 @@ public void update( Graphics g, JComponent c ) { paint( g, c ); } + @Override + public void paint( Graphics g, JComponent c ) { + Border viewportBorder = scrollpane.getViewportBorder(); + if( viewportBorder != null ) { + Rectangle r = scrollpane.getViewportBorderBounds(); + int padding = getBorderLeftRightPadding( scrollpane ); + JScrollBar vsb = scrollpane.getVerticalScrollBar(); + if( padding > 0 && + vsb != null && vsb.isVisible() && + scrollpane.getLayout() instanceof FlatScrollPaneLayout && + ((FlatScrollPaneLayout)scrollpane.getLayout()).canIncreaseViewportWidth( scrollpane ) ) + { + boolean ltr = scrollpane.getComponentOrientation().isLeftToRight(); + int extraWidth = Math.min( padding, vsb.getWidth() ); + viewportBorder.paintBorder( scrollpane, g, r.x - (ltr ? 0 : extraWidth), r.y, r.width + extraWidth, r.height ); + } else + viewportBorder.paintBorder( scrollpane, g, r.x, r.y, r.width, r.height ); + } + } + /** @since 1.3 */ public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) { Component view = getView( scrollPane ); @@ -488,6 +508,13 @@ private static float getBorderArc( JScrollPane scrollPane ) { : 0; } + private static int getBorderLeftRightPadding( JScrollPane scrollPane ) { + Border border = scrollPane.getBorder(); + return (border instanceof FlatScrollPaneBorder) + ? ((FlatScrollPaneBorder)border).getLeftRightPadding( scrollPane ) + : 0; + } + //---- class Handler ------------------------------------------------------ /** @@ -533,23 +560,46 @@ public void layoutContainer( Container parent ) { super.layoutContainer( parent ); JScrollPane scrollPane = (JScrollPane) parent; - Border border = scrollPane.getBorder(); - int padding; - if( border instanceof FlatScrollPaneBorder && - (padding = ((FlatScrollPaneBorder)border).getLeftRightPadding( scrollPane )) > 0 ) - { - JScrollBar vsb = getVerticalScrollBar(); - if( vsb != null && vsb.isVisible() ) { - // move vertical scrollbar to trailing edge - Insets insets = scrollPane.getInsets(); - Rectangle r = vsb.getBounds(); - int y = Math.max( r.y, insets.top + padding ); - int y2 = Math.min( r.y + r.height, scrollPane.getHeight() - insets.bottom - padding ); - boolean ltr = scrollPane.getComponentOrientation().isLeftToRight(); - - vsb.setBounds( r.x + (ltr ? padding : -padding), y, r.width, y2 - y ); + int padding = getBorderLeftRightPadding( scrollPane ); + if( padding > 0 && vsb != null && vsb.isVisible() ) { + // move vertical scrollbar to trailing edge + Insets insets = scrollPane.getInsets(); + Rectangle r = vsb.getBounds(); + int y = Math.max( r.y, insets.top + padding ); + int y2 = Math.min( r.y + r.height, scrollPane.getHeight() - insets.bottom - padding ); + boolean ltr = scrollPane.getComponentOrientation().isLeftToRight(); + + vsb.setBounds( r.x + (ltr ? padding : -padding), y, r.width, y2 - y ); + + // increase width of viewport, column header and horizontal scrollbar + if( canIncreaseViewportWidth( scrollPane ) ) { + int extraWidth = Math.min( padding, vsb.getWidth() ); + resizeViewport( viewport, extraWidth, ltr ); + resizeViewport( colHead, extraWidth, ltr ); + resizeViewport( hsb, extraWidth, ltr ); } } } + + boolean canIncreaseViewportWidth( JScrollPane scrollPane ) { + return scrollPane.getComponentOrientation().isLeftToRight() + ? !isCornerVisible( upperRight ) && !isCornerVisible( lowerRight ) + : !isCornerVisible( upperLeft ) && !isCornerVisible( lowerLeft ); + } + + private static boolean isCornerVisible( Component corner ) { + return corner != null && + corner.getWidth() > 0 && + corner.getHeight() > 0 && + corner.isVisible(); + } + + private static void resizeViewport( Component c, int extraWidth, boolean ltr ) { + if( c == null ) + return; + + Rectangle vr = c.getBounds(); + c.setBounds( vr.x - (ltr ? 0 : extraWidth), vr.y, vr.width + extraWidth, vr.height ); + } } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java index c347c18ad..ebea0428a 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.java @@ -24,6 +24,8 @@ import java.util.stream.Collectors; import javax.swing.*; import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.MatteBorder; import javax.swing.table.AbstractTableModel; import javax.swing.tree.*; import com.formdev.flatlaf.FlatClientProperties; @@ -212,6 +214,19 @@ private void arcSliderChanged() { : "?" ); } + private void viewportBorderChanged() { + Border viewportBorder = viewportBorderCheckBox.isSelected() + ? new CompoundBorder( + new MatteBorder( 1, 1, 0, 0, Color.red ), + new MatteBorder( 0, 0, 1, 1, Color.blue ) ) + : null; + for( JScrollPane scrollPane : allJScrollPanes ) { + scrollPane.setViewportBorder( viewportBorder ); + scrollPane.revalidate(); + scrollPane.repaint(); + } + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents splitPane2 = new JSplitPane(); @@ -247,6 +262,7 @@ private void initComponents() { cornersCheckBox = new JCheckBox(); columnHeaderCheckBox = new JCheckBox(); horizontalScrollBarCheckBox = new JCheckBox(); + viewportBorderCheckBox = new JCheckBox(); rowHeaderCheckBox = new JCheckBox(); verticalScrollBarCheckBox = new JCheckBox(); @@ -437,6 +453,11 @@ private void initComponents() { horizontalScrollBarCheckBox.addActionListener(e -> horizontalScrollBarChanged()); panel3.add(horizontalScrollBarCheckBox, "cell 4 0"); + //---- viewportBorderCheckBox ---- + viewportBorderCheckBox.setText("Viewport border"); + viewportBorderCheckBox.addActionListener(e -> viewportBorderChanged()); + panel3.add(viewportBorderCheckBox, "cell 2 1"); + //---- rowHeaderCheckBox ---- rowHeaderCheckBox.setText("Row Header"); rowHeaderCheckBox.addActionListener(e -> rowHeaderChanged()); @@ -485,6 +506,7 @@ private void initComponents() { private JCheckBox cornersCheckBox; private JCheckBox columnHeaderCheckBox; private JCheckBox horizontalScrollBarCheckBox; + private JCheckBox viewportBorderCheckBox; private JCheckBox rowHeaderCheckBox; private JCheckBox verticalScrollBarCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd index a6af6ad50..ffaf2a6a0 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatRoundedScrollPaneTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "8.1.1.0.298" Java: "19.0.2" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -216,6 +216,13 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 4 0" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "viewportBorderCheckBox" + "text": "Viewport border" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "viewportBorderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 1" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "rowHeaderCheckBox" "text": "Row Header" From b1fdbde5cd73558e09a0b4f147f0824a0010084d Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 25 Nov 2023 23:26:03 +0100 Subject: [PATCH 5/5] ScrollPane: allow specifying scroll pane border arc for multi-line text components, lists, tables and trees --- .../com/formdev/flatlaf/ui/FlatBorder.java | 4 +- .../flatlaf/ui/FlatScrollPaneBorder.java | 40 +++++++++++++++++++ .../formdev/flatlaf/ui/FlatScrollPaneUI.java | 8 ++-- .../com/formdev/flatlaf/FlatLaf.properties | 4 ++ .../dumps/uidefaults/FlatTestLaf_1.8.0.txt | 4 +- .../flatlaf/themeeditor/FlatLafUIKeys.txt | 4 ++ 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java index 11d2cbfba..2903998d4 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatBorder.java @@ -28,7 +28,6 @@ import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JSpinner; -import javax.swing.JViewport; import javax.swing.UIManager; import javax.swing.plaf.basic.BasicBorders; import com.formdev.flatlaf.FlatClientProperties; @@ -195,8 +194,7 @@ protected Paint getBorderColor( Component c ) { protected boolean isEnabled( Component c ) { if( c instanceof JScrollPane ) { // check whether view component is disabled - JViewport viewport = ((JScrollPane)c).getViewport(); - Component view = (viewport != null) ? viewport.getView() : null; + Component view = FlatScrollPaneUI.getView( (JScrollPane) c ); if( view != null && !isEnabled( view ) ) return false; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java index ba4f785c7..ec8c0a41c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneBorder.java @@ -18,7 +18,12 @@ import java.awt.Component; import java.awt.Insets; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTree; import javax.swing.UIManager; +import javax.swing.text.JTextComponent; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.util.UIScale; @@ -26,6 +31,10 @@ * Border for {@link javax.swing.JScrollPane}. * * @uiDefault ScrollPane.arc int + * @uiDefault ScrollPane.List.arc int + * @uiDefault ScrollPane.Table.arc int + * @uiDefault ScrollPane.TextComponent.arc int + * @uiDefault ScrollPane.Tree.arc int * @author Karl Tauber * @since 3.3 @@ -35,6 +44,22 @@ public class FlatScrollPaneBorder { @Styleable protected int arc = UIManager.getInt( "ScrollPane.arc" ); + private boolean isArcStyled; + private final int listArc = FlatUIUtils.getUIInt( "ScrollPane.List.arc", -1 ); + private final int tableArc = FlatUIUtils.getUIInt( "ScrollPane.Table.arc", -1 ); + private final int textComponentArc = FlatUIUtils.getUIInt( "ScrollPane.TextComponent.arc", -1 ); + private final int treeArc = FlatUIUtils.getUIInt( "ScrollPane.Tree.arc", -1 ); + + @Override + public Object applyStyleProperty( String key, Object value ) { + Object oldValue = super.applyStyleProperty( key, value ); + + if( "arc".equals( key ) ) + isArcStyled = true; + + return oldValue; + } + @Override public Insets getBorderInsets( Component c, Insets insets ) { insets = super.getBorderInsets( c, insets ); @@ -55,6 +80,21 @@ protected int getArc( Component c ) { if( isCellEditor( c ) ) return 0; + if( isArcStyled ) + return arc; + + if( c instanceof JScrollPane ) { + Component view = FlatScrollPaneUI.getView( (JScrollPane) c ); + if( listArc >= 0 && view instanceof JList ) + return listArc; + if( tableArc >= 0 && view instanceof JTable ) + return tableArc; + if( textComponentArc >= 0&& view instanceof JTextComponent ) + return textComponentArc; + if( treeArc >= 0 && view instanceof JTree ) + return treeArc; + } + return arc; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index 54f2f3cde..f563af65c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -496,7 +496,7 @@ public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) { return false; } - private static Component getView( JScrollPane scrollPane ) { + static Component getView( JScrollPane scrollPane ) { JViewport viewport = scrollPane.getViewport(); return (viewport != null) ? viewport.getView() : null; } @@ -537,13 +537,15 @@ public void componentRemoved( ContainerEvent e ) { @Override public void focusGained( FocusEvent e ) { // necessary to update focus border - scrollpane.repaint(); + if( scrollpane.getBorder() instanceof FlatBorder ) + scrollpane.repaint(); } @Override public void focusLost( FocusEvent e ) { // necessary to update focus border - scrollpane.repaint(); + if( scrollpane.getBorder() instanceof FlatBorder ) + scrollpane.repaint(); } } 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 a620989f4..428595deb 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -602,6 +602,10 @@ ScrollPane.background = $ScrollBar.track ScrollPane.fillUpperCorner = true ScrollPane.smoothScrolling = true ScrollPane.arc = 0 +#ScrollPane.List.arc = -1 +#ScrollPane.Table.arc = -1 +#ScrollPane.TextComponent.arc = -1 +#ScrollPane.Tree.arc = -1 #---- SearchField ---- diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt index d15bd88dd..14b8ec415 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt @@ -934,7 +934,7 @@ ScrollBarUI com.formdev.flatlaf.ui.FlatScrollBarUI ScrollPane.arc 20 ScrollPane.background #88ff88 HSL 120 100 77 javax.swing.plaf.ColorUIResource [UI] -ScrollPane.border [lazy] 1,10,1,10 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] +ScrollPane.border [lazy] 1,9,1,9 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] ScrollPane.fillUpperCorner true ScrollPane.font [active] $defaultFont [UI] ScrollPane.foreground #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI] @@ -1155,7 +1155,7 @@ Table.foreground #ff0000 HSL 0 100 50 javax.swing.plaf.Colo Table.gridColor #00ffff HSL 180 100 50 javax.swing.plaf.ColorUIResource [UI] Table.intercellSpacing 0,0 javax.swing.plaf.DimensionUIResource [UI] Table.rowHeight 25 -Table.scrollPaneBorder [lazy] 1,10,1,10 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] +Table.scrollPaneBorder [lazy] 1,9,1,9 false com.formdev.flatlaf.ui.FlatScrollPaneBorder [UI] Table.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI] Table.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI] Table.selectionInactiveBackground #888888 HSL 0 0 53 javax.swing.plaf.ColorUIResource [UI] 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 b067ba318..ed50f026b 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 @@ -717,6 +717,10 @@ ScrollBar.trackHighlight ScrollBar.trackInsets ScrollBar.width ScrollBarUI +ScrollPane.List.arc +ScrollPane.Table.arc +ScrollPane.TextComponent.arc +ScrollPane.Tree.arc ScrollPane.ancestorInputMap ScrollPane.ancestorInputMap.RightToLeft ScrollPane.arc