Skip to content

How to use HoverManager

Mikle edited this page Aug 15, 2019 · 5 revisions

Available since: WebLaF v1.2.10 release
Required Java version: Java 6 update 30 or any later
Module: ui


What is it for?

One of the things lacking in Swing is ability to globally track various component states. One of such states is hover - it is a simple state informing that user has mouse cursor hovering over the component that has the state.

There are various solutions to how hover state can be implemented for Swing components - most popular is simply registering MouseListener on the component and tracking mouse enter and exit events. But there are multiple issues with that approach - hover state won't be properly removed in some cases because you simply won't receive "exit" event until mouse is moved. Also it can be completely skipped when component is hidden or removed. It is also not sufficient to track hover state application-wide.

To solve these and some other problems I've created HoverManager that continuously tracks hovered component through multiple registered AWTEventListeners and makes sure that various listeners you can register in HoverManager are informed about every change they might be interested in.


How to use it?

The only way to use HoverManager is to register one of the available listeners, so first you need to decide what exactly you want to listen for - all hover change events or only ones for a particular component.

All listeners receive events from the same source, but they are filtered and delivered differently.

GlobalHoverListener

Can be registered to listen to all hover change events:

HoverManager.registerGlobalHoverListener ( new GlobalHoverListener ()
{
    @Override
    public void hoverChanged ( @Nullable final Component oldHover, @Nullable final Component newHover )
    {
        ...
    }
} );

It can also be registered using an existing component:

HoverManager.registerGlobalHoverListener ( component, new GlobalHoverListener ()
{
    @Override
    public void hoverChanged ( @Nullable final Component oldHover, @Nullable final Component newHover )
    {
        ...
    }
} );

That method is particularly useful if you have a component that will be listening to provided events. Events will not be limited to that component, but listener will be registered in HoverManager differently to help prevent possible memory leaks through the listener itself.

I recommend using second option or registering HoverTracker for particular component as described in the next section.

Here is a small practical example of GlobalHoverListener usage:

public final class GlobalHoverListenerExample
{
    public static void main ( final String[] args )
    {
        SwingUtilities.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                WebLookAndFeel.install ();

                final WebLabel label = new WebLabel ( "...", WebLabel.CENTER );
                final WebColorChooserPanel panel = new WebColorChooserPanel ();
                TestFrame.show ( 10, false, 10, label, panel );

                HoverManager.registerGlobalHoverListener ( label, new GlobalHoverListener ()
                {
                    @Override
                    public void hoverChanged ( @Nullable final Component oldHover, @Nullable final Component newHover )
                    {
                        final String prev = oldHover != null ? ReflectUtils.getClassName ( oldHover ) : "none";
                        final String next = newHover != null ? ReflectUtils.getClassName ( newHover ) : "none";
                        label.setText ( prev   " -> "   next );
                    }
                } );
            }
        } );
    }
}

It will display a small frame with color chooser and label showing what component is currently under the cursor:

HoverTracker

DefaultHoverTracker implementation covers most common cases of hover state tracking - when cursor is on top of the component or one of the component children within it's bounds.

It is generally recommended to use DefaultHoverTracker instead of writing a custom HoverTracker implementation.

Any HoverTracker can be registered in HoverManager like this:

HoverManager.addHoverTracker ( component, new DefaultHoverTracker ( component, false )
{
    @Override
    public void hoverChanged ( final boolean hover )
    {
        ...
    }
} );

Here is a small practical example of HoverTracker usage:

public final class HoverTrackerExample
{
    public static void main ( final String[] args )
    {
        SwingUtilities.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                WebLookAndFeel.install ();

                final WebLabel label = new WebLabel ( "Panel is not hovered", WebLabel.CENTER );

                final WebCheckBox directHoverOnly = new WebCheckBox ( "Direct hover only", false );
                directHoverOnly.setHorizontalAlignment ( WebCheckBox.CENTER );

                final WebButton button = new WebButton ( "Sample button" );

                final WebPanel panel = new WebPanel ( StyleId.panelDecorated, new BorderLayout (), button );
                panel.setBackground ( Color.WHITE );
                panel.setPadding ( 20 );

                TestFrame.show ( 20, false, 20, label, directHoverOnly, panel );

                final DefaultHoverTracker hoverTracker = new DefaultHoverTracker ( panel, false )
                {
                    @Override
                    public void hoverChanged ( final boolean hover )
                    {
                        label.setText ( hover ? "Panel is hovered" : "Panel is not hovered" );
                    }
                };
                HoverManager.addHoverTracker ( panel, hoverTracker );

                directHoverOnly.addActionListener ( new ActionListener ()
                {
                    @Override
                    public void actionPerformed ( final ActionEvent e )
                    {
                        hoverTracker.setDirectHoverOnly ( directHoverOnly.isSelected () );
                    }
                } );
            }
        } );
    }
}

It will display a small frame with label showing whether or not panel is currently hovered:

You can also enable or disable directHoverOnly setting in the DefaultHoverTracker that switches tracking mode, affecting whether or not cursor hovering any of the component's children within it's bounds should also count as hovering the component itself.

Now if you actually want to provide different set of conditions for deciding whether or not component is hovered based on the actual hovered component - you may actually want to write your own HoverTracker implementation and specify your conditions in isInvolved ( tracked, component ) method where tracked is your component and component is the actual hovered component.


That's all you need to know to start using this feature.
I will update HoverManager with more options in the future, so stay tuned!