From f0a49c806e68c72efc7edd7bc4e672273a26d0a1 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 11 Feb 2020 15:38:32 +0100 Subject: [PATCH] DesktopPane support implemented (issues #39 and #11) --- CHANGELOG.md | 2 +- .../icons/FlatInternalFrameCloseIcon.java | 2 +- .../formdev/flatlaf/ui/FlatDesktopIconUI.java | 308 ++++++++++++++++++ .../formdev/flatlaf/ui/FlatDesktopPaneUI.java | 26 ++ .../formdev/flatlaf/FlatDarkLaf.properties | 5 + .../com/formdev/flatlaf/FlatLaf.properties | 10 +- .../formdev/flatlaf/FlatLightLaf.properties | 5 + .../flatlaf/testing/FlatTestLaf.properties | 5 + 8 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopIconUI.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c83fc7e8..d9ac68e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ FlatLaf Change Log ## Unreleased -- Support `JInternalFrame`. (issues #39 and #11) +- Support `JInternalFrame` and `JDesktopPane`. (issues #39 and #11) - Table: Support positioning the column sort arrow in header right, left, top or bottom. (issue #34) - ProgressBar: Fixed visual artifacts in indeterminate mode, on HiDPI screens at diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatInternalFrameCloseIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatInternalFrameCloseIcon.java index 3203d7d18..2461480dc 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatInternalFrameCloseIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatInternalFrameCloseIcon.java @@ -49,7 +49,7 @@ public FlatInternalFrameCloseIcon() { protected void paintIcon( Component c, Graphics2D g ) { paintBackground( c, g ); - g.setColor( FlatButtonUI.buttonStateColor( c, null, null, null, hoverForeground, pressedForeground ) ); + g.setColor( FlatButtonUI.buttonStateColor( c, c.getForeground(), null, null, hoverForeground, pressedForeground ) ); float mx = width / 2; float my = height / 2; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopIconUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopIconUI.java new file mode 100644 index 000000000..51382592b --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopIconUI.java @@ -0,0 +1,308 @@ +/* + * Copyright 2020 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 + * + * https://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.Container; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Point; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.beans.PropertyVetoException; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.event.MouseInputAdapter; +import javax.swing.event.MouseInputListener; +import javax.swing.JLabel; +import javax.swing.JLayeredPane; +import javax.swing.JRootPane; +import javax.swing.JToolTip; +import javax.swing.LookAndFeel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicDesktopIconUI; +import com.formdev.flatlaf.util.UIScale; + +/** + * Provides the Flat LaF UI delegate for {@link javax.swing.JInternalFrame.JDesktopIcon}. + * + * + * + * @uiDefault DesktopIcon.border Border + * + * + * + * @uiDefault DesktopIcon.background Color + * @uiDefault DesktopIcon.foreground Color + * @uiDefault DesktopIcon.iconSize Dimension + * @uiDefault DesktopIcon.closeSize Dimension + * @uiDefault DesktopIcon.closeIcon Icon + * + * @author Karl Tauber + */ +public class FlatDesktopIconUI + extends BasicDesktopIconUI +{ + private Dimension iconSize; + private Dimension closeSize; + + private JLabel dockIcon; + private JButton closeButton; + private JToolTip titleTip; + private ActionListener closeListener; + private MouseInputListener mouseInputListener; + + public static ComponentUI createUI( JComponent c ) { + return new FlatDesktopIconUI(); + } + + @Override + public void uninstallUI( JComponent c ) { + super.uninstallUI( c ); + + dockIcon = null; + closeButton = null; + } + + @Override + protected void installComponents() { + dockIcon = new JLabel(); + dockIcon.setHorizontalAlignment( SwingConstants.CENTER ); + + closeButton = new JButton(); + closeButton.setIcon( UIManager.getIcon( "DesktopIcon.closeIcon" ) ); + closeButton.setFocusable( false ); + closeButton.setBorder( BorderFactory.createEmptyBorder() ); + closeButton.setOpaque( true ); + closeButton.setBackground( FlatUIUtils.nonUIResource( desktopIcon.getBackground() ) ); + closeButton.setForeground( FlatUIUtils.nonUIResource( desktopIcon.getForeground() ) ); + closeButton.setVisible( false ); + + desktopIcon.setLayout( new FlatDesktopIconLayout() ); + desktopIcon.add( closeButton ); + desktopIcon.add( dockIcon ); + } + + @Override + protected void uninstallComponents() { + hideTitleTip(); + + desktopIcon.remove( dockIcon ); + desktopIcon.remove( closeButton ); + desktopIcon.setLayout( null ); + } + + @Override + protected void installDefaults() { + super.installDefaults(); + + LookAndFeel.installColors( desktopIcon, "DesktopIcon.background", "DesktopIcon.foreground" ); + + iconSize = UIManager.getDimension( "DesktopIcon.iconSize" ); + closeSize = UIManager.getDimension( "DesktopIcon.closeSize" ); + } + + @Override + protected void installListeners() { + super.installListeners(); + + closeListener = e -> { + if( frame.isClosable() ) + frame.doDefaultCloseAction(); + }; + closeButton.addActionListener( closeListener ); + closeButton.addMouseListener( mouseInputListener ); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + + closeButton.removeActionListener( closeListener ); + closeButton.removeMouseListener( mouseInputListener ); + closeListener = null; + mouseInputListener = null; + } + + @Override + protected MouseInputListener createMouseInputListener() { + mouseInputListener = new MouseInputAdapter() { + @Override + public void mouseReleased( MouseEvent e ) { + if( frame.isIcon() && desktopIcon.contains( e.getX(), e.getY() ) ) { + hideTitleTip(); + closeButton.setVisible( false ); + + try { + frame.setIcon( false ); + } catch( PropertyVetoException ex ) { + // ignore + } + } + } + + @Override + public void mouseEntered( MouseEvent e ) { + showTitleTip(); + if( frame.isClosable() ) + closeButton.setVisible( true ); + } + + @Override + public void mouseExited( MouseEvent e ) { + hideTitleTip(); + closeButton.setVisible( false ); + } + }; + return mouseInputListener; + } + + private void showTitleTip() { + JRootPane rootPane = SwingUtilities.getRootPane( desktopIcon ); + if( rootPane == null ) + return; + + if( titleTip == null ) { + titleTip = new JToolTip(); + rootPane.getLayeredPane().add( titleTip, JLayeredPane.POPUP_LAYER ); + } + titleTip.setTipText( frame.getTitle() ); + titleTip.setSize( titleTip.getPreferredSize() ); + + int tx = (desktopIcon.getWidth() - titleTip.getWidth()) / 2; + int ty = -(titleTip.getHeight() + UIScale.scale( 4 )); + Point pt = SwingUtilities.convertPoint( desktopIcon, tx, ty, titleTip.getParent() ); + if( pt.x + titleTip.getWidth() > rootPane.getWidth() ) + pt.x = rootPane.getWidth() - titleTip.getWidth(); + if( pt.x < 0 ) + pt.x = 0; + titleTip.setLocation( pt ); + titleTip.repaint(); + } + + private void hideTitleTip() { + if( titleTip == null ) + return; + + titleTip.setVisible( false ); + titleTip.getParent().remove( titleTip ); + titleTip = null; + } + + @Override + public Dimension getPreferredSize( JComponent c ) { + return UIScale.scale( iconSize ); + } + + @Override + public Dimension getMinimumSize( JComponent c ) { + return getPreferredSize( c ); + } + + @Override + public Dimension getMaximumSize( JComponent c ) { + return getPreferredSize( c ); + } + + void updateDockIcon() { + // use invoke later to make sure that components are updated when switching LaF + EventQueue.invokeLater( () -> { + if( dockIcon != null ) + updateDockIconLater(); + } ); + } + + private void updateDockIconLater() { + // make sure that frame is not selected + if( frame.isSelected() ) { + try { + frame.setSelected( false ); + } catch( PropertyVetoException ex ) { + // ignore + } + } + + // paint internal frame to buffered image + int frameWidth = Math.max( frame.getWidth(), 1 ); + int frameHeight = Math.max( frame.getHeight(), 1 ); + BufferedImage frameImage = new BufferedImage( frameWidth, frameHeight, BufferedImage.TYPE_INT_ARGB ); + Graphics2D g = frameImage.createGraphics(); + try { + //TODO fix missing internal frame header when switching LaF + frame.paint( g ); + } finally { + g.dispose(); + } + + // compute preview size (keep ratio; also works with non-square preview) + Insets insets = desktopIcon.getInsets(); + int previewWidth = UIScale.scale( iconSize.width ) - insets.left - insets.right; + int previewHeight = UIScale.scale( iconSize.height ) - insets.top - insets.bottom; + float frameRatio = ((float) frameHeight / (float) frameWidth); + if( ((float) previewWidth / (float) frameWidth) > ((float) previewHeight / (float) frameHeight) ) + previewWidth = Math.round( previewHeight / frameRatio ); + else + previewHeight = Math.round( previewWidth * frameRatio ); + + // scale preview + Image previewImage = frameImage.getScaledInstance( previewWidth, previewHeight, Image.SCALE_SMOOTH ); + dockIcon.setIcon( new ImageIcon( previewImage ) ); + } + + //---- class DockIcon ----------------------------------------------------- + + private class FlatDesktopIconLayout + implements LayoutManager + { + @Override public void addLayoutComponent( String name, Component comp ) {} + @Override public void removeLayoutComponent( Component comp ) {} + + @Override + public Dimension preferredLayoutSize( Container parent ) { + return dockIcon.getPreferredSize(); + } + + @Override + public Dimension minimumLayoutSize( Container parent ) { + return dockIcon.getMinimumSize(); + } + + @Override + public void layoutContainer( Container parent ) { + Insets insets = parent.getInsets(); + + // dock icon + dockIcon.setBounds( insets.left, insets.top, + parent.getWidth() - insets.left - insets.right, + parent.getHeight() - insets.top - insets.bottom ); + + // close button in upper right corner + Dimension cSize = UIScale.scale( closeSize ); + closeButton.setBounds( parent.getWidth() - cSize.width, 0, cSize.width, cSize.height ); + } + } +} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopPaneUI.java index bcaa08962..eb5b72e95 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDesktopPaneUI.java @@ -16,8 +16,11 @@ package com.formdev.flatlaf.ui; +import javax.swing.DefaultDesktopManager; import javax.swing.JComponent; +import javax.swing.JInternalFrame; import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicDesktopPaneUI; /** @@ -36,4 +39,27 @@ public class FlatDesktopPaneUI public static ComponentUI createUI( JComponent c ) { return new FlatDesktopPaneUI(); } + + @Override + protected void installDesktopManager() { + desktopManager = desktop.getDesktopManager(); + if( desktopManager == null ) { + desktopManager = new FlatDesktopManager(); + desktop.setDesktopManager( desktopManager ); + } + } + + //---- class FlatDesktopManager ------------------------------------------- + + private class FlatDesktopManager + extends DefaultDesktopManager + implements UIResource + { + @Override + public void iconifyFrame( JInternalFrame f ) { + super.iconifyFrame( f ); + + ((FlatDesktopIconUI)f.getDesktopIcon().getUI()).updateDockIcon(); + } + } } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties index 41153a26b..4fdbfc5cf 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatDarkLaf.properties @@ -136,6 +136,11 @@ Component.linkColor=#589df6 Desktop.background=#3E434C +#---- DesktopIcon ---- + +DesktopIcon.background=lighten($Desktop.background,10%) + + #---- InternalFrame ---- InternalFrame.activeTitleBackground=darken(@background,10%) 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 768712912..7dac36c87 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -21,7 +21,7 @@ CheckBoxUI=com.formdev.flatlaf.ui.FlatCheckBoxUI CheckBoxMenuItemUI=com.formdev.flatlaf.ui.FlatCheckBoxMenuItemUI ColorChooserUI=com.formdev.flatlaf.ui.FlatColorChooserUI ComboBoxUI=com.formdev.flatlaf.ui.FlatComboBoxUI -DesktopIconUI=javax.swing.plaf.basic.BasicDesktopIconUI +DesktopIconUI=com.formdev.flatlaf.ui.FlatDesktopIconUI DesktopPaneUI=com.formdev.flatlaf.ui.FlatDesktopPaneUI EditorPaneUI=com.formdev.flatlaf.ui.FlatEditorPaneUI FileChooserUI=com.formdev.flatlaf.ui.FlatFileChooserUI @@ -151,6 +151,14 @@ Component.arrowType=chevron Component.hideMnemonics=true +#---- DesktopIcon ---- + +DesktopIcon.border=4,4,4,4 +DesktopIcon.iconSize=64,64 +DesktopIcon.closeSize=20,20 +DesktopIcon.closeIcon=com.formdev.flatlaf.icons.FlatInternalFrameCloseIcon + + #---- EditorPane ---- EditorPane.border=com.formdev.flatlaf.ui.FlatMarginBorder diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties index c8c66dc09..4b84cc72d 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties @@ -138,6 +138,11 @@ Component.linkColor=#2470B3 Desktop.background=#E6EBF0 +#---- DesktopIcon ---- + +DesktopIcon.background=darken($Desktop.background,10%) + + #---- HelpButton ---- HelpButton.questionMarkColor=#4F9EE3 diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties index 3ce5b3f61..b70f564db 100644 --- a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties +++ b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/FlatTestLaf.properties @@ -135,6 +135,11 @@ Component.focusColor=#97c3f3 Desktop.background=#afe +#---- DesktopIcon ---- + +DesktopIcon.background=darken($Desktop.background,20%) + + #---- HelpButton ---- HelpButton.questionMarkColor=#0000ff