From 62f0ef19f4c606cfba9d8e7623d0080497d8672e Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 15 Nov 2022 14:27:52 +0100 Subject: [PATCH 1/2] macOS themes: make spinner look like macOS stepper (issue #497; PR #533) --- .../formdev/flatlaf/ui/FlatRoundBorder.java | 29 +++++- .../com/formdev/flatlaf/ui/FlatSpinnerUI.java | 98 +++++++++++++------ .../flatlaf/themes/FlatMacDarkLaf.properties | 1 + .../dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt | 6 +- .../testing/FlatTextComponentsTest.java | 38 ++++++- .../testing/FlatTextComponentsTest.jfd | 14 +++ 6 files changed, 150 insertions(+), 36 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java index bd6e6c539..ac89a411f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRoundBorder.java @@ -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; /** @@ -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 ) ) @@ -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; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java index 14a2c1fe1..292bcf139 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java @@ -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; } @@ -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 ); @@ -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 @@ -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 ); @@ -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) ) ); } } @@ -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 @@ -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 ); @@ -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 ); diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties index d2f684860..f3279d94f 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties @@ -253,6 +253,7 @@ Slider.focusedColor = $Component.focusColor Spinner.buttonStyle = mac Spinner.disabledBackground = @disabledComponentBackground Spinner.buttonBackground = @buttonBackground +Spinner.buttonArrowColor = @foreground Spinner.buttonSeparatorWidth = 0 diff --git a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt index 28f780b0c..d345200b8 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt @@ -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 / #d1d1d1 HSL 0 0 82 com.formdev.flatlaf.util.DerivedColor [UI] lighten(10%) +Spinner.buttonPressedArrowColor #ffffff HSL 0 0 100 / #eaeaea HSL 0 0 92 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 diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java index b9dbeb409..c12e8935a 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java @@ -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() { @@ -216,18 +237,19 @@ private void initComponents() { JComboBox comboBox3 = new JComboBox<>(); JLabel spinnerLabel = new JLabel(); JSpinner spinner1 = new JSpinner(); + JSpinner spinner6 = new JSpinner(); JLabel label2 = new JLabel(); JComboBox comboBox2 = new JComboBox<>(); JSpinner spinner2 = new JSpinner(); JLabel label1 = new JLabel(); - JComboBox comboBox5 = new JComboBox<>(); - JSpinner spinner4 = new JSpinner(); + comboBox5 = new JComboBox<>(); + spinner4 = new JSpinner(); JLabel label3 = new JLabel(); JComboBox comboBox4 = new JComboBox<>(); JSpinner spinner3 = new JSpinner(); JLabel label4 = new JLabel(); - JComboBox comboBox6 = new JComboBox<>(); - JSpinner spinner5 = new JSpinner(); + comboBox6 = new JComboBox<>(); + spinner5 = new JSpinner(); JLabel label5 = new JLabel(); textField = new JTextField(); dragEnabledCheckBox = new JCheckBox(); @@ -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("Large row height:
(default pref height)"); add(label2, "cell 0 8,aligny top,growy 0"); @@ -690,6 +716,10 @@ private void initComponents() { private JCheckBox trailingComponentVisibleCheckBox; private JCheckBox showClearButtonCheckBox; private JCheckBox showRevealButtonCheckBox; + private JComboBox comboBox5; + private JSpinner spinner4; + private JComboBox comboBox6; + private JSpinner spinner5; private JTextField textField; private JCheckBox dragEnabledCheckBox; private JTextArea textArea; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd index 41e11bf18..179190c4c 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd @@ -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": "Large row height:
(default pref height)" @@ -435,6 +441,7 @@ new FormModel { "editable": true auxiliary() { "JavaCodeGenerator.typeParameters": "String" + "JavaCodeGenerator.variableLocal": false } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 10,growx" @@ -442,6 +449,9 @@ new FormModel { 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" } ) @@ -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" @@ -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" } ) From a1f18e1ec979611d9b6a291829d0f758681f0f18 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 16 Nov 2022 10:51:38 +0100 Subject: [PATCH 2/2] macOS themes: fixed spinner arrow hover/pressed colors (issue #497; PR #533) --- .../com/formdev/flatlaf/themes/FlatMacDarkLaf.properties | 2 ++ flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties index f3279d94f..533740354 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/themes/FlatMacDarkLaf.properties @@ -254,6 +254,8 @@ 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 diff --git a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt index d345200b8..eb04a1e3d 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatMacDarkLaf_1.8.0.txt @@ -986,8 +986,8 @@ Spinner.buttonArrowColor #dddddd HSL 0 0 87 javax.swing.plaf.Colo 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 #f7f7f7 HSL 0 0 97 / #d1d1d1 HSL 0 0 82 com.formdev.flatlaf.util.DerivedColor [UI] lighten(10%) -Spinner.buttonPressedArrowColor #ffffff HSL 0 0 100 / #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