ScrollPane: support rounded border

This commit is contained in:
Karl Tauber
2023-08-13 14:01:09 +02:00
parent c953ff84d0
commit 7bec5ec6dc
18 changed files with 942 additions and 36 deletions

View File

@@ -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;
}
}

View File

@@ -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.JTree;
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 @@ public class FlatScrollPaneUI
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 @@ public class FlatScrollPaneUI
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 @@ public class FlatScrollPaneUI
handler = null;
}
/**
* @since 3.3
*/
protected FlatScrollPaneLayout createScrollPaneLayout() {
return new FlatScrollPaneLayout();
}
@Override
protected MouseWheelListener createMouseWheelListener() {
MouseWheelListener superListener = super.createMouseWheelListener();
@@ -290,8 +313,7 @@ public class FlatScrollPaneUI
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 @@ public class FlatScrollPaneUI
/** @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 class FlatScrollPaneUI
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 class FlatScrollPaneUI
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 class FlatScrollPaneUI
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 );
}
}
}
}
}

View File

@@ -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

View File

@@ -601,7 +601,7 @@ public class TestFlatStyleableInfo
);
// border
flatBorder( expected );
flatScrollPaneBorder( expected );
assertMapEquals( expected, ui.getStyleableInfos( c ) );
}
@@ -1005,17 +1005,23 @@ public class TestFlatStyleableInfo
expectedMap( expected,
"arc", int.class,
"roundRect", Boolean.class
);
}
private void flatScrollPaneBorder( Map<String, Class<?>> expected ) {
flatBorder( expected );
expectedMap( expected,
"arc", int.class
);
}
private void flatTextBorder( Map<String, Class<?>> expected ) {
flatBorder( expected );
expectedMap( expected,
"arc", int.class,
"roundRect", Boolean.class
);
}

View File

@@ -625,7 +625,7 @@ public class TestFlatStyleableValue
FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI();
// border
flatBorder( c, ui );
flatScrollPaneBorder( c, ui );
testBoolean( c, ui, "showButtons", true );
}
@@ -965,15 +965,19 @@ public class TestFlatStyleableValue
flatBorder( c, ui );
testInteger( c, ui, "arc", 123 );
testBoolean( c, ui, "roundRect", true );
}
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 @@ public class TestFlatStyleableValue
// 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 @@ public class TestFlatStyleableValue
flatBorder( border );
testValue( border, "arc", 6 );
testValue( border, "roundRect", true );
}
@Test

View File

@@ -760,7 +760,7 @@ public class TestFlatStyling
FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI();
// border
flatBorder( style -> ui.applyStyle( style ) );
flatScrollPaneBorder( style -> ui.applyStyle( style ) );
ui.applyStyle( "showButtons: true" );
@@ -1234,15 +1234,19 @@ public class TestFlatStyling
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
applyStyle.accept( "roundRect: true" );
}
private void flatScrollPaneBorder( Consumer<String> applyStyle ) {
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
}
private void flatTextBorder( Consumer<String> applyStyle ) {
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
applyStyle.accept( "roundRect: true" );
}