Merge PR #612: macOS themes: make spinner look like macOS stepper

This commit is contained in:
Karl Tauber
2022-11-16 10:59:10 +01:00
6 changed files with 152 additions and 36 deletions

View File

@@ -17,7 +17,10 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics;
import javax.swing.JSpinner;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.SpinnerUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
/** /**
@@ -35,6 +38,19 @@ public class FlatRoundBorder
// only used via styling (not in UI defaults, but has likewise client properties) // only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected Boolean roundRect; /** @since 2 */ @Styleable protected Boolean roundRect;
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
// make mac style spinner border smaller (border does not surround arrow buttons)
if( isMacStyleSpinner( c ) ) {
int macStyleButtonsWidth = ((FlatSpinnerUI)((JSpinner)c).getUI()).getMacStyleButtonsWidth();
width -= macStyleButtonsWidth;
if( !c.getComponentOrientation().isLeftToRight() )
x += macStyleButtonsWidth;
}
super.paintBorder( c, g, x, y, width, height );
}
@Override @Override
protected int getArc( Component c ) { protected int getArc( Component c ) {
if( isCellEditor( c ) ) if( isCellEditor( c ) )
@@ -43,6 +59,17 @@ public class FlatRoundBorder
Boolean roundRect = FlatUIUtils.isRoundRect( c ); Boolean roundRect = FlatUIUtils.isRoundRect( c );
if( roundRect == null ) if( roundRect == null )
roundRect = this.roundRect; roundRect = this.roundRect;
return roundRect != null ? (roundRect ? Short.MAX_VALUE : 0) : arc; return roundRect != null
? (roundRect ? Short.MAX_VALUE : 0)
: (isMacStyleSpinner( c ) ? 0 : arc);
}
private boolean isMacStyleSpinner( Component c ) {
if( c instanceof JSpinner ) {
SpinnerUI ui = ((JSpinner)c).getUI();
if( ui instanceof FlatSpinnerUI )
return ((FlatSpinnerUI)ui).isMacStyle();
}
return false;
} }
} }

View File

@@ -340,20 +340,31 @@ public class FlatSpinnerUI
private Component createArrowButton( int direction, String name ) { private Component createArrowButton( int direction, String name ) {
FlatArrowButton button = new FlatArrowButton( direction, arrowType, buttonArrowColor, FlatArrowButton button = new FlatArrowButton( direction, arrowType, buttonArrowColor,
buttonDisabledArrowColor, buttonHoverArrowColor, null, buttonPressedArrowColor, null ); buttonDisabledArrowColor, buttonHoverArrowColor, null, buttonPressedArrowColor, null )
{
@Override
public int getArrowWidth() {
return isMacStyle() ? 7 : super.getArrowWidth();
}
@Override
public float getArrowThickness() {
return isMacStyle() ? 1.5f : super.getArrowThickness();
}
@Override
public float getYOffset() {
return isMacStyle() ? 0 : super.getYOffset();
}
@Override
public boolean isRoundBorderAutoXOffset() {
return isMacStyle() ? false : super.isRoundBorderAutoXOffset();
}
};
button.setName( name ); button.setName( name );
button.setYOffset( (direction == SwingConstants.NORTH) ? 1.25f : -1.25f ); button.setYOffset( (direction == SwingConstants.NORTH) ? 1.25f : -1.25f );
if( direction == SwingConstants.NORTH ) if( direction == SwingConstants.NORTH )
installNextButtonListeners( button ); installNextButtonListeners( button );
else else
installPreviousButtonListeners( button ); installPreviousButtonListeners( button );
if( "mac".equals( buttonStyle ) ) {
button.setArrowWidth( 7 );
button.setArrowThickness( 1.5f );
button.setYOffset( (direction == SwingConstants.NORTH) ? 0.75f : -0.75f );
button.setRoundBorderAutoXOffset( false );
}
return button; return button;
} }
@@ -381,10 +392,13 @@ public class FlatSpinnerUI
int width = c.getWidth(); int width = c.getWidth();
int height = c.getHeight(); int height = c.getHeight();
boolean enabled = spinner.isEnabled(); boolean enabled = spinner.isEnabled();
boolean ltr = spinner.getComponentOrientation().isLeftToRight();
boolean isMacStyle = isMacStyle();
int macStyleButtonsWidth = isMacStyle ? getMacStyleButtonsWidth() : 0;
// paint background // paint background
g2.setColor( getBackground( enabled ) ); g2.setColor( getBackground( enabled ) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, ltr ? 0 : macStyleButtonsWidth, 0, width - macStyleButtonsWidth, height, focusWidth, arc );
// paint button background and separator // paint button background and separator
boolean paintButton = !"none".equals( buttonStyle ); boolean paintButton = !"none".equals( buttonStyle );
@@ -393,22 +407,20 @@ public class FlatSpinnerUI
Component button = (handler.nextButton != null) ? handler.nextButton : handler.previousButton; Component button = (handler.nextButton != null) ? handler.nextButton : handler.previousButton;
int arrowX = button.getX(); int arrowX = button.getX();
int arrowWidth = button.getWidth(); int arrowWidth = button.getWidth();
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor; Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( "mac".equals( buttonStyle ) ) { if( isMacStyle ) {
Insets insets = spinner.getInsets(); Insets insets = spinner.getInsets();
int gapX = scale( 3 ); int lineWidth = Math.round( FlatUIUtils.getBorderLineWidth( spinner ) );
int gapY = scale( 1 ); int bx = arrowX;
int bx = arrowX + gapX; int by = insets.top - lineWidth;
int by = insets.top + gapY; int bw = arrowWidth;
int bw = arrowWidth - (gapX * 2); int bh = height - insets.top - insets.bottom + (lineWidth * 2);
int bh = height - insets.top - insets.bottom - (gapY * 2);
float lw = scale( buttonSeparatorWidth ); float lw = scale( buttonSeparatorWidth );
// buttons border // buttons border
FlatUIUtils.paintOutlinedComponent( g2, bx, by, bw, bh, FlatUIUtils.paintOutlinedComponent( g2, bx, by, bw, bh,
0, 0, 0, lw, arc - focusWidth, 0, 0, 0, lw, scale( 12 ),
null, separatorColor, buttonBackground ); null, separatorColor, buttonBackground );
// separator between buttons // separator between buttons
@@ -423,7 +435,7 @@ public class FlatSpinnerUI
if( enabled && buttonBackground != null ) { if( enabled && buttonBackground != null ) {
g2.setColor( buttonBackground ); g2.setColor( buttonBackground );
Shape oldClip = g2.getClip(); Shape oldClip = g2.getClip();
if( isLeftToRight ) if( ltr )
g2.clipRect( arrowX, 0, width - arrowX, height ); g2.clipRect( arrowX, 0, width - arrowX, height );
else else
g2.clipRect( 0, 0, arrowX + arrowWidth, height ); g2.clipRect( 0, 0, arrowX + arrowWidth, height );
@@ -435,7 +447,7 @@ public class FlatSpinnerUI
if( separatorColor != null && buttonSeparatorWidth > 0 ) { if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor ); g2.setColor( separatorColor );
float lw = scale( buttonSeparatorWidth ); float lw = scale( buttonSeparatorWidth );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; float lx = ltr ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) ); g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) );
} }
} }
@@ -446,6 +458,19 @@ public class FlatSpinnerUI
FlatUIUtils.resetRenderingHints( g, oldRenderingHints ); FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} }
boolean isMacStyle() {
return "mac".equals( buttonStyle );
}
int getMacStyleButtonsWidth() {
return (handler.nextButton != null || handler.previousButton != null)
? scale( MAC_STEPPER_GAP ) + scale( MAC_STEPPER_WIDTH )
: 0;
}
private static final int MAC_STEPPER_WIDTH = 15;
private static final int MAC_STEPPER_GAP = 3;
//---- class Handler ------------------------------------------------------ //---- class Handler ------------------------------------------------------
private class Handler private class Handler
@@ -502,6 +527,7 @@ public class FlatSpinnerUI
Insets insets = parent.getInsets(); Insets insets = parent.getInsets();
Rectangle r = FlatUIUtils.subtractInsets( new Rectangle( size ), insets ); Rectangle r = FlatUIUtils.subtractInsets( new Rectangle( size ), insets );
// editor gets all space if there are no buttons
if( nextButton == null && previousButton == null ) { if( nextButton == null && previousButton == null ) {
if( editor != null ) if( editor != null )
editor.setBounds( r ); editor.setBounds( r );
@@ -517,20 +543,36 @@ public class FlatSpinnerUI
int minButtonWidth = (maxButtonWidth * 3) / 4; int minButtonWidth = (maxButtonWidth * 3) / 4;
// make button area square (except if width is limited) // make button area square (except if width is limited)
int buttonsWidth = Math.min( Math.max( buttonsRect.height, minButtonWidth ), maxButtonWidth ); boolean isMacStyle = isMacStyle();
buttonsRect.width = buttonsWidth; int buttonsGap = isMacStyle ? scale( MAC_STEPPER_GAP ) : 0;
int prefButtonWidth = isMacStyle ? scale( MAC_STEPPER_WIDTH ) : buttonsRect.height;
int buttonsWidth = Math.min( Math.max( prefButtonWidth, minButtonWidth ), maxButtonWidth );
if( parent.getComponentOrientation().isLeftToRight() ) { // update editor and buttons bounds
editorRect.width -= buttonsWidth; buttonsRect.width = buttonsWidth;
buttonsRect.x += editorRect.width; editorRect.width -= buttonsWidth + buttonsGap;
} else { boolean ltr = parent.getComponentOrientation().isLeftToRight();
editorRect.x += buttonsWidth; if( ltr )
editorRect.width -= buttonsWidth; buttonsRect.x += editorRect.width + buttonsGap;
else
editorRect.x += buttonsWidth + buttonsGap;
// in mac button style increase buttons height and move to the right
// for exact alignment with border
if( isMacStyle ) {
int lineWidth = Math.round( FlatUIUtils.getBorderLineWidth( spinner ) );
if( lineWidth > 0 ) {
buttonsRect.x += ltr ? lineWidth : -lineWidth;
buttonsRect.y -= lineWidth;
buttonsRect.height += lineWidth * 2;
}
} }
// set editor bounds
if( editor != null ) if( editor != null )
editor.setBounds( editorRect ); editor.setBounds( editorRect );
// set buttons bounds
int nextHeight = (buttonsRect.height / 2) + (buttonsRect.height % 2); // round up int nextHeight = (buttonsRect.height / 2) + (buttonsRect.height % 2); // round up
if( nextButton != null ) if( nextButton != null )
nextButton.setBounds( buttonsRect.x, buttonsRect.y, buttonsRect.width, nextHeight ); nextButton.setBounds( buttonsRect.x, buttonsRect.y, buttonsRect.width, nextHeight );

View File

@@ -253,6 +253,9 @@ Slider.focusedColor = $Component.focusColor
Spinner.buttonStyle = mac Spinner.buttonStyle = mac
Spinner.disabledBackground = @disabledComponentBackground Spinner.disabledBackground = @disabledComponentBackground
Spinner.buttonBackground = @buttonBackground Spinner.buttonBackground = @buttonBackground
Spinner.buttonArrowColor = @foreground
Spinner.buttonHoverArrowColor = lighten($Spinner.buttonArrowColor,10%,derived noAutoInverse)
Spinner.buttonPressedArrowColor = lighten($Spinner.buttonArrowColor,20%,derived noAutoInverse)
Spinner.buttonSeparatorWidth = 0 Spinner.buttonSeparatorWidth = 0

View File

@@ -982,12 +982,12 @@ SliderUI com.formdev.flatlaf.ui.FlatSliderUI
Spinner.arrowButtonSize 16,5 java.awt.Dimension Spinner.arrowButtonSize 16,5 java.awt.Dimension
Spinner.background #282828 HSL 0 0 16 javax.swing.plaf.ColorUIResource [UI] Spinner.background #282828 HSL 0 0 16 javax.swing.plaf.ColorUIResource [UI]
Spinner.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatRoundBorder [UI] Spinner.border [lazy] 3,3,3,3 false com.formdev.flatlaf.ui.FlatRoundBorder [UI]
Spinner.buttonArrowColor #b7b7b7 HSL 0 0 72 javax.swing.plaf.ColorUIResource [UI] Spinner.buttonArrowColor #dddddd HSL 0 0 87 javax.swing.plaf.ColorUIResource [UI]
Spinner.buttonBackground #565656 HSL 0 0 34 javax.swing.plaf.ColorUIResource [UI] Spinner.buttonBackground #565656 HSL 0 0 34 javax.swing.plaf.ColorUIResource [UI]
Spinner.buttonDisabledArrowColor #777777 HSL 0 0 47 javax.swing.plaf.ColorUIResource [UI] Spinner.buttonDisabledArrowColor #777777 HSL 0 0 47 javax.swing.plaf.ColorUIResource [UI]
Spinner.buttonDisabledSeparatorColor #ffffff0c 5% HSLA 0 0 100 5 javax.swing.plaf.ColorUIResource [UI] Spinner.buttonDisabledSeparatorColor #ffffff0c 5% HSLA 0 0 100 5 javax.swing.plaf.ColorUIResource [UI]
Spinner.buttonHoverArrowColor #d1d1d1 HSL 0 0 82 com.formdev.flatlaf.util.DerivedColor [UI] lighten(10%) Spinner.buttonHoverArrowColor #f7f7f7 HSL 0 0 97 com.formdev.flatlaf.util.DerivedColor [UI] lighten(10%)
Spinner.buttonPressedArrowColor #eaeaea HSL 0 0 92 com.formdev.flatlaf.util.DerivedColor [UI] lighten(20%) Spinner.buttonPressedArrowColor #ffffff HSL 0 0 100 com.formdev.flatlaf.util.DerivedColor [UI] lighten(20%)
Spinner.buttonSeparatorColor #ffffff19 10% HSLA 0 0 100 10 javax.swing.plaf.ColorUIResource [UI] Spinner.buttonSeparatorColor #ffffff19 10% HSLA 0 0 100 10 javax.swing.plaf.ColorUIResource [UI]
Spinner.buttonSeparatorWidth 0 Spinner.buttonSeparatorWidth 0
Spinner.buttonStyle mac Spinner.buttonStyle mac

View File

@@ -45,6 +45,27 @@ public class FlatTextComponentsTest
FlatTextComponentsTest() { FlatTextComponentsTest() {
initComponents(); initComponents();
updatePreferredSizes();
}
@Override
public void updateUI() {
super.updateUI();
if( comboBox5 != null )
updatePreferredSizes();
}
private void updatePreferredSizes() {
Dimension size40 = UIScale.scale( new Dimension( 60, 40 ) );
comboBox5.setPreferredSize( size40 );
spinner4.setPreferredSize( size40 );
Dimension size14 = UIScale.scale( new Dimension( 60, 14 ) );
comboBox6.setPreferredSize( size14 );
comboBox6.setMinimumSize( size14 );
spinner5.setPreferredSize( size14 );
spinner5.setMinimumSize( size14 );
} }
private void editableChanged() { private void editableChanged() {
@@ -216,18 +237,19 @@ public class FlatTextComponentsTest
JComboBox<String> comboBox3 = new JComboBox<>(); JComboBox<String> comboBox3 = new JComboBox<>();
JLabel spinnerLabel = new JLabel(); JLabel spinnerLabel = new JLabel();
JSpinner spinner1 = new JSpinner(); JSpinner spinner1 = new JSpinner();
JSpinner spinner6 = new JSpinner();
JLabel label2 = new JLabel(); JLabel label2 = new JLabel();
JComboBox<String> comboBox2 = new JComboBox<>(); JComboBox<String> comboBox2 = new JComboBox<>();
JSpinner spinner2 = new JSpinner(); JSpinner spinner2 = new JSpinner();
JLabel label1 = new JLabel(); JLabel label1 = new JLabel();
JComboBox<String> comboBox5 = new JComboBox<>(); comboBox5 = new JComboBox<>();
JSpinner spinner4 = new JSpinner(); spinner4 = new JSpinner();
JLabel label3 = new JLabel(); JLabel label3 = new JLabel();
JComboBox<String> comboBox4 = new JComboBox<>(); JComboBox<String> comboBox4 = new JComboBox<>();
JSpinner spinner3 = new JSpinner(); JSpinner spinner3 = new JSpinner();
JLabel label4 = new JLabel(); JLabel label4 = new JLabel();
JComboBox<String> comboBox6 = new JComboBox<>(); comboBox6 = new JComboBox<>();
JSpinner spinner5 = new JSpinner(); spinner5 = new JSpinner();
JLabel label5 = new JLabel(); JLabel label5 = new JLabel();
textField = new JTextField(); textField = new JTextField();
dragEnabledCheckBox = new JCheckBox(); dragEnabledCheckBox = new JCheckBox();
@@ -563,6 +585,10 @@ public class FlatTextComponentsTest
spinner1.setComponentPopupMenu(popupMenu1); spinner1.setComponentPopupMenu(popupMenu1);
add(spinner1, "cell 1 7,growx"); add(spinner1, "cell 1 7,growx");
//---- spinner6 ----
spinner6.setBorder(BorderFactory.createEmptyBorder());
add(spinner6, "cell 2 7,growx");
//---- label2 ---- //---- label2 ----
label2.setText("<html>Large row height:<br>(default pref height)</html>"); label2.setText("<html>Large row height:<br>(default pref height)</html>");
add(label2, "cell 0 8,aligny top,growy 0"); add(label2, "cell 0 8,aligny top,growy 0");
@@ -690,6 +716,10 @@ public class FlatTextComponentsTest
private JCheckBox trailingComponentVisibleCheckBox; private JCheckBox trailingComponentVisibleCheckBox;
private JCheckBox showClearButtonCheckBox; private JCheckBox showClearButtonCheckBox;
private JCheckBox showRevealButtonCheckBox; private JCheckBox showRevealButtonCheckBox;
private JComboBox<String> comboBox5;
private JSpinner spinner4;
private JComboBox<String> comboBox6;
private JSpinner spinner5;
private JTextField textField; private JTextField textField;
private JCheckBox dragEnabledCheckBox; private JCheckBox dragEnabledCheckBox;
private JTextArea textArea; private JTextArea textArea;

View File

@@ -403,6 +403,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 7,growx" "value": "cell 1 7,growx"
} ) } )
add( new FormComponent( "javax.swing.JSpinner" ) {
name: "spinner6"
"border": new javax.swing.border.EmptyBorder( 0, 0, 0, 0 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 7,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) { add( new FormComponent( "javax.swing.JLabel" ) {
name: "label2" name: "label2"
"text": "<html>Large row height:<br>(default pref height)</html>" "text": "<html>Large row height:<br>(default pref height)</html>"
@@ -435,6 +441,7 @@ new FormModel {
"editable": true "editable": true
auxiliary() { auxiliary() {
"JavaCodeGenerator.typeParameters": "String" "JavaCodeGenerator.typeParameters": "String"
"JavaCodeGenerator.variableLocal": false
} }
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 10,growx" "value": "cell 1 10,growx"
@@ -442,6 +449,9 @@ new FormModel {
add( new FormComponent( "javax.swing.JSpinner" ) { add( new FormComponent( "javax.swing.JSpinner" ) {
name: "spinner4" name: "spinner4"
"preferredSize": new java.awt.Dimension( 60, 40 ) "preferredSize": new java.awt.Dimension( 60, 40 )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 11,growx" "value": "cell 1 11,growx"
} ) } )
@@ -478,6 +488,7 @@ new FormModel {
"minimumSize": new java.awt.Dimension( 60, 14 ) "minimumSize": new java.awt.Dimension( 60, 14 )
auxiliary() { auxiliary() {
"JavaCodeGenerator.typeParameters": "String" "JavaCodeGenerator.typeParameters": "String"
"JavaCodeGenerator.variableLocal": false
} }
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 14,growx" "value": "cell 1 14,growx"
@@ -486,6 +497,9 @@ new FormModel {
name: "spinner5" name: "spinner5"
"minimumSize": new java.awt.Dimension( 60, 14 ) "minimumSize": new java.awt.Dimension( 60, 14 )
"preferredSize": new java.awt.Dimension( 60, 14 ) "preferredSize": new java.awt.Dimension( 60, 14 )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 15,growx,hmax 14" "value": "cell 1 15,growx,hmax 14"
} ) } )