mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-06 22:10:54 +03:00
Popup: fixed flicker of popups (e.g. tooltips) while they are moving (e.g. following mouse pointer) (issues #832 and #672)
This commit is contained in:
@@ -6,6 +6,8 @@ FlatLaf Change Log
|
|||||||
#### New features and improvements
|
#### New features and improvements
|
||||||
|
|
||||||
- Label: Support painting background with rounded corners. (issue #842)
|
- Label: Support painting background with rounded corners. (issue #842)
|
||||||
|
- Popup: Fixed flicker of popups (e.g. tooltips) while they are moving (e.g.
|
||||||
|
following mouse pointer). (issues #832 and #672)
|
||||||
|
|
||||||
#### Incompatibilities
|
#### Incompatibilities
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.formdev.flatlaf.ui;
|
package com.formdev.flatlaf.ui;
|
||||||
|
|
||||||
import java.awt.AWTEvent;
|
import java.awt.AWTEvent;
|
||||||
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.Container;
|
import java.awt.Container;
|
||||||
@@ -41,6 +42,7 @@ import java.lang.invoke.MethodHandle;
|
|||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JLayeredPane;
|
import javax.swing.JLayeredPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
@@ -76,6 +78,8 @@ public class FlatPopupFactory
|
|||||||
private MethodHandle java8getPopupMethod;
|
private MethodHandle java8getPopupMethod;
|
||||||
private MethodHandle java9getPopupMethod;
|
private MethodHandle java9getPopupMethod;
|
||||||
|
|
||||||
|
private final ArrayList<NonFlashingPopup> stillShownHeavyWeightPopups = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Popup getPopup( Component owner, Component contents, int x, int y )
|
public Popup getPopup( Component owner, Component contents, int x, int y )
|
||||||
throws IllegalArgumentException
|
throws IllegalArgumentException
|
||||||
@@ -88,14 +92,27 @@ public class FlatPopupFactory
|
|||||||
|
|
||||||
fixLinuxWaylandJava21focusIssue( owner );
|
fixLinuxWaylandJava21focusIssue( owner );
|
||||||
|
|
||||||
|
// reuse a heavy weight popup window, which is still shown on screen,
|
||||||
|
// to avoid flicker when popup (e.g. tooltip) is moving while mouse is moved
|
||||||
|
for( NonFlashingPopup popup : stillShownHeavyWeightPopups ) {
|
||||||
|
if( popup.delegate != null &&
|
||||||
|
popup.owner == owner &&
|
||||||
|
(popup.contents == contents ||
|
||||||
|
(popup.contents instanceof JToolTip && contents instanceof JToolTip)) )
|
||||||
|
{
|
||||||
|
stillShownHeavyWeightPopups.remove( popup );
|
||||||
|
return reuseStillShownHeavyWeightPopups( popup, contents, x, y );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
|
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
|
||||||
|
|
||||||
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing )
|
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing )
|
||||||
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), contents );
|
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents );
|
||||||
|
|
||||||
// macOS and Linux adds drop shadow to heavy weight popups
|
// macOS and Linux adds drop shadow to heavy weight popups
|
||||||
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
|
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
|
||||||
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
|
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
|
||||||
if( popup.popupWindow != null && SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() )
|
if( popup.popupWindow != null && SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() )
|
||||||
setupRoundedBorder( popup.popupWindow, owner, contents );
|
setupRoundedBorder( popup.popupWindow, owner, contents );
|
||||||
return popup;
|
return popup;
|
||||||
@@ -105,7 +122,7 @@ public class FlatPopupFactory
|
|||||||
if( isWindows11BorderSupported() &&
|
if( isWindows11BorderSupported() &&
|
||||||
getBorderCornerRadius( owner, contents ) > 0 )
|
getBorderCornerRadius( owner, contents ) > 0 )
|
||||||
{
|
{
|
||||||
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
|
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
|
||||||
if( popup.popupWindow != null )
|
if( popup.popupWindow != null )
|
||||||
setupRoundedBorder( popup.popupWindow, owner, contents );
|
setupRoundedBorder( popup.popupWindow, owner, contents );
|
||||||
return popup;
|
return popup;
|
||||||
@@ -227,6 +244,24 @@ public class FlatPopupFactory
|
|||||||
return UIManager.get( uiKey );
|
return UIManager.get( uiKey );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reuse a heavy weight popup window, which is still shown on screen,
|
||||||
|
* by updating window location and contents.
|
||||||
|
* This avoid flicker when popup (e.g. a tooltip) is moving while mouse is moved.
|
||||||
|
* E.g. overridden JComponent.getToolTipLocation(MouseEvent).
|
||||||
|
* See ToolTipManager.checkForTipChange(MouseEvent).
|
||||||
|
*/
|
||||||
|
private static NonFlashingPopup reuseStillShownHeavyWeightPopups(
|
||||||
|
NonFlashingPopup reusePopup, Component contents, int ownerX, int ownerY )
|
||||||
|
{
|
||||||
|
// clone popup because PopupFactory.getPopup() should not return old instance
|
||||||
|
NonFlashingPopup popup = reusePopup.cloneForReuse();
|
||||||
|
|
||||||
|
// update popup location, size and contents
|
||||||
|
popup.reset( contents, ownerX, ownerY );
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
|
||||||
//---- tooltips -----------------------------------------------------------
|
//---- tooltips -----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -490,18 +525,31 @@ public class FlatPopupFactory
|
|||||||
|
|
||||||
//---- class NonFlashingPopup ---------------------------------------------
|
//---- class NonFlashingPopup ---------------------------------------------
|
||||||
|
|
||||||
private static class NonFlashingPopup
|
/**
|
||||||
|
* Fixes popup background flashing effect when using dark theme on light platform theme,
|
||||||
|
* where the light popup background is shown for a fraction of a second before
|
||||||
|
* the dark popup content is shown.
|
||||||
|
* This is fixed by setting popup background to content background.
|
||||||
|
* <p>
|
||||||
|
* Defers hiding of heavy weight popup window for an event cycle,
|
||||||
|
* which allows reusing popup window to avoid flicker when "moving" popup.
|
||||||
|
*/
|
||||||
|
private class NonFlashingPopup
|
||||||
extends Popup
|
extends Popup
|
||||||
{
|
{
|
||||||
private Popup delegate;
|
private Popup delegate;
|
||||||
|
Component owner;
|
||||||
private Component contents;
|
private Component contents;
|
||||||
|
|
||||||
// heavy weight
|
// heavy weight
|
||||||
protected Window popupWindow;
|
Window popupWindow;
|
||||||
private Color oldPopupWindowBackground;
|
private Color oldPopupWindowBackground;
|
||||||
|
|
||||||
NonFlashingPopup( Popup delegate, Component contents ) {
|
private boolean disposed;
|
||||||
|
|
||||||
|
NonFlashingPopup( Popup delegate, Component owner, Component contents ) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
this.owner = owner;
|
||||||
this.contents = contents;
|
this.contents = contents;
|
||||||
|
|
||||||
popupWindow = SwingUtilities.windowForComponent( contents );
|
popupWindow = SwingUtilities.windowForComponent( contents );
|
||||||
@@ -515,8 +563,27 @@ public class FlatPopupFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NonFlashingPopup( NonFlashingPopup reusePopup ) {
|
||||||
|
delegate = reusePopup.delegate;
|
||||||
|
owner = reusePopup.owner;
|
||||||
|
contents = reusePopup.contents;
|
||||||
|
popupWindow = reusePopup.popupWindow;
|
||||||
|
oldPopupWindowBackground = reusePopup.oldPopupWindowBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
NonFlashingPopup cloneForReuse() {
|
||||||
|
return new NonFlashingPopup( this );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void show() {
|
public final void show() {
|
||||||
|
if( disposed )
|
||||||
|
return;
|
||||||
|
|
||||||
|
showImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void showImpl() {
|
||||||
if( delegate != null ) {
|
if( delegate != null ) {
|
||||||
showPopupAndFixLocation( delegate, popupWindow );
|
showPopupAndFixLocation( delegate, popupWindow );
|
||||||
|
|
||||||
@@ -540,13 +607,36 @@ public class FlatPopupFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hide() {
|
public final void hide() {
|
||||||
|
if( disposed )
|
||||||
|
return;
|
||||||
|
disposed = true;
|
||||||
|
|
||||||
|
// immediately hide non-heavy weight popups or combobox popups
|
||||||
|
if( !(popupWindow instanceof JWindow) || contents instanceof BasicComboPopup ) {
|
||||||
|
hideImpl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer hiding of heavy weight popup window for an event cycle,
|
||||||
|
// which allows reusing popup window to avoid flicker when "moving" popup
|
||||||
|
((JWindow)popupWindow).getContentPane().removeAll();
|
||||||
|
stillShownHeavyWeightPopups.add( this );
|
||||||
|
EventQueue.invokeLater( () -> {
|
||||||
|
// hide popup if it was not reused
|
||||||
|
if( stillShownHeavyWeightPopups.remove( this ) )
|
||||||
|
hideImpl();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
void hideImpl() {
|
||||||
if( contents instanceof JComponent )
|
if( contents instanceof JComponent )
|
||||||
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null );
|
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null );
|
||||||
|
|
||||||
if( delegate != null ) {
|
if( delegate != null ) {
|
||||||
delegate.hide();
|
delegate.hide();
|
||||||
delegate = null;
|
delegate = null;
|
||||||
|
owner = null;
|
||||||
contents = null;
|
contents = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,6 +647,30 @@ public class FlatPopupFactory
|
|||||||
popupWindow = null;
|
popupWindow = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset( Component contents, int ownerX, int ownerY ) {
|
||||||
|
// update popup window location
|
||||||
|
popupWindow.setLocation( ownerX, ownerY );
|
||||||
|
|
||||||
|
// replace component in content pane
|
||||||
|
Container contentPane = ((JWindow)popupWindow).getContentPane();
|
||||||
|
contentPane.removeAll();
|
||||||
|
contentPane.add( contents, BorderLayout.CENTER );
|
||||||
|
popupWindow.invalidate();
|
||||||
|
popupWindow.validate();
|
||||||
|
popupWindow.pack();
|
||||||
|
|
||||||
|
// update client property on contents
|
||||||
|
if( this.contents != contents ) {
|
||||||
|
Object old = (this.contents instanceof JComponent)
|
||||||
|
? ((JComponent)this.contents).getClientProperty( KEY_POPUP_USES_NATIVE_BORDER )
|
||||||
|
: null;
|
||||||
|
if( contents instanceof JComponent )
|
||||||
|
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, old );
|
||||||
|
|
||||||
|
this.contents = contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//---- class DropShadowPopup ----------------------------------------------
|
//---- class DropShadowPopup ----------------------------------------------
|
||||||
@@ -564,8 +678,6 @@ public class FlatPopupFactory
|
|||||||
private class DropShadowPopup
|
private class DropShadowPopup
|
||||||
extends NonFlashingPopup
|
extends NonFlashingPopup
|
||||||
{
|
{
|
||||||
private final Component owner;
|
|
||||||
|
|
||||||
// light weight
|
// light weight
|
||||||
private JComponent lightComp;
|
private JComponent lightComp;
|
||||||
private Border oldBorder;
|
private Border oldBorder;
|
||||||
@@ -580,11 +692,11 @@ public class FlatPopupFactory
|
|||||||
// heavy weight
|
// heavy weight
|
||||||
private Popup dropShadowDelegate;
|
private Popup dropShadowDelegate;
|
||||||
private Window dropShadowWindow;
|
private Window dropShadowWindow;
|
||||||
|
private JPanel dropShadowPanel2;
|
||||||
private Color oldDropShadowWindowBackground;
|
private Color oldDropShadowWindowBackground;
|
||||||
|
|
||||||
DropShadowPopup( Popup delegate, Component owner, Component contents ) {
|
DropShadowPopup( Popup delegate, Component owner, Component contents ) {
|
||||||
super( delegate, contents );
|
super( delegate, owner, contents );
|
||||||
this.owner = owner;
|
|
||||||
|
|
||||||
Dimension size = contents.getPreferredSize();
|
Dimension size = contents.getPreferredSize();
|
||||||
if( size.width <= 0 || size.height <= 0 )
|
if( size.width <= 0 || size.height <= 0 )
|
||||||
@@ -600,24 +712,24 @@ public class FlatPopupFactory
|
|||||||
// the drop shadow and is positioned behind the popup window.
|
// the drop shadow and is positioned behind the popup window.
|
||||||
|
|
||||||
// create panel that paints the drop shadow
|
// create panel that paints the drop shadow
|
||||||
JPanel dropShadowPanel = new JPanel();
|
dropShadowPanel2 = new JPanel();
|
||||||
dropShadowPanel.setBorder( createDropShadowBorder() );
|
dropShadowPanel2.setBorder( createDropShadowBorder() );
|
||||||
dropShadowPanel.setOpaque( false );
|
dropShadowPanel2.setOpaque( false );
|
||||||
|
|
||||||
// set preferred size of drop shadow panel
|
// set preferred size of drop shadow panel
|
||||||
Dimension prefSize = popupWindow.getPreferredSize();
|
Dimension prefSize = popupWindow.getPreferredSize();
|
||||||
Insets insets = dropShadowPanel.getInsets();
|
Insets insets = dropShadowPanel2.getInsets();
|
||||||
dropShadowPanel.setPreferredSize( new Dimension(
|
dropShadowPanel2.setPreferredSize( new Dimension(
|
||||||
prefSize.width + insets.left + insets.right,
|
prefSize.width + insets.left + insets.right,
|
||||||
prefSize.height + insets.top + insets.bottom ) );
|
prefSize.height + insets.top + insets.bottom ) );
|
||||||
|
|
||||||
// create heavy weight popup for drop shadow
|
// create heavy weight popup for drop shadow
|
||||||
int x = popupWindow.getX() - insets.left;
|
int x = popupWindow.getX() - insets.left;
|
||||||
int y = popupWindow.getY() - insets.top;
|
int y = popupWindow.getY() - insets.top;
|
||||||
dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel, x, y, true );
|
dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel2, x, y, true );
|
||||||
|
|
||||||
// make drop shadow popup window translucent
|
// make drop shadow popup window translucent
|
||||||
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel );
|
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel2 );
|
||||||
if( dropShadowWindow != null ) {
|
if( dropShadowWindow != null ) {
|
||||||
oldDropShadowWindowBackground = dropShadowWindow.getBackground();
|
oldDropShadowWindowBackground = dropShadowWindow.getBackground();
|
||||||
dropShadowWindow.setBackground( new Color( 0, true ) );
|
dropShadowWindow.setBackground( new Color( 0, true ) );
|
||||||
@@ -654,6 +766,23 @@ public class FlatPopupFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DropShadowPopup( DropShadowPopup reusePopup ) {
|
||||||
|
super( reusePopup );
|
||||||
|
|
||||||
|
// not necessary to clone fields used for light/medium weight popups
|
||||||
|
|
||||||
|
// heavy weight
|
||||||
|
dropShadowDelegate = reusePopup.dropShadowDelegate;
|
||||||
|
dropShadowWindow = reusePopup.dropShadowWindow;
|
||||||
|
dropShadowPanel2 = reusePopup.dropShadowPanel2;
|
||||||
|
oldDropShadowWindowBackground = reusePopup.oldDropShadowWindowBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
NonFlashingPopup cloneForReuse() {
|
||||||
|
return new DropShadowPopup( this );
|
||||||
|
}
|
||||||
|
|
||||||
private Border createDropShadowBorder() {
|
private Border createDropShadowBorder() {
|
||||||
return new FlatDropShadowBorder(
|
return new FlatDropShadowBorder(
|
||||||
UIManager.getColor( "Popup.dropShadowColor" ),
|
UIManager.getColor( "Popup.dropShadowColor" ),
|
||||||
@@ -662,14 +791,14 @@ public class FlatPopupFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void show() {
|
void showImpl() {
|
||||||
if( dropShadowDelegate != null )
|
if( dropShadowDelegate != null )
|
||||||
showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow );
|
showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow );
|
||||||
|
|
||||||
if( mediumWeightPanel != null )
|
if( mediumWeightPanel != null )
|
||||||
showMediumWeightDropShadow();
|
showMediumWeightDropShadow();
|
||||||
|
|
||||||
super.show();
|
super.showImpl();
|
||||||
|
|
||||||
// fix location of light weight popup in case it has left or top drop shadow
|
// fix location of light weight popup in case it has left or top drop shadow
|
||||||
if( lightComp != null ) {
|
if( lightComp != null ) {
|
||||||
@@ -680,10 +809,11 @@ public class FlatPopupFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hide() {
|
void hideImpl() {
|
||||||
if( dropShadowDelegate != null ) {
|
if( dropShadowDelegate != null ) {
|
||||||
dropShadowDelegate.hide();
|
dropShadowDelegate.hide();
|
||||||
dropShadowDelegate = null;
|
dropShadowDelegate = null;
|
||||||
|
dropShadowPanel2 = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mediumWeightPanel != null ) {
|
if( mediumWeightPanel != null ) {
|
||||||
@@ -692,7 +822,7 @@ public class FlatPopupFactory
|
|||||||
mediumWeightPanel = null;
|
mediumWeightPanel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.hide();
|
super.hideImpl();
|
||||||
|
|
||||||
if( dropShadowWindow != null ) {
|
if( dropShadowWindow != null ) {
|
||||||
dropShadowWindow.setBackground( oldDropShadowWindowBackground );
|
dropShadowWindow.setBackground( oldDropShadowWindowBackground );
|
||||||
@@ -776,5 +906,25 @@ public class FlatPopupFactory
|
|||||||
if( dropShadowPanel != null && mediumWeightPanel != null )
|
if( dropShadowPanel != null && mediumWeightPanel != null )
|
||||||
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
|
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void reset( Component contents, int ownerX, int ownerY ) {
|
||||||
|
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 ) );
|
||||||
|
|
||||||
|
// update drop shadow popup window location and size
|
||||||
|
int x = popupWindow.getX() - insets.left;
|
||||||
|
int y = popupWindow.getY() - insets.top;
|
||||||
|
dropShadowWindow.setBounds( x, y, w, h );
|
||||||
|
dropShadowWindow.pack();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,12 @@
|
|||||||
package com.formdev.flatlaf.testing;
|
package com.formdev.flatlaf.testing;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.Random;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
import com.formdev.flatlaf.util.Animator;
|
import com.formdev.flatlaf.util.Animator;
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import net.miginfocom.swing.*;
|
import net.miginfocom.swing.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +31,8 @@ import net.miginfocom.swing.*;
|
|||||||
public class FlatPopupTest
|
public class FlatPopupTest
|
||||||
extends FlatTestPanel
|
extends FlatTestPanel
|
||||||
{
|
{
|
||||||
private Popup popup;
|
private Popup[] popups;
|
||||||
|
private JPanel[] popupPanels;
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
public static void main( String[] args ) {
|
||||||
SwingUtilities.invokeLater( () -> {
|
SwingUtilities.invokeLater( () -> {
|
||||||
@@ -54,19 +59,25 @@ public class FlatPopupTest
|
|||||||
|
|
||||||
private void showPopup( int xoffset, int yoffset ) {
|
private void showPopup( int xoffset, int yoffset ) {
|
||||||
hidePopup();
|
hidePopup();
|
||||||
|
createPopupPanels();
|
||||||
|
int xoffset2 = popupPanels[0].getPreferredSize().width + UIScale.scale( 10 );
|
||||||
|
|
||||||
Point pt = showPopupButton.getLocationOnScreen();
|
Point pt = showPopupButton.getLocationOnScreen();
|
||||||
popup = PopupFactory.getSharedInstance().getPopup( showPopupButton, popupPanel,
|
popups = new Popup[popupPanels.length];
|
||||||
pt.x + xoffset, pt.y + showPopupButton.getHeight() + yoffset );
|
for( int i = 0; i < popupPanels.length; i++ ) {
|
||||||
popup.show();
|
popups[i] = PopupFactory.getSharedInstance().getPopup( this, popupPanels[i],
|
||||||
|
pt.x + xoffset + (xoffset2 * i), pt.y + showPopupButton.getHeight() + yoffset );
|
||||||
|
popups[i].show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hidePopup() {
|
private void hidePopup() {
|
||||||
if( popup == null )
|
if( popups == null )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
popup.hide();
|
for( Popup popup : popups )
|
||||||
popup = null;
|
popup.hide();
|
||||||
|
popups = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void movePopupDown() {
|
private void movePopupDown() {
|
||||||
@@ -80,13 +91,29 @@ public class FlatPopupTest
|
|||||||
private void movePopup( int xoffset, int yoffset ) {
|
private void movePopup( int xoffset, int yoffset ) {
|
||||||
showPopup();
|
showPopup();
|
||||||
|
|
||||||
Animator animator = new Animator( 1000, fraction -> {
|
Animator animator = new Animator( 1500, fraction -> {
|
||||||
System.out.println(fraction);
|
// System.out.println(fraction);
|
||||||
showPopup( (int) (fraction * xoffset), (int) (fraction * yoffset) );
|
showPopup( (int) (fraction * xoffset), (int) (fraction * yoffset) );
|
||||||
} );
|
} );
|
||||||
animator.start();
|
animator.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createPopupPanels() {
|
||||||
|
int count = (int) countField.getValue();
|
||||||
|
if( popupPanels != null && popupPanels.length == count )
|
||||||
|
return;
|
||||||
|
|
||||||
|
Random random = new Random();
|
||||||
|
popupPanels = new JPanel[count];
|
||||||
|
for( int i = 0; i < popupPanels.length; i++ ) {
|
||||||
|
JLabel l = new JLabel( "popup " + (i + 1) );
|
||||||
|
JPanel p = new JPanel();
|
||||||
|
p.setBackground( new Color( random.nextInt( 0xffffff ) ) );
|
||||||
|
p.add( l );
|
||||||
|
popupPanels[i] = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateUI() {
|
public void updateUI() {
|
||||||
super.updateUI();
|
super.updateUI();
|
||||||
@@ -94,8 +121,15 @@ public class FlatPopupTest
|
|||||||
if( popupMenu1 != null ) {
|
if( popupMenu1 != null ) {
|
||||||
SwingUtilities.updateComponentTreeUI( popupMenu1 );
|
SwingUtilities.updateComponentTreeUI( popupMenu1 );
|
||||||
SwingUtilities.updateComponentTreeUI( popupMenu2 );
|
SwingUtilities.updateComponentTreeUI( popupMenu2 );
|
||||||
SwingUtilities.updateComponentTreeUI( popupPanel );
|
|
||||||
}
|
}
|
||||||
|
if( popupPanels != null ) {
|
||||||
|
for( JPanel popupPanel : popupPanels )
|
||||||
|
SwingUtilities.updateComponentTreeUI( popupPanel );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void countChanged() {
|
||||||
|
// TODO add your code here
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
@@ -107,16 +141,17 @@ public class FlatPopupTest
|
|||||||
showPopupButton = new JButton();
|
showPopupButton = new JButton();
|
||||||
hidePopupButton = new JButton();
|
hidePopupButton = new JButton();
|
||||||
movePopupDownButton = new JButton();
|
movePopupDownButton = new JButton();
|
||||||
movePopuprightButton = new JButton();
|
movePopupRightButton = new JButton();
|
||||||
|
countLabel = new JLabel();
|
||||||
|
countField = new JSpinner();
|
||||||
label4 = new JLabel();
|
label4 = new JLabel();
|
||||||
|
movingToolTipPanel = new MovingToolTipPanel();
|
||||||
popupMenu1 = new JPopupMenu();
|
popupMenu1 = new JPopupMenu();
|
||||||
menuItem1 = new JMenuItem();
|
menuItem1 = new JMenuItem();
|
||||||
menuItem2 = new JMenuItem();
|
menuItem2 = new JMenuItem();
|
||||||
menu1 = new JMenu();
|
menu1 = new JMenu();
|
||||||
menuItem3 = new JMenuItem();
|
menuItem3 = new JMenuItem();
|
||||||
menuItem4 = new JMenuItem();
|
menuItem4 = new JMenuItem();
|
||||||
popupPanel = new JPanel();
|
|
||||||
label3 = new JLabel();
|
|
||||||
popupMenu2 = new JPopupMenu();
|
popupMenu2 = new JPopupMenu();
|
||||||
menuItem5 = new JMenuItem();
|
menuItem5 = new JMenuItem();
|
||||||
menuItem6 = new JMenuItem();
|
menuItem6 = new JMenuItem();
|
||||||
@@ -146,14 +181,18 @@ public class FlatPopupTest
|
|||||||
"[fill]" +
|
"[fill]" +
|
||||||
"[fill]" +
|
"[fill]" +
|
||||||
"[fill]" +
|
"[fill]" +
|
||||||
"[fill]",
|
"[fill]" +
|
||||||
|
"[fill]" +
|
||||||
|
"[fill]" +
|
||||||
|
"[grow,fill]",
|
||||||
// rows
|
// rows
|
||||||
"[]" +
|
"[]" +
|
||||||
"[]" +
|
"[]" +
|
||||||
"[]" +
|
"[]" +
|
||||||
"[]" +
|
"[]" +
|
||||||
"[]" +
|
"[]" +
|
||||||
"[]"));
|
"[]" +
|
||||||
|
"[grow,fill]"));
|
||||||
|
|
||||||
//---- label1 ----
|
//---- label1 ----
|
||||||
label1.setText("Label with light-weight tooltip");
|
label1.setText("Label with light-weight tooltip");
|
||||||
@@ -190,14 +229,24 @@ public class FlatPopupTest
|
|||||||
movePopupDownButton.addActionListener(e -> movePopupDown());
|
movePopupDownButton.addActionListener(e -> movePopupDown());
|
||||||
add(movePopupDownButton, "cell 2 4");
|
add(movePopupDownButton, "cell 2 4");
|
||||||
|
|
||||||
//---- movePopuprightButton ----
|
//---- movePopupRightButton ----
|
||||||
movePopuprightButton.setText("move right");
|
movePopupRightButton.setText("move right");
|
||||||
movePopuprightButton.addActionListener(e -> movePopupRight());
|
movePopupRightButton.addActionListener(e -> movePopupRight());
|
||||||
add(movePopuprightButton, "cell 3 4");
|
add(movePopupRightButton, "cell 3 4");
|
||||||
|
|
||||||
|
//---- countLabel ----
|
||||||
|
countLabel.setText("Count:");
|
||||||
|
add(countLabel, "cell 4 4");
|
||||||
|
|
||||||
|
//---- countField ----
|
||||||
|
countField.setModel(new SpinnerNumberModel(1, 1, null, 1));
|
||||||
|
countField.addChangeListener(e -> countChanged());
|
||||||
|
add(countField, "cell 5 4");
|
||||||
|
|
||||||
//---- label4 ----
|
//---- label4 ----
|
||||||
label4.setText("(switches to heavy-weight when moving outside of window)");
|
label4.setText("(switches to heavy-weight when moving outside of window)");
|
||||||
add(label4, "cell 0 5 4 1,alignx right,growx 0");
|
add(label4, "cell 0 5 4 1,alignx right,growx 0");
|
||||||
|
add(movingToolTipPanel, "cell 0 6 7 1");
|
||||||
|
|
||||||
//======== popupMenu1 ========
|
//======== popupMenu1 ========
|
||||||
{
|
{
|
||||||
@@ -225,21 +274,6 @@ public class FlatPopupTest
|
|||||||
popupMenu1.add(menu1);
|
popupMenu1.add(menu1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//======== popupPanel ========
|
|
||||||
{
|
|
||||||
popupPanel.setBackground(new Color(153, 255, 153));
|
|
||||||
popupPanel.setLayout(new MigLayout(
|
|
||||||
"hidemode 3",
|
|
||||||
// columns
|
|
||||||
"[fill]",
|
|
||||||
// rows
|
|
||||||
"[]"));
|
|
||||||
|
|
||||||
//---- label3 ----
|
|
||||||
label3.setText("popup");
|
|
||||||
popupPanel.add(label3, "cell 0 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
//======== popupMenu2 ========
|
//======== popupMenu2 ========
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -336,16 +370,17 @@ public class FlatPopupTest
|
|||||||
private JButton showPopupButton;
|
private JButton showPopupButton;
|
||||||
private JButton hidePopupButton;
|
private JButton hidePopupButton;
|
||||||
private JButton movePopupDownButton;
|
private JButton movePopupDownButton;
|
||||||
private JButton movePopuprightButton;
|
private JButton movePopupRightButton;
|
||||||
|
private JLabel countLabel;
|
||||||
|
private JSpinner countField;
|
||||||
private JLabel label4;
|
private JLabel label4;
|
||||||
|
private MovingToolTipPanel movingToolTipPanel;
|
||||||
private JPopupMenu popupMenu1;
|
private JPopupMenu popupMenu1;
|
||||||
private JMenuItem menuItem1;
|
private JMenuItem menuItem1;
|
||||||
private JMenuItem menuItem2;
|
private JMenuItem menuItem2;
|
||||||
private JMenu menu1;
|
private JMenu menu1;
|
||||||
private JMenuItem menuItem3;
|
private JMenuItem menuItem3;
|
||||||
private JMenuItem menuItem4;
|
private JMenuItem menuItem4;
|
||||||
private JPanel popupPanel;
|
|
||||||
private JLabel label3;
|
|
||||||
private JPopupMenu popupMenu2;
|
private JPopupMenu popupMenu2;
|
||||||
private JMenuItem menuItem5;
|
private JMenuItem menuItem5;
|
||||||
private JMenuItem menuItem6;
|
private JMenuItem menuItem6;
|
||||||
@@ -368,4 +403,45 @@ public class FlatPopupTest
|
|||||||
private JMenuItem menuItem22;
|
private JMenuItem menuItem22;
|
||||||
private JMenuItem menuItem23;
|
private JMenuItem menuItem23;
|
||||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||||
|
|
||||||
|
//---- class MovingToolTipPanel -------------------------------------------
|
||||||
|
|
||||||
|
private static class MovingToolTipPanel
|
||||||
|
extends JPanel
|
||||||
|
{
|
||||||
|
private MovingToolTipPanel() {
|
||||||
|
initComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolTipText( MouseEvent e ) {
|
||||||
|
return e.getX() + "," + e.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point getToolTipLocation( MouseEvent e ) {
|
||||||
|
// multiply Y by two to make it possible to move tooltip outside of window,
|
||||||
|
// which forces use of heavy weight popups for all Lafs
|
||||||
|
return new Point( e.getX() , e.getY() * 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
|
||||||
|
label6 = new JLabel();
|
||||||
|
|
||||||
|
//======== this ========
|
||||||
|
setBorder(new LineBorder(Color.red));
|
||||||
|
setToolTipText("text");
|
||||||
|
setLayout(new FlowLayout());
|
||||||
|
|
||||||
|
//---- label6 ----
|
||||||
|
label6.setText("moving tooltip area");
|
||||||
|
add(label6);
|
||||||
|
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
|
||||||
|
private JLabel label6;
|
||||||
|
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
JFDML JFormDesigner: "7.0.4.0.360" Java: "16" encoding: "UTF-8"
|
JFDML JFormDesigner: "8.2.3.0.386" Java: "21" encoding: "UTF-8"
|
||||||
|
|
||||||
new FormModel {
|
new FormModel {
|
||||||
contentType: "form/swing"
|
contentType: "form/swing"
|
||||||
root: new FormRoot {
|
root: new FormRoot {
|
||||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||||
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
|
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
|
||||||
"$columnConstraints": "[fill][fill][fill][fill]"
|
"$columnConstraints": "[fill][fill][fill][fill][fill][fill][grow,fill]"
|
||||||
"$rowConstraints": "[][][][][][]"
|
"$rowConstraints": "[][][][][][][grow,fill]"
|
||||||
} ) {
|
} ) {
|
||||||
name: "this"
|
name: "this"
|
||||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
@@ -59,21 +59,51 @@ new FormModel {
|
|||||||
"value": "cell 2 4"
|
"value": "cell 2 4"
|
||||||
} )
|
} )
|
||||||
add( new FormComponent( "javax.swing.JButton" ) {
|
add( new FormComponent( "javax.swing.JButton" ) {
|
||||||
name: "movePopuprightButton"
|
name: "movePopupRightButton"
|
||||||
"text": "move right"
|
"text": "move right"
|
||||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "movePopupRight", false ) )
|
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "movePopupRight", false ) )
|
||||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
"value": "cell 3 4"
|
"value": "cell 3 4"
|
||||||
} )
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
|
name: "countLabel"
|
||||||
|
"text": "Count:"
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 4 4"
|
||||||
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JSpinner" ) {
|
||||||
|
name: "countField"
|
||||||
|
"model": new javax.swing.SpinnerNumberModel {
|
||||||
|
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"
|
||||||
|
} )
|
||||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
name: "label4"
|
name: "label4"
|
||||||
"text": "(switches to heavy-weight when moving outside of window)"
|
"text": "(switches to heavy-weight when moving outside of window)"
|
||||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
"value": "cell 0 5 4 1,alignx right,growx 0"
|
"value": "cell 0 5 4 1,alignx right,growx 0"
|
||||||
} )
|
} )
|
||||||
|
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) {
|
||||||
|
name: "movingToolTipPanel"
|
||||||
|
"border": new javax.swing.border.LineBorder( sfield java.awt.Color red, 1, false )
|
||||||
|
"toolTipText": "text"
|
||||||
|
auxiliary() {
|
||||||
|
"JavaCodeGenerator.className": "MovingToolTipPanel"
|
||||||
|
}
|
||||||
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
|
name: "label6"
|
||||||
|
"text": "moving tooltip area"
|
||||||
|
} )
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 6 7 1"
|
||||||
|
} )
|
||||||
}, new FormLayoutConstraints( null ) {
|
}, new FormLayoutConstraints( null ) {
|
||||||
"location": new java.awt.Point( 0, 0 )
|
"location": new java.awt.Point( 0, 0 )
|
||||||
"size": new java.awt.Dimension( 490, 350 )
|
"size": new java.awt.Dimension( 700, 350 )
|
||||||
} )
|
} )
|
||||||
add( new FormContainer( "javax.swing.JPopupMenu", new FormLayoutManager( class javax.swing.JPopupMenu ) ) {
|
add( new FormContainer( "javax.swing.JPopupMenu", new FormLayoutManager( class javax.swing.JPopupMenu ) ) {
|
||||||
name: "popupMenu1"
|
name: "popupMenu1"
|
||||||
@@ -100,23 +130,6 @@ new FormModel {
|
|||||||
}, new FormLayoutConstraints( null ) {
|
}, new FormLayoutConstraints( null ) {
|
||||||
"location": new java.awt.Point( 5, 395 )
|
"location": new java.awt.Point( 5, 395 )
|
||||||
} )
|
} )
|
||||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
|
||||||
"$layoutConstraints": "hidemode 3"
|
|
||||||
"$columnConstraints": "[fill]"
|
|
||||||
"$rowConstraints": "[]"
|
|
||||||
} ) {
|
|
||||||
name: "popupPanel"
|
|
||||||
"background": new java.awt.Color( 153, 255, 153, 255 )
|
|
||||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
|
||||||
name: "label3"
|
|
||||||
"text": "popup"
|
|
||||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
|
||||||
"value": "cell 0 0"
|
|
||||||
} )
|
|
||||||
}, new FormLayoutConstraints( null ) {
|
|
||||||
"location": new java.awt.Point( 225, 395 )
|
|
||||||
"size": new java.awt.Dimension( 200, 200 )
|
|
||||||
} )
|
|
||||||
add( new FormContainer( "javax.swing.JPopupMenu", new FormLayoutManager( class javax.swing.JPopupMenu ) ) {
|
add( new FormContainer( "javax.swing.JPopupMenu", new FormLayoutManager( class javax.swing.JPopupMenu ) ) {
|
||||||
name: "popupMenu2"
|
name: "popupMenu2"
|
||||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||||
|
|||||||
Reference in New Issue
Block a user