From 69ae33e2afcd880a31b9a7b44fed47bc8d5eacbd Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 29 Aug 2019 15:26:25 +0200 Subject: [PATCH] Spinner basic implementation --- .../java/com/formdev/flatlaf/FlatLaf.java | 3 + .../com/formdev/flatlaf/ui/FlatBorder.java | 11 + .../com/formdev/flatlaf/ui/FlatSpinnerUI.java | 321 ++++++++++++++++++ .../com/formdev/flatlaf/FlatLaf.properties | 11 + .../formdev/flatlaf/FlatTestLaf.properties | 3 +- 5 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index bcc9308c..4bda6975 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -109,6 +109,9 @@ public abstract class FlatLaf defaults.put( "TextPane.disabledBackground", control ); defaults.put( "TextPane.inactiveBackground", control ); + // initialize some own defaults (for overriding) + defaults.put( "Spinner.disabledBackground", control ); + initFonts( defaults ); loadDefaultsFromProperties( defaults ); 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 896b2e79..6e77c909 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 @@ -24,7 +24,10 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Paint; import javax.swing.JComboBox; +import javax.swing.JComponent; import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextField; import javax.swing.JViewport; import javax.swing.UIManager; import javax.swing.plaf.basic.BasicBorders; @@ -98,6 +101,14 @@ public class FlatBorder } else if( c instanceof JComboBox && ((JComboBox)c).isEditable() ) { Component editorComponent = ((JComboBox)c).getEditor().getEditorComponent(); return (editorComponent != null) ? editorComponent.hasFocus() : false; + } else if( c instanceof JSpinner ) { + JComponent editor = ((JSpinner)c).getEditor(); + if( editor instanceof JSpinner.DefaultEditor ) { + JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField(); + if( textField != null ) + return textField.hasFocus(); + } + return false; } else return c.hasFocus(); } 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 new file mode 100644 index 00000000..7457637d --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java @@ -0,0 +1,321 @@ +/* + * Copyright 2019 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 static com.formdev.flatlaf.util.UIScale.scale; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.geom.Rectangle2D; +import javax.swing.JComponent; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicSpinnerUI; + +/** + * Provides the Flat LaF UI delegate for {@link javax.swing.JSpinner}. + * + * TODO document used UI defaults of superclass + * + * @uiDefault Component.focusWidth int + * @uiDefault Component.arc int + * @uiDefault Component.borderColor Color + * @uiDefault Component.disabledBorderColor Color + * @uiDefault Spinner.disabledBackground Color + * @uiDefault Spinner.buttonBackground Color + * @uiDefault Spinner.buttonArrowColor Color + * @uiDefault Spinner.buttonDisabledArrowColor Color + * @uiDefault Spinner.padding Insets + * + * @author Karl Tauber + */ +public class FlatSpinnerUI + extends BasicSpinnerUI +{ + private Handler handler; + + protected int focusWidth; + protected int arc; + protected Color borderColor; + protected Color disabledBorderColor; + protected Color disabledBackground; + protected Color buttonBackground; + protected Color buttonArrowColor; + protected Color buttonDisabledArrowColor; + protected Insets padding; + + public static ComponentUI createUI( JComponent c ) { + return new FlatSpinnerUI(); + } + + @Override + protected void installDefaults() { + super.installDefaults(); + + focusWidth = UIManager.getInt( "Component.focusWidth" ); + arc = UIManager.getInt( "Component.arc" ); + borderColor = UIManager.getColor( "Component.borderColor" ); + disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" ); + disabledBackground = UIManager.getColor( "Spinner.disabledBackground" ); + buttonBackground = UIManager.getColor( "Spinner.buttonBackground" ); + buttonArrowColor = UIManager.getColor( "Spinner.buttonArrowColor" ); + buttonDisabledArrowColor = UIManager.getColor( "Spinner.buttonDisabledArrowColor" ); + padding = UIManager.getInsets( "Spinner.padding" ); + + // scale + padding = scale( padding ); + } + + @Override + protected void uninstallDefaults() { + super.uninstallDefaults(); + + borderColor = null; + disabledBorderColor = null; + disabledBackground = null; + buttonBackground = null; + buttonArrowColor = null; + buttonDisabledArrowColor = null; + padding = null; + } + + @Override + protected void installListeners() { + super.installListeners(); + + addEditorFocusListener( spinner.getEditor() ); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + + removeEditorFocusListener( spinner.getEditor() ); + + handler = null; + } + + public Handler getHandler() { + if( handler == null ) + handler = new Handler(); + return handler; + } + + @Override + protected void replaceEditor( JComponent oldEditor, JComponent newEditor ) { + super.replaceEditor( oldEditor, newEditor ); + + removeEditorFocusListener( oldEditor ); + addEditorFocusListener( newEditor ); + } + + private void addEditorFocusListener( JComponent editor ) { + if( editor instanceof JSpinner.DefaultEditor ) { + JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField(); + if( textField != null ) + textField.addFocusListener( getHandler() ); + } + } + + private void removeEditorFocusListener( JComponent editor ) { + if( editor instanceof JSpinner.DefaultEditor ) { + JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField(); + if( textField != null ) + textField.removeFocusListener( getHandler() ); + } + } + + @Override + protected LayoutManager createLayout() { + return getHandler(); + } + + @Override + protected Component createNextButton() { + return createArrowButton( SwingConstants.NORTH, "Spinner.nextButton" ); + } + + @Override + protected Component createPreviousButton() { + return createArrowButton( SwingConstants.SOUTH, "Spinner.previousButton" ); + } + + private Component createArrowButton( int direction, String name ) { + Component button = new FlatArrowButton( direction, buttonArrowColor, buttonDisabledArrowColor, null, null ); + button.setName( name ); + if( direction == SwingConstants.NORTH ) + installNextButtonListeners( button ); + else + installPreviousButtonListeners( button ); + return button; + } + + @Override + public void update( Graphics g, JComponent c ) { + if( c.isOpaque() ) { + FlatUIUtils.paintParentBackground( g, c ); + + Graphics2D g2 = (Graphics2D) g; + FlatUIUtils.setRenderingHints( g2 ); + + int width = c.getWidth(); + int height = c.getHeight(); + float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0; + float arc = (c.getBorder() instanceof FlatRoundBorder) ? scale( (float) this.arc ) : 0; + Component nextButton = getHandler().nextButton; + int arrowX = nextButton.getX(); + int arrowWidth = nextButton.getWidth(); + boolean enabled = spinner.isEnabled(); + boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight(); + + // paint background + g2.setColor( enabled ? c.getBackground() : disabledBackground ); + FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc ); + + // paint arrow buttons background + if( enabled ) { + g2.setColor( buttonBackground ); + Shape oldClip = g2.getClip(); + if( isLeftToRight ) + g2.clipRect( arrowX, 0, width - arrowX, height ); + else + g2.clipRect( 0, 0, arrowX + arrowWidth, height ); + FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc ); + g2.setClip( oldClip ); + } + + // paint vertical line between value and arrow buttons + g2.setColor( enabled ? borderColor : disabledBorderColor ); + float lw = scale( 1f ); + float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; + g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) ); + } + + paint( g, c ); + } + + //---- class Handler ------------------------------------------------------ + + private class Handler + implements LayoutManager, FocusListener + { + //---- interface LayoutManager ---- + + private Component editor = null; + private Component nextButton; + private Component previousButton; + + @Override + public void addLayoutComponent( String name, Component c ) { + switch( name ) { + case "Editor": editor = c; break; + case "Next": nextButton = c; break; + case "Previous": previousButton = c; break; + } + } + + @Override + public void removeLayoutComponent( Component c ) { + if( c == editor ) + editor = null; + else if( c == nextButton ) + nextButton = null; + else if( c == previousButton ) + previousButton = null; + } + + @Override + public Dimension preferredLayoutSize( Container parent ) { + Insets insets = parent.getInsets(); + Dimension editorSize = (editor != null) ? editor.getPreferredSize() : new Dimension( 0, 0 ); + + // the arrows width is the same as the inner height so that the arrows area is square + int innerHeight = editorSize.height + padding.top + padding.bottom; + return new Dimension( + insets.left + insets.right + editorSize.width + padding.left + padding.right + innerHeight, + insets.top + insets.bottom + innerHeight ); + } + + @Override + public Dimension minimumLayoutSize( Container parent ) { + return preferredLayoutSize( parent ); + } + + @Override + public void layoutContainer( Container parent ) { + Dimension size = parent.getSize(); + Insets insets = parent.getInsets(); + Rectangle r = FlatUIUtils.subtract( new Rectangle( size ), insets ); + + if( nextButton == null && previousButton == null ) { + if( editor != null ) + editor.setBounds( r ); + return; + } + + Rectangle editorRect = new Rectangle( r ); + Rectangle buttonsRect = new Rectangle( r ); + + // make button area square + int buttonsWidth = r.height; + buttonsRect.width = buttonsWidth; + + if( parent.getComponentOrientation().isLeftToRight() ) { + editorRect.width -= buttonsWidth; + buttonsRect.x += editorRect.width; + } else { + editorRect.x += buttonsWidth; + editorRect.width -= buttonsWidth; + } + + if( editor != null ) + editor.setBounds( FlatUIUtils.subtract( editorRect, padding ) ); + + int nextHeight = Math.round( buttonsRect.height / 2f ); + if( nextButton != null ) + nextButton.setBounds( buttonsRect.x, buttonsRect.y, buttonsRect.width, nextHeight ); + if( previousButton != null ) { + previousButton.setBounds( buttonsRect.x, buttonsRect.y + nextHeight, + buttonsRect.width, buttonsRect.height - nextHeight ); + } + } + + //---- interface FocusListener ---- + + @Override + public void focusGained( FocusEvent e ) { + spinner.repaint(); + } + + @Override + public void focusLost( FocusEvent e ) { + spinner.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 d438ff25..58ef2145 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -29,6 +29,7 @@ ScrollBarUI=com.formdev.flatlaf.ui.FlatScrollBarUI ScrollPaneUI=com.formdev.flatlaf.ui.FlatScrollPaneUI SeparatorUI=com.formdev.flatlaf.ui.FlatSeparatorUI SliderUI=com.formdev.flatlaf.ui.FlatSliderUI +SpinnerUI=com.formdev.flatlaf.ui.FlatSpinnerUI SplitPaneUI=com.formdev.flatlaf.ui.FlatSplitPaneUI TabbedPaneUI=com.formdev.flatlaf.ui.FlatTabbedPaneUI TextAreaUI=com.formdev.flatlaf.ui.FlatTextAreaUI @@ -117,6 +118,16 @@ Slider.trackWidth=3 Slider.thumbWidth=11 +#---- Spinner ---- + +Spinner.border=com.formdev.flatlaf.ui.FlatRoundBorder +Spinner.background=@textComponentBackground +Spinner.buttonBackground=@@ComboBox.buttonEditableBackground +Spinner.buttonArrowColor=@@ComboBox.buttonArrowColor +Spinner.buttonDisabledArrowColor=@@ComboBox.buttonDisabledArrowColor +Spinner.padding=@textComponentMargin + + #---- SplitPane ---- SplitPane.dividerSize=5 diff --git a/flatlaf-core/src/test/resources/com/formdev/flatlaf/FlatTestLaf.properties b/flatlaf-core/src/test/resources/com/formdev/flatlaf/FlatTestLaf.properties index 2f340412..579b32a5 100644 --- a/flatlaf-core/src/test/resources/com/formdev/flatlaf/FlatTestLaf.properties +++ b/flatlaf-core/src/test/resources/com/formdev/flatlaf/FlatTestLaf.properties @@ -16,12 +16,13 @@ #---- variables ---- +@background=ccffcc @textComponentBackground=ffffff #---- globals ---- -*.background=ccffcc +*.background=@background *.foreground=ff0000 *.textBackground=ccffcc *.textForeground=ff0000