From cca9707f6b3982b3cdac9faaef431b939a0deb17 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 25 Jan 2025 06:55:37 +0100 Subject: [PATCH] Popup: on Windows 10, update drop shadow of heavy-weight popup if popup moved/resized (issue #942) --- CHANGELOG.md | 2 + .../flatlaf/ui/FlatDropShadowBorder.java | 2 +- .../formdev/flatlaf/ui/FlatPopupFactory.java | 69 ++++++++-- .../flatlaf/testing/FlatPopupTest.java | 123 +++++++++++++++++- .../formdev/flatlaf/testing/FlatPopupTest.jfd | 44 ++++++- 5 files changed, 218 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f86e20c..77d9561b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ FlatLaf Change Log - FileChooser: Improved performance when navigating to large directories with thousands of files. (issue #953) - PopupFactory: Fixed NPE on Windows 10 when `owner` is `null`. (issue #952) +- Popup: On Windows 10, drop shadow of heavy-weight popup was not updated if + popup moved/resized. (issue #942) - FlatLaf window decorations: Minimize and maximize icons were not shown for custom scale factors less than 100% (e.g. `-Dflatlaf.uiScale=75%`). (issue #951) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java index 6914daf9..25f09c6d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatDropShadowBorder.java @@ -74,7 +74,7 @@ public class FlatDropShadowBorder this.shadowColor = shadowColor; this.shadowInsets = shadowInsets; - this.shadowOpacity = shadowOpacity; + this.shadowOpacity = Math.min( Math.max( shadowOpacity, 0f ), 1f ); shadowSize = maxInset( shadowInsets ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java index 9b14bab2..553695d8 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java @@ -709,6 +709,7 @@ public class FlatPopupFactory private class DropShadowPopup extends NonFlashingPopup + implements ComponentListener { // light weight private JComponent lightComp; @@ -768,7 +769,7 @@ public class FlatPopupFactory } // Windows 11: reset corner preference on reused heavy weight popups - if( isWindows11BorderSupported() ) { + if( SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded() ) { resetWindows11Border( popupWindow ); if( dropShadowWindow != null ) resetWindows11Border( dropShadowWindow ); @@ -838,10 +839,18 @@ public class FlatPopupFactory if( insets.left != 0 || insets.top != 0 ) lightComp.setLocation( lightComp.getX() - insets.left, lightComp.getY() - insets.top ); } + + if( popupWindow != null ) { + removeAllPopupWindowComponentListeners(); + popupWindow.addComponentListener( this ); + } } @Override void hideImpl() { + if( popupWindow != null ) + removeAllPopupWindowComponentListeners(); + if( dropShadowDelegate != null ) { dropShadowDelegate.hide(); dropShadowDelegate = null; @@ -941,23 +950,55 @@ public class FlatPopupFactory @Override void reset( Component contents, int ownerX, int ownerY ) { + if( popupWindow != null ) + removeAllPopupWindowComponentListeners(); + super.reset( contents, ownerX, ownerY ); - if( dropShadowWindow != null ) { - // set preferred size of drop shadow panel - Dimension prefSize = popupWindow.getPreferredSize(); - Insets insets = dropShadowPanel2.getInsets(); - int w = prefSize.width + insets.left + insets.right; - int h = prefSize.height + insets.top + insets.bottom; - dropShadowPanel2.setPreferredSize( new Dimension( w, h ) ); - dropShadowPanel2.invalidate(); - dropShadowWindow.pack(); + updateDropShadowWindowBounds(); + } - // update drop shadow popup window location - int x = popupWindow.getX() - insets.left; - int y = popupWindow.getY() - insets.top; - dropShadowWindow.setLocation( x, y ); + private void updateDropShadowWindowBounds() { + if( dropShadowWindow == null ) + return; + + // calculate size of drop shadow window + Dimension size = popupWindow.getSize(); + Insets insets = dropShadowPanel2.getInsets(); + int w = size.width + insets.left + insets.right; + int h = size.height + insets.top + insets.bottom; + + // update drop shadow popup window bounds + int x = popupWindow.getX() - insets.left; + int y = popupWindow.getY() - insets.top; + dropShadowWindow.setBounds( x, y, w, h ); + dropShadowWindow.validate(); + } + + private void removeAllPopupWindowComponentListeners() { + // make sure that there is no old component listener + // necessary because this class is cloned if reusing popup windows + for( ComponentListener l : popupWindow.getComponentListeners() ) { + if( l instanceof DropShadowPopup ) + popupWindow.removeComponentListener( l ); } } + + //---- interface ComponentListener ---- + + @Override + public void componentResized( ComponentEvent e ) { + if( e.getSource() == popupWindow ) + updateDropShadowWindowBounds(); + } + + @Override + public void componentMoved( ComponentEvent e ) { + if( e.getSource() == popupWindow ) + updateDropShadowWindowBounds(); + } + + @Override public void componentShown( ComponentEvent e ) {} + @Override public void componentHidden( ComponentEvent e ) {} } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.java index e2776ba6..3f97c6a4 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.java @@ -21,6 +21,10 @@ import java.awt.event.MouseEvent; import java.util.Random; import javax.swing.*; import javax.swing.border.*; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.UIScale; import net.miginfocom.swing.*; @@ -43,6 +47,8 @@ public class FlatPopupTest FlatPopupTest() { initComponents(); + addPopupMenuListener( popupMenu1, "popupMenu1" ); + addPopupMenuListener( popupMenu2, "popupMenu2" ); } private void showPopupMenu() { @@ -114,6 +120,46 @@ public class FlatPopupTest } } + private void showDirectPopup() { + DirectPopupContent content = new DirectPopupContent(); + content.putClientProperty( FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, true ); + Point pt = showDirectPopupButton.getLocationOnScreen(); + + System.setProperty( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, "false" ); + UIManager.put( "Popup.dropShadowColor", Color.red ); + UIManager.put( "Popup.dropShadowInsets", new Insets( 5, 5, 5, 5 ) ); + UIManager.put( "Popup.dropShadowOpacity", 1f ); + + Popup popup = PopupFactory.getSharedInstance().getPopup( showDirectPopupButton, + content, pt.x, pt.y + showDirectPopupButton.getHeight() + 10 ); + content.popup = popup; + popup.show(); + + System.clearProperty( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER ); + UIManager.put( "Popup.dropShadowColor", null ); + UIManager.put( "Popup.dropShadowInsets", null ); + UIManager.put( "Popup.dropShadowOpacity", null ); + } + + private void addPopupMenuListener( JPopupMenu popupMenu, String name ) { + popupMenu.addPopupMenuListener( new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible( PopupMenuEvent e ) { + System.out.println( "popupMenuWillBecomeVisible " + name ); + } + + @Override + public void popupMenuWillBecomeInvisible( PopupMenuEvent e ) { + System.out.println( "popupMenuWillBecomeInvisible " + name ); + } + + @Override + public void popupMenuCanceled( PopupMenuEvent e ) { + System.out.println( "popupMenuCanceled " + name ); + } + } ); + } + @Override public void updateUI() { super.updateUI(); @@ -128,15 +174,12 @@ public class FlatPopupTest } } - private void countChanged() { - // TODO add your code here - } - private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents label1 = new JLabel(); label2 = new JLabel(); showPopupMenuButton = new JButton(); + showDirectPopupButton = new JButton(); showLargePopupMenuButton = new JButton(); showPopupButton = new JButton(); hidePopupButton = new JButton(); @@ -209,6 +252,11 @@ public class FlatPopupTest showPopupMenuButton.addActionListener(e -> showPopupMenu()); add(showPopupMenuButton, "cell 0 2"); + //---- showDirectPopupButton ---- + showDirectPopupButton.setText("show direct move/resize popup"); + showDirectPopupButton.addActionListener(e -> showDirectPopup()); + add(showDirectPopupButton, "cell 2 2 2 1"); + //---- showLargePopupMenuButton ---- showLargePopupMenuButton.setText("show heavy-weight JPopupMenu"); showLargePopupMenuButton.addActionListener(e -> showLargePopupMenu()); @@ -240,7 +288,6 @@ public class FlatPopupTest //---- countField ---- countField.setModel(new SpinnerNumberModel(1, 1, null, 1)); - countField.addChangeListener(e -> countChanged()); add(countField, "cell 5 4"); //---- label4 ---- @@ -366,6 +413,7 @@ public class FlatPopupTest private JLabel label1; private JLabel label2; private JButton showPopupMenuButton; + private JButton showDirectPopupButton; private JButton showLargePopupMenuButton; private JButton showPopupButton; private JButton hidePopupButton; @@ -444,4 +492,69 @@ public class FlatPopupTest private JLabel label6; // JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on } + + //---- class MyPopupContent ----------------------------------------------- + + private static class DirectPopupContent + extends JPanel + { + Popup popup; + + DirectPopupContent() { + initComponents(); + } + + private void resizePopup() { + Window popupWindow = SwingUtilities.windowForComponent( this ); + popupWindow.setSize( popupWindow.getWidth() + 20, popupWindow.getHeight() + 50 ); + } + + private void movePopup() { + Window popupWindow = SwingUtilities.windowForComponent( this ); + popupWindow.setLocation( popupWindow.getX() + 20, popupWindow.getY() + 50 ); + } + + private void hidePopup() { + popup.hide(); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off + resizeButton = new JButton(); + moveButton = new JButton(); + hideButton = new JButton(); + + //======== this ======== + setLayout(new MigLayout( + "hidemode 3", + // columns + "[fill]" + + "[fill]" + + "[fill]", + // rows + "[]")); + + //---- resizeButton ---- + resizeButton.setText("Resize"); + resizeButton.addActionListener(e -> resizePopup()); + add(resizeButton, "cell 0 0"); + + //---- moveButton ---- + moveButton.setText("Move"); + moveButton.addActionListener(e -> movePopup()); + add(moveButton, "cell 1 0"); + + //---- hideButton ---- + hideButton.setText("Hide"); + hideButton.addActionListener(e -> hidePopup()); + add(hideButton, "cell 2 0"); + // JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off + private JButton resizeButton; + private JButton moveButton; + private JButton hideButton; + // JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on + } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.jfd index d6739f69..42f369de 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPopupTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.2.3.0.386" Java: "21" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -30,6 +30,13 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 2" } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "showDirectPopupButton" + "text": "show direct move/resize popup" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showDirectPopup", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 2 2 1" + } ) add( new FormComponent( "javax.swing.JButton" ) { name: "showLargePopupMenuButton" "text": "show heavy-weight JPopupMenu" @@ -77,7 +84,6 @@ new FormModel { minimum: 1 value: 1 } - addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "countChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 5 4" } ) @@ -215,5 +221,39 @@ new FormModel { }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 5, 505 ) } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "hidemode 3" + "$columnConstraints": "[fill][fill][fill]" + "$rowConstraints": "[]" + } ) { + name: "panel1" + auxiliary() { + "JavaCodeGenerator.className": "DirectPopupContent" + } + add( new FormComponent( "javax.swing.JButton" ) { + name: "resizeButton" + "text": "Resize" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "resizePopup", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "moveButton" + "text": "Move" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "movePopup", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "hideButton" + "text": "Hide" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hidePopup", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 180, 395 ) + "size": new java.awt.Dimension( 270, 100 ) + } ) } }