Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macOS themes: make spinner look like macOS stepper #612

Merged
merged 2 commits into from Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -17,7 +17,10 @@
package com.formdev.flatlaf.ui;

import java.awt.Component;
import java.awt.Graphics;
import javax.swing.JSpinner;
import javax.swing.UIManager;
import javax.swing.plaf.SpinnerUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;

/**
Expand All @@ -35,6 +38,19 @@ public class FlatRoundBorder
// only used via styling (not in UI defaults, but has likewise client properties)
/** @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
protected int getArc( Component c ) {
if( isCellEditor( c ) )
Expand All @@ -43,6 +59,17 @@ protected int getArc( Component c ) {
Boolean roundRect = FlatUIUtils.isRoundRect( c );
if( roundRect == null )
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;
}
}
Expand Up @@ -340,20 +340,31 @@ protected Component createPreviousButton() {

private Component createArrowButton( int direction, String name ) {
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.setYOffset( (direction == SwingConstants.NORTH) ? 1.25f : -1.25f );
if( direction == SwingConstants.NORTH )
installNextButtonListeners( button );
else
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;
}

Expand Down Expand Up @@ -381,10 +392,13 @@ public void update( Graphics g, JComponent c ) {
int width = c.getWidth();
int height = c.getHeight();
boolean enabled = spinner.isEnabled();
boolean ltr = spinner.getComponentOrientation().isLeftToRight();
boolean isMacStyle = isMacStyle();
int macStyleButtonsWidth = isMacStyle ? getMacStyleButtonsWidth() : 0;

// paint background
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
boolean paintButton = !"none".equals( buttonStyle );
Expand All @@ -393,22 +407,20 @@ public void update( Graphics g, JComponent c ) {
Component button = (handler.nextButton != null) ? handler.nextButton : handler.previousButton;
int arrowX = button.getX();
int arrowWidth = button.getWidth();
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;

if( "mac".equals( buttonStyle ) ) {
if( isMacStyle ) {
Insets insets = spinner.getInsets();
int gapX = scale( 3 );
int gapY = scale( 1 );
int bx = arrowX + gapX;
int by = insets.top + gapY;
int bw = arrowWidth - (gapX * 2);
int bh = height - insets.top - insets.bottom - (gapY * 2);
int lineWidth = Math.round( FlatUIUtils.getBorderLineWidth( spinner ) );
int bx = arrowX;
int by = insets.top - lineWidth;
int bw = arrowWidth;
int bh = height - insets.top - insets.bottom + (lineWidth * 2);
float lw = scale( buttonSeparatorWidth );

// buttons border
FlatUIUtils.paintOutlinedComponent( g2, bx, by, bw, bh,
0, 0, 0, lw, arc - focusWidth,
0, 0, 0, lw, scale( 12 ),
null, separatorColor, buttonBackground );

// separator between buttons
Expand All @@ -423,7 +435,7 @@ public void update( Graphics g, JComponent c ) {
if( enabled && buttonBackground != null ) {
g2.setColor( buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
if( ltr )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
Expand All @@ -435,7 +447,7 @@ public void update( Graphics g, JComponent c ) {
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
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) ) );
}
}
Expand All @@ -446,6 +458,19 @@ public void update( Graphics g, JComponent c ) {
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 ------------------------------------------------------

private class Handler
Expand Down Expand Up @@ -502,6 +527,7 @@ public void layoutContainer( Container parent ) {
Insets insets = parent.getInsets();
Rectangle r = FlatUIUtils.subtractInsets( new Rectangle( size ), insets );

// editor gets all space if there are no buttons
if( nextButton == null && previousButton == null ) {
if( editor != null )
editor.setBounds( r );
Expand All @@ -517,20 +543,36 @@ public void layoutContainer( Container parent ) {
int minButtonWidth = (maxButtonWidth * 3) / 4;

// make button area square (except if width is limited)
int buttonsWidth = Math.min( Math.max( buttonsRect.height, minButtonWidth ), maxButtonWidth );
buttonsRect.width = buttonsWidth;
boolean isMacStyle = isMacStyle();
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() ) {
editorRect.width -= buttonsWidth;
buttonsRect.x += editorRect.width;
} else {
editorRect.x += buttonsWidth;
editorRect.width -= buttonsWidth;
// update editor and buttons bounds
buttonsRect.width = buttonsWidth;
editorRect.width -= buttonsWidth + buttonsGap;
boolean ltr = parent.getComponentOrientation().isLeftToRight();
if( ltr )
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 )
editor.setBounds( editorRect );

// set buttons bounds
int nextHeight = (buttonsRect.height / 2) + (buttonsRect.height % 2); // round up
if( nextButton != null )
nextButton.setBounds( buttonsRect.x, buttonsRect.y, buttonsRect.width, nextHeight );
Expand Down
Expand Up @@ -253,6 +253,9 @@ Slider.focusedColor = $Component.focusColor
Spinner.buttonStyle = mac
Spinner.disabledBackground = @disabledComponentBackground
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


Expand Down
6 changes: 3 additions & 3 deletions flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt
Expand Up @@ -982,12 +982,12 @@ SliderUI com.formdev.flatlaf.ui.FlatSliderUI
Spinner.arrowButtonSize 16,5 java.awt.Dimension
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.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.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.buttonHoverArrowColor #d1d1d1 HSL 0 0 82 com.formdev.flatlaf.util.DerivedColor [UI] lighten(10%)
Spinner.buttonPressedArrowColor #eaeaea HSL 0 0 92 com.formdev.flatlaf.util.DerivedColor [UI] lighten(20%)
Spinner.buttonHoverArrowColor #f7f7f7 HSL 0 0 97 com.formdev.flatlaf.util.DerivedColor [UI] lighten(10%)
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.buttonSeparatorWidth 0
Spinner.buttonStyle mac
Expand Down
Expand Up @@ -45,6 +45,27 @@ public static void main( String[] args ) {

FlatTextComponentsTest() {
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() {
Expand Down Expand Up @@ -216,18 +237,19 @@ private void initComponents() {
JComboBox<String> comboBox3 = new JComboBox<>();
JLabel spinnerLabel = new JLabel();
JSpinner spinner1 = new JSpinner();
JSpinner spinner6 = new JSpinner();
JLabel label2 = new JLabel();
JComboBox<String> comboBox2 = new JComboBox<>();
JSpinner spinner2 = new JSpinner();
JLabel label1 = new JLabel();
JComboBox<String> comboBox5 = new JComboBox<>();
JSpinner spinner4 = new JSpinner();
comboBox5 = new JComboBox<>();
spinner4 = new JSpinner();
JLabel label3 = new JLabel();
JComboBox<String> comboBox4 = new JComboBox<>();
JSpinner spinner3 = new JSpinner();
JLabel label4 = new JLabel();
JComboBox<String> comboBox6 = new JComboBox<>();
JSpinner spinner5 = new JSpinner();
comboBox6 = new JComboBox<>();
spinner5 = new JSpinner();
JLabel label5 = new JLabel();
textField = new JTextField();
dragEnabledCheckBox = new JCheckBox();
Expand Down Expand Up @@ -563,6 +585,10 @@ private void initComponents() {
spinner1.setComponentPopupMenu(popupMenu1);
add(spinner1, "cell 1 7,growx");

//---- spinner6 ----
spinner6.setBorder(BorderFactory.createEmptyBorder());
add(spinner6, "cell 2 7,growx");

//---- label2 ----
label2.setText("<html>Large row height:<br>(default pref height)</html>");
add(label2, "cell 0 8,aligny top,growy 0");
Expand Down Expand Up @@ -690,6 +716,10 @@ private void initComponents() {
private JCheckBox trailingComponentVisibleCheckBox;
private JCheckBox showClearButtonCheckBox;
private JCheckBox showRevealButtonCheckBox;
private JComboBox<String> comboBox5;
private JSpinner spinner4;
private JComboBox<String> comboBox6;
private JSpinner spinner5;
private JTextField textField;
private JCheckBox dragEnabledCheckBox;
private JTextArea textArea;
Expand Down
Expand Up @@ -403,6 +403,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"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" ) {
name: "label2"
"text": "<html>Large row height:<br>(default pref height)</html>"
Expand Down Expand Up @@ -435,13 +441,17 @@ new FormModel {
"editable": true
auxiliary() {
"JavaCodeGenerator.typeParameters": "String"
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 10,growx"
} )
add( new FormComponent( "javax.swing.JSpinner" ) {
name: "spinner4"
"preferredSize": new java.awt.Dimension( 60, 40 )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 11,growx"
} )
Expand Down Expand Up @@ -478,6 +488,7 @@ new FormModel {
"minimumSize": new java.awt.Dimension( 60, 14 )
auxiliary() {
"JavaCodeGenerator.typeParameters": "String"
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 14,growx"
Expand All @@ -486,6 +497,9 @@ new FormModel {
name: "spinner5"
"minimumSize": new java.awt.Dimension( 60, 14 )
"preferredSize": new java.awt.Dimension( 60, 14 )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 15,growx,hmax 14"
} )
Expand Down